艾伟_转载:使用LINQ to SQL更新数据库(中):几种解决方案

简介: 在前一篇文章中,我提出了在使用LINQ to SQL进行更新操作时可能会遇到的几种问题。其实这并不是我一个人遇到的问题,当我在互联网上寻找答案时,我发现很多人都对这个话题发表过类似文章。但另我无法满足的是,他们尽管提出了问题,却没有进行详细的剖析,只给出了解决方案(如添加RowVersion列、去除关联等),但却没有说明为什么必须这么做。

前一篇文章中,我提出了在使用LINQ to SQL进行更新操作时可能会遇到的几种问题。其实这并不是我一个人遇到的问题,当我在互联网上寻找答案时,我发现很多人都对这个话题发表过类似文章。但另我无法满足的是,他们尽管提出了问题,却没有进行详细的剖析,只给出了解决方案(如添加RowVersion列、去除关联等),但却没有说明为什么必须这么做。这也是我写上篇的初衷,希望通过对LINQ to SQL源代码的分析,来一步一步找出解决问题的办法。本文将对这些方法一一进行讨论。

方案一:重新赋值

TerryLeeAnytaoDing Xue等人的开源框架Ezsocio中,有些地方采取了重新赋值的方法。在Update方法内部,根据主键获取数据库中的实体,然后与参数中的实体对其属性一一赋值。

public void UpdateProfile(Profile p)
{
    using (RepositoryContext db = new RepositoryContext())
    {
        var profile = db.GetTable<Profile>().First<Profile>(u => u.ID == p.ID);
        profile.Birthday = p.Birthday;
        profile.Gender = p.Gender;
        profile.Hometown = p.Hometown;
        profile.MSN = p.MSN;
        profile.NickName = p.NickName;
        profile.PhoneNumber = p.PhoneNumber;
        profile.QQ = p.QQ;
        profile.State = p.State;
        profile.TrueName = p.TrueName;
        profile.StateRefreshTime = p.StateRefreshTime;
        profile.Avatar = p.Avatar;
        profile.Website = p.Website;
        db.SubmitChanges();
    }
}

杨过兄也同样给出了该方案的反射方法,实现属性值的自动拷贝。

但我个人认为这是一种避实就虚的方案,没有使用LINQ to SQL提供的用于更新操作的API,而采取了一种迂回的策略。这其实是一种妥协,难道因为Attach方法“不好用”,我们就不用了吗?呵呵。

方案二:禁用对象跟踪

对此,lea提出可以通过将DataContext的ObjectTrackingEnabled属性设置为false,来达到正确更新的目的。

public Product GetProduct(int id)
{
    NorthwindDataContext db = new NorthwindDataContext();
    db.ObjectTrackingEnabled = false;
    return db.Products.SingleOrDefault(p => p.ProductID == id);
}

其他的代码没有任何变化。

为什么禁用对象跟踪之后,就能正常更新了呢?我们还是从源代码中来寻找答案吧。

public bool ObjectTrackingEnabled
{
    get
    {
        this.CheckDispose();
        return this.objectTrackingEnabled;
    }
    set
    {
        this.CheckDispose();
        if (this.Services.HasCachedObjects)
        {
            throw System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
        }
        this.objectTrackingEnabled = value;
        if (!this.objectTrackingEnabled)
        {
            this.deferredLoadingEnabled = false;
        }
        this.services.ResetServices();
    }
}

原来设置ObjectTrackingEnabled为false时,会同时将DeferredLoadingEnabled设置为false。这样,在执行查询时,将不会为实体加载任何需延迟查询的数据,因此Attach时也不会抛出异常(见上篇的分析)。

在MSDN中我们还得到下面这条有用的信息:将ObjectTrackingEnable属性设置为false,可以提高检索时的性能,因为这样可以减少要跟踪的项目。这真是一个很有诱惑的特性。

但禁用对象跟踪时,要特别注意两点:(1)必须在执行查询前禁用。(2)禁用之后不能再调用Attach和SubmitChanges方法。否则都将引发异常。

方案三:移除关联

前一篇文章中已经介绍一个蹩脚的方法,即在GetProduct方法中手动设置与Product关联的Category为null。我们可以把这部分代码提取出来,放入一个Detach方法中。因为这个Detach是实体的方法,可以使用分部类:

public partial class Product
{
    public void Detach()
    {
        this._Category = default(EntityRef<Category>);
    }
}
public partial class Category
{
    public void Detach()
    {
        foreach (var product in this.Products)
        {
            product.Detach();
        }
    }
}

但是这种对每个实体都定义Detach的方法过于繁琐。随着实体的增多,关系越来越复杂,很容易出现漏掉的属性。张逸提出了一个非常优雅的方法,利用反射对该逻辑进行抽象:

private void Detach(TEntity entity)
{
    foreach (FieldInfo fi in entity.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.
Instance)) {
if (fi.FieldType.ToString().Contains("EntityRef")) { var value = fi.GetValue(entity); if (value != null) { fi.SetValue(entity, null); } } if (fi.FieldType.ToString().Contains("EntitySet")) { var value = fi.GetValue(entity); if (value != null) { MethodInfo mi = value.GetType().GetMethod("Clear"); if (mi != null) { mi.Invoke(value, null); } fi.SetValue(entity, value); } } } }

也有人认为在Detach时应该把PropertyChanging和PropertyChanged事件设置为null,但总体的思路是一样的。

方案四:使用委托

这是ZC29同学在我上一篇文章的评论里给出的方法,我个人认为非常值得借鉴。

public void UpdateProductWithDelegate(Expression<Func<Product, bool>> predicate, Action
<
Product> action) { NorthwindDataContext db = new NorthwindDataContext(); var product = db.Products.SingleOrDefault(predicate); action(product); db.SubmitChanges(); } // Client code ProductRepository repository = new ProductRepository(); repository.UpdateProductWithDelegate(p => p.ProductID == 1, p => { p.ProductName = "Changed"; });

使用Lambda表达式将GetProduct的逻辑植入UpdateProduct中,并且使用委托将更新逻辑也延缓执行,这样巧妙地将查找和更新放进了一个DataContext里,从而绕开了Attach。但是这种方法API有些过于复杂,对客户端编程人员的水平要求过高。而且在Update里还要执行一遍Get的逻辑,尽管性能上的损失微乎其微,但看上去总多多少少给人一种不够DRY的感觉。

方案五:使用UPDATE语句

Ezsocio的源代码中,我发现了RepositoryBase.UpdateEntity方法。在方法内部进行SQL语句的拼接,并且将只更新发生更改的列。由于此处已经不再使用ITable,并且需要完整的框架支持,因此不再进行过多的评述。详情请参考Ezsocio的源代码。

总结

本文列举了近几天我在互联网上找到的几种解决方案,它们各有利弊,孰优孰劣,见仁见智。在下篇中,我将对这几种方法进行性能上的比较,从而找出最优方案。

相关文章

目录
相关文章
|
1天前
|
SQL 数据库
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
SQL Server附加数据库出现错误823,附加数据库失败。数据库没有备份,无法通过备份恢复数据库。 SQL Server数据库出现823错误的可能原因有:数据库物理页面损坏、数据库物理页面校验值损坏导致无法识别该页面、断电或者文件系统问题导致页面丢失。
26 12
数据库数据恢复—SQL Server数据库报错“错误823”的数据恢复案例
|
10天前
|
SQL 存储 数据管理
SQL Server数据库
SQL Server数据库
22 11
|
5天前
|
SQL 监控 关系型数据库
MySQL数据库中如何检查一条SQL语句是否被回滚
检查MySQL中的SQL语句是否被回滚需要综合使用日志分析、事务状态监控和事务控制语句。理解和应用这些工具和命令,可以有效地管理和验证数据库事务的执行情况,确保数据的一致性和系统的稳定性。此外,熟悉事务的ACID属性和正确设置事务隔离级别对于预防数据问题和解决事务冲突同样重要。
19 2
|
17天前
|
SQL 安全 数据库
基于SQL Server事务日志的数据库恢复技术及实战代码详解
基于事务日志的数据库恢复技术是SQL Server中一个非常强大的功能,它能够帮助数据库管理员在数据丢失或损坏的情况下,有效地恢复数据。通过定期备份数据库和事务日志,并在需要时按照正确的步骤恢复,可以最大限度地减少数据丢失的风险。需要注意的是,恢复数据是一个需要谨慎操作的过程,建议在执行恢复操作之前,详细了解相关的操作步骤和注意事项,以确保数据的安全和完整。
37 0
|
21天前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
36 0
|
21天前
|
SQL 数据处理 数据库
|
21天前
|
SQL 存储 调度
|
21天前
|
SQL 安全 数据库
|
21天前
|
Java 数据库连接 数据库
告别繁琐 SQL!Hibernate 入门指南带你轻松玩转 ORM,解锁高效数据库操作新姿势
【8月更文挑战第31天】Hibernate 是一款流行的 Java 持久层框架,简化了对象关系映射(ORM)过程,使开发者能以面向对象的方式进行数据持久化操作而无需直接编写 SQL 语句。本文提供 Hibernate 入门指南,介绍核心概念及示例代码,涵盖依赖引入、配置文件设置、实体类定义、工具类构建及基本 CRUD 操作。通过学习,你将掌握使用 Hibernate 简化数据持久化的技巧,为实际项目应用打下基础。
40 0
|
21天前
|
SQL 存储 监控