始终加密列

时间:2018-02-26 16:52:55

标签: sql sql-server entity-framework always-encrypted

你好我有SQL服务器设置始终加密的功能,我也设置EF用于始终加密的列,但是当我尝试添加/更新时,对于Db操作我使用DbContext,在我的Db中输入我得到关注错误:

Operand type clash: decimal(1,0) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '****', column_encryption_key_database_name = '****') is incompatible with decimal(6,2) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '*****', column_encryption_key_database_name = '****')

我使用的模型

public class Model
{
    /// <summary>
    /// Payment method name
    /// </summary>
    [Column(TypeName = "nvarchar(MAX)")]
    public string Name { get; set; }

    /// <summary>
    /// Payment method description
    /// </summary>
    [Column(TypeName = "nvarchar(MAX)")]
    public string Description { get; set; }

    /// <summary>
    /// Fee charges for using payment method
    /// </summary>
    [Column(TypeName = "decimal(6,2)")]
    public decimal Fee { get; set; }
}

我还尝试在OnModelCreating方法中指定十进制格式

 builder.Entity<Model>().Property(x => x.Fee).HasColumnType("decimal(6,2)");

我错过了什么? 感谢您的任何建议

1 个答案:

答案 0 :(得分:0)

我和我的同事已经使用DiagnosticSource找到了解决该问题的方法。

您必须知道:

  • 实体框架核心将自身挂接到DiagnosticSource中。
  • DiagnosticSource使用观察者模式来通知其观察者。

想法是填充命令对象(由EFCore创建)的'Precision'和'Scale'字段,这样对Sql的调用将包含正确执行查询所需的所有信息。

首先,创建侦听器:

namespace YOUR_NAMESPACE_HERE
{
    public class EfGlobalListener : IObserver<DiagnosticListener>
    {
        private readonly CommandInterceptor _interceptor = new CommandInterceptor();

        public void OnCompleted()
        {
        }
        public void OnError(Exception error)
        {
        }
        public void OnNext(DiagnosticListener value)
        {
            if (value.Name == DbLoggerCategory.Name)
            {
                value.Subscribe(_interceptor);
            }
        }
    }
}

CommandInterceptor在哪里:

namespace YOUR_NAMESPACE_HERE
{
    public class CommandInterceptor : IObserver<KeyValuePair<string, object>>
    {
        // This snippet of code is only as example, you could maybe use Reflection to retrieve Field mapping instead of using Dictionary
        private Dictionary<string, (byte Precision, byte Scale)> _tableMapping = new Dictionary<string, (byte Precision, byte Scale)>
        {
            { "Table1.DecimalField1", (18, 2) },
            { "Table2.DecimalField1", (12, 6) },
            { "Table2.DecimalField2", (10, 4) },
        };

        public void OnCompleted()
        {
        }
        public void OnError(Exception error)
        {
        }
        public void OnNext(KeyValuePair<string, object> value)
        {
            if (value.Key == RelationalEventId.CommandExecuting.Name)
            {
                // After that EF Core generates the command to send to the DB, this method will be called

                // Cast command object
                var command = ((CommandEventData)value.Value).Command;

                // command.CommandText -> contains SQL command string
                // command.Parameters -> contains all params used in sql command

                // ONLY FOR EXAMPLE PURPOSES
                // This code may contain errors.
                // It was written only as an example.

                string table = null;
                string[] columns = null;
                string[] parameters = null;

                var regex = new Regex(@"^INSERT INTO \[(.+)\] \((.*)\)|^VALUES \((.*)\)|UPDATE \[(.*)\] SET (.*)$", RegexOptions.Multiline);
                var matches = regex.Matches(command.CommandText);

                foreach (Match match in matches)
                {
                    if(match.Groups[1].Success)
                    {
                        // INSERT - TABLE NAME
                        table = match.Groups[1].Value;
                    }
                    if (match.Groups[2].Success)
                    {
                        // INSERT - COLS NAMES
                        columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
                    }
                    if (match.Groups[3].Success)
                    {
                        // INSERT - PARAMS VALUES
                        parameters = match.Groups[3].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
                    }
                    if (match.Groups[4].Success)
                    {
                        // UPDATE - TABLE NAME
                        table = match.Groups[4].Value;
                    }
                    if (match.Groups[5].Success)
                    {
                        // UPDATE - COLS/PARAMS NAMES/VALUES
                        var colParams = match.Groups[5].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
                        columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
                        parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
                    }
                }

                // After taking all the necessary information from the sql command
                // we can add Precision and Scale to all decimal parameters
                foreach (var item in command.Parameters.OfType<SqlParameter>().Where(p => p.DbType == DbType.Decimal))
                {
                    var index = Array.IndexOf<string>(parameters, item.ParameterName);
                    var columnName = columns.ElementAt(index);

                    var key = $"{table}.{columnName}";

                    // Add Precision and Scale, that fix our problems w/ always encrypted columns
                    item.Precision = _tableMapping[key].Precision;
                    item.Scale = _tableMapping[key].Scale;
                }
            }
        }
    }
}

最后在Startup.cs中添加以下代码行以注册侦听器:

DiagnosticListener.AllListeners.Subscribe(new EfGlobalListener());