Windows 7 下 MySql Guid 问题

之前的文章提到过关于 MySql 数据库要如何保存 .NET Guid 类型的问题.

根据 MySQL Connector Net Version 5.2 - 2/11/2008 CHANGES 中提到的:

>
BINARY(16) columns are now returned as Guid objects

那么使用 BINARY(16) 列做为 Guid 的载体是理所当然的了.

通过一系统的试验, 读取是没问题的, 写入时要先转化为二进制数组 (ToByteArray), 因为只有这样才能保证长度是16, 否则任何方法都会告诉你数据太长.

我是个不安分的人, 正如你在标题中看到的, 刚刚忍不住装上了 Windows 7 beta1, 在一阵兴奋和陶醉过后, 撞上了第一个棘手的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#light
#r @"D:\Program Files\MySQL\MySQL Connector Net 5.2.5\Binaries\.NET 2.0\MySql.Data.dll"
open System
open System.Data
open MySql.Data.MySqlClient

let conn =
let builder = MySqlConnectionStringBuilder(CharacterSet = "utf8", Server = "localhost", UserID = "root", Password = "erp", Database = "erp")
let conn = new MySqlConnection(builder.ConnectionString)
conn.Open()
conn

let cmd = conn.CreateCommand()

cmd.CommandText <- "
DROP TABLE IF EXISTS `TestGuid`;
CREATE TABLE IF NOT EXISTS `TestGuid`
(`ID` BINARY(16))
ENGINE = InnoDB;
INSERT
INTO `TestGuid`
VALUES (0x012345670123012301230123456789ab);
INSERT
INTO `TestGuid`
VALUES (?_ID);"


let guid = Guid("12345678-1234-1234-1234-123456789abc")

cmd.Parameters.AddWithValue("_ID", guid.ToByteArray())
cmd.ExecuteNonQuery()
cmd.CommandText <- "
SELECT *
FROM `TestGuid`"

let reader = cmd.ExecuteReader()
while reader.Read() do
for i in 0 .. reader.FieldCount - 1 do
print_any (reader.GetValue i)
printfn ""

Console.ReadKey()

上面这段代码只是简单的创建一个名为 TestGuid 的表, 只有的一个名为 ID 的 BINARY(16) 列.

第一行数据直接插入 0x012345670123012301230123456789ab (从0开始, 按 8 4 4 4 12 分5部分)

第二行数据使用参数 Guid(“12345678-1234-1234-1234-123456789abc”) (从1开始, 规律同上)

之后又将数据取回, 输出到命令行, 猜猜看在 Windows 7 中结果是什么?

67452301-2301-2301-0123-0123456789ab

12345678-1234-1234-1234-123456789abc

注意看第一行, 前半部分, 也就是 8 4 4 的部分, 顺序乱了…

Guid 去掉中间的连字符 “-“, 共32个字符, 每两位合并后可以获得16个字节, 那么第一条数据的前8个字节顺序被搞反了, 而且是在各自的组内被搞反的.

让我们再回到数据库中看一眼

1
2
3
4
SELECT HEX(ID) FROM TestGuid
HEX(ID)
'012345670123012301230123456789AB'
'78563412341234121234123456789ABC'

天~ 这次是第二个反了.

冷静的想一想, 看来 MySql Server 是没有问题的, 问题出在 MySQL Connector Net 上面. 它在写入数据的时候搞错了二进制的高位与低位, 在读取的时候同样又搞错了一次, 错上加错, 结果似乎是对了, 只不过这个问题在其他系统中是不存在的, 换句话说, 这个问题带来的是跨系统之间数据不兼容.

没办法, 只能判断一下数据类型和操作系统版本, 暂时的规避一下吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 先给 List 扩展一个切片方法, 以便支持 lst.[1 .. 3] 这样的操作
type Microsoft.FSharp.Collections.List<'a> with
member t.GetSlice(n1, n2) =
let l = t.Length - 1
let n1 = match n1 with Some x -> max x 0 | _ -> 0
let n2 = match n2 with Some x -> min x l | _ -> l
[ for i in n1 .. n2 -> List.nth t i ]

/// 修正 MySql 不能识别 Windows7 Guid 的问题
let repairWin7Guid (x :obj) =
match x with
| :? Guid as g ->
if Environment.OSVersion.Version.Major = 6 && Environment.OSVersion.Version.Minor >= 1 then
let w = g.ToByteArray() |> Array.to_list
let r = w.[3] :: w.[2] :: w.[1] :: w.[0] :: w.[5] :: w.[4] :: w.[7] :: w.[6] :: w.[8 ..] |> Array.of_list
Guid(r) |> box
else
x
| _ -> x

这样做似乎不能彻底的解决问题, 幸好我的项目中 Guid 的出入口只有一个, 只要加在这两处就行了.

越来越对 MySql 不放心了 T_T