DapperExtensions:添加“在重复键上插入...更新”

时间:2016-07-03 16:06:27

标签: c# orm dapper dapper-extensions

现在我正在使用Dapper + Dapper.Extensions。是的,它很简单,很棒。但是我遇到了一个问题:Dapper.Extensions只有插入命令而不是 InsertUpdateOnDUplicateKey 。我想添加这样的方法,但我没有看到很好的方法:

  1. 我想让这个方法像插入
  2. 一样通用
  3. 我无法获取特定类型的属性缓存列表,因为我不想直接使用反射来构建原始sql
  4. 这里可能的方法在github上分叉但我只想在我的项目中创建它。有人知道如何扩展吗?我理解这个功能(“插入...复制重复键”)仅在MySQL中受支持。但我无法在DapperExtensions中找到扩展点以在外部添加此功能 更新:这是我的前叉https://github.com/MaximTkachenko/Dapper-Extensions/commits/master

2 个答案:

答案 0 :(得分:0)

这段代码在MySQL相关项目中帮助了我,我绝对欠你一个。

我在MySQL和MS SQL上做了很多与数据库相关的开发。我还尝试在我的项目之间共享尽可能多的代码。

MS SQL与" ON DUPLICATE KEY UPDATE"没有直接的等价物,因此在使用MS SQL时我以前无法使用此扩展。

在将一个Web应用程序(严重依赖于此Dapper.Extensions调整)从MySQL迁移到MS SQL时,我终于决定对此做些什么。

此代码使用" IF EXISTS =>更新ELSE INSERT"方法基本上和#34;在重复键更新"在MySQL上。

请注意:该代码段假定您正在处理此方法之外的交易。或者你可以追加" BEGIN TRAN"到了开始和" COMMIT"到生成的sql字符串的末尾。

public static class SqlGeneratorExt
{
    public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap, bool hasIdentityKeyWithValue = false)
    {
        var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || (p.KeyType == KeyType.Identity && !hasIdentityKeyWithValue))).ToList();
        var keys = columns.Where(c => c.KeyType != KeyType.NotAKey).Select(p => $"{generator.GetColumnName(classMap, p, false)}=@{p.Name}");
        var nonkeycolumns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly) && p.KeyType == KeyType.NotAKey).ToList();
        if (!columns.Any())
        {
            throw new ArgumentException("No columns were mapped.");
        }
        var tablename = generator.GetTableName(classMap);
        var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
        var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
        var valuesSetters = nonkeycolumns.Select(p => $"{generator.GetColumnName(classMap, p, false)}=@{p.Name}").ToList();
        var where = keys.AppendStrings(seperator: " and ");
        var sqlbuilder = new StringBuilder();
        sqlbuilder.AppendLine($"IF EXISTS (select * from {tablename} WITH (UPDLOCK, HOLDLOCK) WHERE ({where})) ");
        sqlbuilder.AppendLine(valuesSetters.Any() ? $"UPDATE {tablename} SET {valuesSetters.AppendStrings()} WHERE ({where}) " : "SELECT 0 ");
        sqlbuilder.AppendLine($"ELSE INSERT INTO {tablename} ({columnNames.AppendStrings()}) VALUES ({parameters.AppendStrings()}) ");
        return sqlbuilder.ToString();
    }
}

答案 1 :(得分:-1)

实际上我关闭了拉取请求并删除了我的分叉,因为:

  1. 我在2014年看到了一些公开拉请求
  2. 我找到了一种在Dapper.Extensions中“注入”我的代码的方法。
  3. 我提醒我的问题:我想为Dapper.Extensions创建更多通用查询。这意味着我需要访问实体,SqlGenerator等的映射缓存。所以这是我的方式。我想为MySQL增加使用 INSERT .. UPDATE ON DUPLICATE KEY 的能力。我为ISqlGenerator

    创建了扩展方法
       public static class SqlGeneratorExt
        {
            public static string InsertUpdateOnDuplicateKey(this ISqlGenerator generator, IClassMapper classMap)
            {
                var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity));
                if (!columns.Any())
                {
                    throw new ArgumentException("No columns were mapped.");
                }
    
                var columnNames = columns.Select(p => generator.GetColumnName(classMap, p, false));
                var parameters = columns.Select(p => generator.Configuration.Dialect.ParameterPrefix + p.Name);
                var valuesSetters = columns.Select(p => string.Format("{0}=VALUES({1})", generator.GetColumnName(classMap, p, false), p.Name));
    
                string sql = string.Format("INSERT INTO {0} ({1}) VALUES ({2}) ON DUPLICATE KEY UPDATE {3}",
                                           generator.GetTableName(classMap),
                                           columnNames.AppendStrings(),
                                           parameters.AppendStrings(),
                                           valuesSetters.AppendStrings());
    
                return sql;
            }
        }
    

    IDapperImplementor

    的另一种扩展方法
    public static class DapperImplementorExt
    {
        public static void InsertUpdateOnDuplicateKey<T>(this IDapperImplementor implementor, IDbConnection connection, IEnumerable<T> entities, int? commandTimeout = null) where T : class
        {
            IClassMapper classMap = implementor.SqlGenerator.Configuration.GetMap<T>();
            var properties = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
            string emptyGuidString = Guid.Empty.ToString();
    
            foreach (var e in entities)
            {
                foreach (var column in properties)
                {
                    if (column.KeyType == KeyType.Guid)
                    {
                        object value = column.PropertyInfo.GetValue(e, null);
                        string stringValue = value.ToString();
                        if (!string.IsNullOrEmpty(stringValue) && stringValue != emptyGuidString)
                        {
                            continue;
                        }
    
                        Guid comb = implementor.SqlGenerator.Configuration.GetNextGuid();
                        column.PropertyInfo.SetValue(e, comb, null);
                    }
                }
            }
    
            string sql = implementor.SqlGenerator.InsertUpdateOnDuplicateKey(classMap);
    
            connection.Execute(sql, entities, null, commandTimeout, CommandType.Text);
        }
    }
    

    现在我可以创建从Database类派生的新类来使用我自己的sql

    public class Db : Database
    {
        private readonly IDapperImplementor _dapperIml;
    
        public Db(IDbConnection connection, ISqlGenerator sqlGenerator) : base(connection, sqlGenerator)
        {
            _dapperIml = new DapperImplementor(sqlGenerator);
        }
    
        public void InsertUpdateOnDuplicateKey<T>(IEnumerable<T> entities, int? commandTimeout) where T : class
        {
            _dapperIml.InsertUpdateOnDuplicateKey(Connection, entities, commandTimeout);
        }
    }
    

    是的,需要创建另一个DapperImplementor实例,因为基类中的DapperImplementor实例是私有的:(。所以现在我可以使用我的Db类从Dapper.Extension调用我自己的通用sql查询和本机查询。使用示例数据库可以找到class而不是IDbConnection扩展here