是否有一种更简洁的 Dapper 方法来仅更新随 Dapper 更改的列?
Posted
技术标签:
【中文标题】是否有一种更简洁的 Dapper 方法来仅更新随 Dapper 更改的列?【英文标题】:Is there a Cleaner Dapper way to Update only columns that changed with Dapper? 【发布时间】:2018-03-15 15:29:22 【问题描述】:我正在寻找一种方法来仅更新 Dapper 中的设置属性。即仅当它不为空时才更新实体的属性。
我正在用一种相当粗略的方法解决同样的问题,如下所示,但我相信应该有一种更简洁、更简洁的方法。
public void UpdateCustomer(Customer cust)
try
StringBuilder sb = new StringBuilder("UPDATE CUSTOMER_SETUP SET DATE_MODIFIED = @DATE_MODIFIED ");
if(cust.BUSINESSNAME != null) sb.Append(",BUSINESSNAME = @BUSINESSNAME ");
if (cust.BUSINESS_ADDRESS != null) sb.Append(",BUSINESS_ADDRESS = @BUSINESS_ADDRESS ");
if (cust.CONTACT_NAME != null) sb.Append(",CONTACT_NAME = @CONTACT_NAME ");
if (cust.CONTACT_TITLE != null) sb.Append(",CONTACT_TITLE = @CONTACT_TITLE ");
if (cust.CONTACT_PHONE1 != null) sb.Append(",CONTACT_PHONE1 = @CONTACT_PHONE1 ");
if (cust.CONTACT_PHONE2 != null) sb.Append(",CONTACT_PHONE2 = @CONTACT_PHONE2 ");
if (cust.CONTACT_EMAIL != null) sb.Append(",CONTACT_EMAIL = @CONTACT_EMAIL ");
if (cust.CONTACT_URL != null) sb.Append(",CONTACT_URL = @CONTACT_URL ");
if (cust.DATE_CREATED != null) sb.Append(",DATE_CREATED = @DATE_CREATED ");
if (cust.CUSTOMER_TYPE != null) sb.Append(",CUSTOMER_TYPE = @CUSTOMER_TYPE ");
if (cust.SUBSCRIPTION_TYPE != null) sb.Append(",SUBSCRIPTION_TYPE = @SUBSCRIPTION_TYPE ");
sb.Append("WHERE ID = @ID ");
sb.Append("; SELECT CAST(SCOPE_IDENTITY() as int ");
var sql = sb.ToString();
using (connection = new SqlConnection(connectString))
connection.Execute(sql, cust);
catch (Exception ex)
throw ex;
【问题讨论】:
dapper 的核心故意不包含完整的更改跟踪器;然而,“dapper.contrib”为此包含了一些钩子。我无法评论它们的行为 - 我不需要使用它们 这本身并不是完整的更改跟踪。更改跟踪需要比较实体的值以查看是否有任何更改。这以某种方式加载了旧状态为前提。我正在寻找的功能是一个更简单的版本...仅更新实体的非空属性。 【参考方案1】:您正在寻找的功能称为更改跟踪。此功能是更大的工作单元模式的一部分。
Dapper 不支持更改跟踪。
Dapper 的附加组件很少在不同级别上支持此功能。请参考 this this 博文查看对比图。如图表中所述,Dapper.Contrib 和 Dapper.Rainbow 以不同的方式支持它。
正如@MarcGravell 在评论中所说,POCO 属性的null
值很常见。它并不总是意味着“不更新该字段”。这也可能意味着“将该数据库字段设置为null
(或DBNull
)”。由于 null
的属性值没有一个保证的含义,因此大多数 ORM 实现它的方式与 Dapper 相同。
【讨论】:
这本身并不是完整的更改跟踪。更改跟踪需要比较实体的值以查看是否有任何更改。这以某种方式加载了旧状态为前提。我正在寻找的功能是一个更简单的版本...仅更新实体的非空属性。 @oliverdejohnson in general terms:值为 null 并不罕见或意外,如果数据层从未将事物设置为null
,人们会感到困惑 - 所以(即“它是否为空;如果是:不要设置”)不是它们通常的实现方式【参考方案2】:
我们将 Dapper.Rainbow 的 Snapshotter
包装起来,使其像更改跟踪器一样工作。您需要一个 db 对象的实例才能使其工作。
它非常适合我们提供一个字典,您可以很容易地使用它来生成您所追求的 SQL。
它可能看起来像这样:
public class Foopublic string Nameget;set;
var foo = new Foo();
var snapshotter = Snapshotter.Start(foo);
foo.Name = "A new name";
var dynparams = snapshotter.Diff(); //we basically wrap the snapshotter to give a dict here, but it's basically the same thing
foreach(var name in dynparams.ParameterNames)
sb.Append($",name = @dynparams[name] ");
【讨论】:
我将检查 Dapper.Rainbow。我希望我可以通过 Nuget 获得。将对此进行审查,看看它是否能以更简洁的方式解决问题。谢谢 你真的可以单独拉出snapshotter类,而不必下载所有的dapper.rainbow.github.com/StackExchange/Dapper/blob/master/Dapper.Rainbow/… 如果我错了,请纠正我,但这与 OP 已经在做的没什么不同。 OP 正在检查每个属性的null
。用你的方式,OP 得到被修改的属性列表(字典?)。唯一的好处是,OP 不需要检查所有属性。 缺点是,OP 必须为Start
和Diff
找到合适的位置。这类似于实施 UoW。如果是这种情况,为什么不绕过 Dapper 并使用支持 UoW/Change Tracking 的 ORM 开箱即用?
我认为最大的区别在于它可以为您处理所有更改,而无需手动检查是否为空。此外,可能有一些值类型根本没有改变,因此也不需要更新......这可以处理。更改属性字典还有其他好处……例如在日志中记录由特定客户更改的属性。所有这些在这里都是自动化的,因此比必须为每个类的每个属性都考虑这些要干净得多。【参考方案3】:
自己动手。
给模型添加私有集合,读取时复制数据。更新时比较新旧。根据需要构建 SQL 语句。
通过添加到数据库前处理,而不是让列“干火”给数据库供应商,是否会带来净性能提升?我认为大多数数据库供应商都知道列数据何时未更改,并且会在必要时排除这些列,例如特定列上的更新触发器。
【讨论】:
【参考方案4】:这是您想要的使用 Dapper 的更新查询示例。它可能会帮助你。
public async Task<bool> UpdateDataByIdAsync(Data data)
using mysqlConnection connection = new MySqlConnection("your connection string");
const string sqlQuery = @"Update Datas Set name = @name, description = @description, tags = @tags
where data_id = @data_id;";
var rowAffected = await connection.ExecuteAsync(sqlQuery, data);
return rowAffected > 0;
【讨论】:
【参考方案5】:ISNULL 函数可能会解决这个问题。它修复了我的。
const string sqlQuery = @"Update Datas Set name = ISNULL(@name,name), description = ISNULL(@description,description) , tags = ISNULL(@tags,tags)
where data_id = @data_id;";
【讨论】:
以上是关于是否有一种更简洁的 Dapper 方法来仅更新随 Dapper 更改的列?的主要内容,如果未能解决你的问题,请参考以下文章