【问题标题】:Linq caching data values - major concurrency problem?Linq 缓存数据值 - 主要的并发问题?
【发布时间】:2009-09-30 15:51:25
【问题描述】:

这是我做的一个小实验:

MyClass obj = dataContext.GetTable<MyClass>().Where(x => x.ID = 1).Single();
Console.WriteLine(obj.MyProperty); // output = "initial"
Console.WriteLine("Waiting..."); // put a breakpoint after this line
obj = null;
obj = dataContext.GetTable<MyClass>().Where(x => x.ID = 1).Single(); // same as before, but reloaded
Console.WriteLine(obj.MyProperty); // output still = "initial"
obj.MyOtherProperty = "foo";
dataContext.SubmitChanges(); // throws concurrency exception

当我在第 3 行之后命中断点时,我转到 SQL 查询窗口并手动将值更改为“更新”。然后我继续跑步。 Linq 不会重新加载我的对象,而是重新使用它之前在内存中的对象!这是数据并发的巨大问题!

如何禁用 Linq 显然保存在内存中的对象的隐藏缓存?

编辑 - 仔细想想,简直无法想象微软会在 Linq 框架中留下如此巨大的鸿沟。上面的代码是我实际正在做的事情的简化版本,我可能遗漏了一些细微之处。简而言之,如果您自己进行实验以验证我的上述发现是否正确,我将不胜感激。或者,必须有某种“秘密开关”使 Linq 对并发数据更新具有鲁棒性。但是什么?

【问题讨论】:

    标签: c# linq linq-to-sql concurrency


    【解决方案1】:

    这不是我以前遇到过的问题(因为我不倾向于长时间保持 DataContexts 处于打开状态),但看起来其他人有:

    http://www.rocksthoughts.com/blog/archive/2008/01/14/linq-to-sql-caching-gotcha.aspx

    【讨论】:

    • 更糟糕的是,如果你按照他的建议设置 ObjectTrackingEnabled = false,你只能从数据库中读取;你不能做 SubmitChanges!这真的让生活变得困难!
    • 这里是 C# gotcha 知识库的链接:stackoverflow.com/questions/241134/…
    【解决方案2】:

    LinqToSql 有各种各样的工具来处理并发问题。

    然而,第一步是承认有一个并发问题需要解决!

    首先,DataContext 的预期对象生命周期应该与 UnitOfWork 匹配。如果您长时间坚持使用一个课程,您将不得不更加努力地工作,因为该课程并非旨在以这种方式使用。

    其次,DataContext 跟踪每个对象的两个副本。一种是原始状态,一种是更改/可变状态。如果您要求 Id = 1 的 MyClass,它将返回上次它给您的 相同实例,这是更改/可更改的版本......而不是原始版本。它必须这样做以防止内存实例中的并发问题... LinqToSql 不允许一个 DataContext 知道 MyClass(Id = 1) 的两个可变版本。

    第三,DataContext 不知道你的内存变化是发生在数据库变化之前还是之后,因此在没有一些指导的情况下无法判断并发冲突。它所看到的只是:

    • 我从数据库中读取了 MyClass(Id = 1)。
    • 程序员修改了 MyClass(Id = 1)。
    • 我将 MyClass(Id = 1) 发送回数据库(查看此 sql 以查看 where 子句中的乐观并发)
      • 如果数据库的版本与原始版本匹配,则更新将成功(乐观并发)。
      • 如果数据库的版本与原始版本不匹配,更新将失败并出现并发异常。

    好的,既然已经说明了问题,这里有几种处理方法。

    您可以丢弃 DataContext 并重新开始。这对某些人来说有点繁琐,但至少它很容易实现。

    您可以通过调用DataContext.Refresh(RefreshMode, target) (reference docs with many good concurrency links in the "Remarks" section) 来请求使用数据库值刷新原始实例或已更改/可更改的实例。这将带来客户端的更改,并允许您的代码计算出最终结果应该是什么。

    您可以在 dbml (ColumnAttribute.UpdateCheck) 中关闭并发检查。这会禁用乐观并发,并且您的代码将覆盖其他任何人的更改。也很重,也很容易实现。

    【讨论】:

      【解决方案3】:

      将 DataContext 的 ObjectTrackingEnabled 属性设置为 false。

      当 ObjectTrackingEnabled 设置为 true 时,DataContext 的行为类似于Unit of Work。它将保持任何对象加载在内存中,以便它可以跟踪对其的更改。 DataContext 必须记住您最初加载的对象,以了解是否进行了任何更改。

      如果您在只读场景中工作,则应关闭对象跟踪。这可能是一个不错的性能改进。

      如果您不是在只读方案中工作,那么我不确定您为什么希望它以这种方式工作。如果您进行了编辑,那么为什么要它从数据库中提取修改状态?

      【讨论】:

      • 是的,但我不在只读环境中工作。还有其他想法吗?
      • 这是设计使然。它旨在让您使用乐观并发拉入状态、修改和回写。您可以在 DataContext 上调用 Refresh 但这可能会终止您所做的任何更改。它并不意味着提供实时同步。实时同步和编辑不能很好地结合在一起。
      【解决方案4】:

      LINQ to SQL 使用标识映射设计模式,这意味着它总是会返回给定主键的对象的相同实例(除非您关闭对象跟踪)。

      解决方案是,如果您不希望它干扰第一个实例,则只需使用第二个数据上下文,或者如果您这样做,则刷新第一个实例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-11
        • 2013-01-11
        相关资源
        最近更新 更多