NHibernate:如何将 C# [Guid] 插入 MySQL [BINARY(16) DEFAULT (uuid_to_bin(uuid(),1))] 列?

NHibernate: How to insert C# [Guid] into MySQL [BINARY(16) DEFAULT (uuid_to_bin(uuid(),1))] column?

环境: MySQL 服务器 8.0,.NET Core 3.1,MySql.Data 8.0.28,NHibernate 5.3.11

我关注table:

CREATE TABLE `Master` (
  `Row_Id` char(36) NOT NULL DEFAULT (uuid()),
  `Path` varchar(1000) NOT NULL,
  PRIMARY KEY (`Row_Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

以下是实体定义和映射:

public class MasterEntity
{
    public virtual Guid RowId { get; set; }
    public virtual string Path { get; set; }
}

internal sealed class MasterMap : ClassMapping<MasterEntity>
{
    public MasterMap()
    {
        Table("Master");

        Id
        (
            x => x.RowId,
            map =>
            {
                map.Column("Row_Id");
                map.Generator(Generators.GuidComb);
            }
        );

        Property(x => x.Path, map => { map.Column("Path"); map.NotNullable(true); map.Type(TypeFactory.GetAnsiStringType(1000)); });
    }
}

以下是我如何 INSERT 使用 NHibernate 这个实体:

using(ISession session = SessionFactory.OpenSession())
{
    MasterEntity entity = new MasterEntity();
    entity.Path = "c:\whatever";
    session.Save(entity);
    session.Flush();
}

这将正确插入记录。到这里,一切都很好。

现在,我将 Row_Id 列的定义更改如下:

`Row_Id` binary(16) NOT NULL DEFAULT (uuid_to_bin(uuid(),1)),

我没有更改我的 C# 代码中的任何内容。现在,session.Flush(); 调用抛出以下异常:

NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available]
 ---> MySql.Data.MySqlClient.MySqlException (0x80004005): Data too long for column 'Row_Id' at row 1

错误看起来很明显。 C#中的Guid为32,列长为16。
我需要在映射或实体定义(或代码的其他部分)中进行哪些更改才能将 C# Guid 插入 BINARY(16) DEFAULT (uuid_to_bin(uuid(),1)) 列?

默认情况下,MySql.Data 会将 Guid 存储为 CHAR(36)。您可以通过在连接字符串中指定 Old Guids = True; 来使用 BINARY(16)

来自Connector/NET 8.0 Connection Options Reference

The back-end representation of a GUID type was changed from BINARY(16) to CHAR(36). This was done to allow developers to use the server function UUID() to populate a GUID table - UUID() generates a 36-character string. Developers of older applications can add 'Old Guids=true' to the connection string to use a GUID of data type BINARY(16).

中建议的方式有效;但它有一个问题。

题中代码使用uuid_to_bin(uuid(),1);第二个 swap 参数设置为 1。有了这个,INSERT 工作得很好;但是当你 SELECT 该行时,你会得到完全不同的 UUID。这是因为,数据库驱动程序不知道 UUID 是否被交换。

更好的解决方案是使用 MySqlConnector 而不是 Oracle 的 Connector/NET (MySql.Data.dll).

对于ADO.NET:

按照说明进行配置 here

For .NET Core 2.1 or later, call DbProviderFactories.RegisterFactory("MySqlConnector", MySqlConnectorFactory.Instance) during application startup. This will register MySqlConnector’s DbProviderFactory implementation in the central DbProviderFactories registry.

我的观察是,不需要调用 DbProviderFactories.RegisterFactory。它只是通过添加 MySqlConnector.dll 的引用并删除 MySql.Data.dll.

的引用来工作

对于 MySqlConnectorOldGuids=True; 设置可用但已过时;避免它。
GuidFormat=TimeSwapBinary16; 用于 uuid_to_bin(uuid(),1)(交换参数设置为 1)。
提到了其他可能的值 here:

Determines which column type (if any) should be read as a System.Guid. The options include:

Char36:
All CHAR(36) columns are read/written as a Guid using lowercase hex with hyphens, which matches UUID().

Char32:
All CHAR(32) columns are read/written as a Guid using lowercase hex without hyphens.

Binary16:
All BINARY(16) columns are read/written as a Guid using big-endian byte order, which matches UUID_TO_BIN(x).

TimeSwapBinary16:
All BINARY(16) columns are read/written as a Guid using big-endian byte order with time parts swapped, which matches UUID_TO_BIN(x,1).

LittleEndianBinary16:
All BINARY(16) columns are read/written as a Guid using little-endian byte order, i.e. the byte order used by Guid.ToByteArray() and the Guid(byte[]) constructor.

None:
No column types are automatically read as a Guid.

Default:
Same as Char36 if OldGuids=False; same as LittleEndianBinary16 if OldGuids=True.

对于 NHibernate:

  • 安装NHibernate.MySqlConnector from nuget package.
  • 在会话工厂配置中添加 configuration.DataBaseIntegration(c => c.MySqlConnectorDriver());
  • 如上所述在连接字符串中设置 GuidFormat

对于其他 ORM:

请参考 this 与其他 ORM 的用法。