DapperExtensions:添加 "insert ... update on duplicate key"
DapperExtensions: Add "insert ... update on duplicate key"
现在我正在使用 Dapper + Dapper.Extensions。是的,这很简单而且很棒。但是我遇到了一个问题:Dapper.Extensions 只有 Insert 命令和 not InsertUpdateOnDUplicateKey。我想添加这样的方法,但我没有找到好的方法:
- 我想让这个方法像插入一样通用
- 我无法获取特定类型的缓存属性列表,因为我不想直接使用反射来构建原始 sql
此处可能的方法是在 github 上分叉它,但我只想在我的项目中使用它。有人知道如何扩展它吗?我了解此功能 ("insert ... update on duplicate key") 仅在 MySQL 中受支持。但是我无法在 DapperExtensions 中找到扩展点以在外部添加此功能。
更新:这是我的叉子https://github.com/MaximTkachenko/Dapper-Extensions/commits/master
实际上我关闭了我的拉取请求并删除了我的分叉,因为:
- 我看到一些在 2014 年创建的公开拉取请求
- 我找到了一种在 Dapper.Extensions 中“注入”我的代码的方法。
我提醒我的问题:我想为 Dapper.Extensions 创建更多通用查询。这意味着我需要访问实体、SqlGenerator 等的映射缓存。所以这是我的方法。我想添加使 INSERT .. UPDATE ON DUPLICATE KEY for MySQL 的能力。我为 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;
}
}
的另一种扩展方法
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 class 创建新的 class 来使用我自己的 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 实例,因为来自基础 class 的 DapperImplementor 实例是私有的 :(。所以现在我可以使用我的 Db class 调用我自己的通用 sql 来自 Dapper.Extension 的查询和本机查询。可以找到使用数据库 class 而不是 IDbConnection 扩展的示例 here。
这段代码在 MySQL 相关的项目中对我帮助很大,我绝对欠你一个。
我在 MySQL 和 MS SQL 上进行了大量与数据库相关的开发。我还尝试在我的项目之间共享尽可能多的代码。
MS SQL 没有 "ON DUPLICATE KEY UPDATE" 的直接等效项,因此我以前在使用 MS SQL 时无法使用此扩展。
在将 Web 应用程序(严重依赖此 Dapper.Extensions 调整)从 MySQL 迁移到 MS SQL 时,我最终决定对此做点什么。
此代码使用 "IF EXISTS => UPDATE ELSE INSERT" 方法,基本上与 MySQL 上的 "ON DUPLICATE KEY UPDATE" 相同。
请注意:该代码段假定您正在处理此方法之外的事务。或者,您可以将 "BEGIN TRAN" 附加到生成的 sql 字符串的开头,将 "COMMIT" 附加到结尾。
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();
}
}
现在我正在使用 Dapper + Dapper.Extensions。是的,这很简单而且很棒。但是我遇到了一个问题:Dapper.Extensions 只有 Insert 命令和 not InsertUpdateOnDUplicateKey。我想添加这样的方法,但我没有找到好的方法:
- 我想让这个方法像插入一样通用
- 我无法获取特定类型的缓存属性列表,因为我不想直接使用反射来构建原始 sql
此处可能的方法是在 github 上分叉它,但我只想在我的项目中使用它。有人知道如何扩展它吗?我了解此功能 ("insert ... update on duplicate key") 仅在 MySQL 中受支持。但是我无法在 DapperExtensions 中找到扩展点以在外部添加此功能。
更新:这是我的叉子https://github.com/MaximTkachenko/Dapper-Extensions/commits/master
实际上我关闭了我的拉取请求并删除了我的分叉,因为:
- 我看到一些在 2014 年创建的公开拉取请求
- 我找到了一种在 Dapper.Extensions 中“注入”我的代码的方法。
我提醒我的问题:我想为 Dapper.Extensions 创建更多通用查询。这意味着我需要访问实体、SqlGenerator 等的映射缓存。所以这是我的方法。我想添加使 INSERT .. UPDATE ON DUPLICATE KEY for MySQL 的能力。我为 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;
}
}
的另一种扩展方法
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 class 创建新的 class 来使用我自己的 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 实例,因为来自基础 class 的 DapperImplementor 实例是私有的 :(。所以现在我可以使用我的 Db class 调用我自己的通用 sql 来自 Dapper.Extension 的查询和本机查询。可以找到使用数据库 class 而不是 IDbConnection 扩展的示例 here。
这段代码在 MySQL 相关的项目中对我帮助很大,我绝对欠你一个。
我在 MySQL 和 MS SQL 上进行了大量与数据库相关的开发。我还尝试在我的项目之间共享尽可能多的代码。
MS SQL 没有 "ON DUPLICATE KEY UPDATE" 的直接等效项,因此我以前在使用 MS SQL 时无法使用此扩展。
在将 Web 应用程序(严重依赖此 Dapper.Extensions 调整)从 MySQL 迁移到 MS SQL 时,我最终决定对此做点什么。
此代码使用 "IF EXISTS => UPDATE ELSE INSERT" 方法,基本上与 MySQL 上的 "ON DUPLICATE KEY UPDATE" 相同。
请注意:该代码段假定您正在处理此方法之外的事务。或者,您可以将 "BEGIN TRAN" 附加到生成的 sql 字符串的开头,将 "COMMIT" 附加到结尾。
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();
}
}