NHibernate 中始终加密映射

Always encrypted mapping in NHibernate

目前我正在使用 SQL Server 2016 来利用 Always Encrypted 功能。有几列我应该加密。我用 SQL 服务器加密了这些列。 NHibernate 可以很容易地从 SQL Server 读取数据,但是当它试图在数据库中插入数据时,它会抛出如下异常:

Operand type clash: nvarchar(4000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'EncTest') is incompatible with nvarchar(250) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'EncTest')

这是我对 NHibernate 中特定列的列映射:

<column name="DisableTxt" length="100" sql-type="NVarChar" />

我应该在 hbm 文件中定义什么映射?

我找到了解决这个问题的方法,首先我想描述一下为什么 NHibernate 不能与 Always Encrypted 中的 Encrypted Columns 一起工作:

当我们在连接字符串中启用 AlwaysEncrypted 时,ADO.NET 会在任何数据库操作之前自动执行 store procedure sp_describe_parameter_encryption 以确定哪些参数对应于数据库列使用 Always Encrypted 功能进行保护。此 sp 对字段的长度敏感,如果指定的参数长度不等于列长度,SQL 服务器将给我们以下错误:

Operand type clash: nvarchar(4000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'EncTest') is incompatible with nvarchar(250) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'EncTest')

发生这种情况是因为 NHibernate 总是为 NVarchar 列定义 4000 的参数大小(如果列长度不是 NVarchar(max))。所以想象一下,我们有一个长度为 30 的列,但是 NHibernate 为指定列定义了一个长度为 4000 的参数。为什么 NHibernate 这样做?

如果您查看 Nhibernate 来源的 SqlClientDriver.cs146,您将看到以下评论:

// Do not override the default length for string using data from SqlType, since LIKE expressions needs
// larger columns. https://nhibernate.jira.com/browse/NH-3036

那么我们如何解决这个问题呢?我们可以为 NHibernate 创建一个新的 Driver 来定义准确的参数长度。 (当然如果你不关心 %% 之类的表达式)。 (我在 NHiberate 3.x 上用过这个方法)

public class NewDriver : NHibernate.Driver.Sql2008ClientDriver
{
        public override IDbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes)
        {
            IDbCommand command = base.GenerateCommand(type, sqlString, parameterTypes);
            NewDirver.SetParameterSizes(command.Parameters, parameterTypes);
            return command;
        }

        public static void SetParamterSizes(IDataParameterCollection parameters, SqlType[] parameterTypes)
        {
            for(int index=0;index<parameters.Count;++index)
            {
                NewDriver.SetVariableLengthParameterSize((IDbDataParameter)parameters[index], parameterTypes[index]);
            }
        }

        public static void SetVariableLengthParmaeterSize(IDbDataParameter dbParam, SqlType sqlType)
        {
            SqlClientDriver.SetDefaultParameterSize(dbParam, sqlType);
            if(sqlType.LengthDefined && !IsText(dbParam, sqlType) && !IsBlob(dbParam, sqlType))
            {
                dbParam.Size = sqlType.Length;
            }

            if(sqlType.PrecesionsDefined)
            {
                dbParam.Precision = sqlType.Precision;
                dbParam.Scale = sqlType.Scale;
            }
        }


}