是否有一种更简洁的 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 必须为StartDiff 找到合适的位置。这类似于实施 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 更改的列?的主要内容,如果未能解决你的问题,请参考以下文章

有没有一种更简洁的方法来获得第一次出现的东西?

是否有一种简洁的方法可以为同一个扩展定义两个或多个 bash 别名?

记录--一种更现代的深浅拷贝方法

部分删除/销毁 $_SESSION 数据? PHP

SpriteKit:场景完成呈现时回调?

是否有一种简洁的功能方式来断言集合中的元素类型?