Linq缓存数据值 – 主要的并发问题?

这是我做的一个小实验:

MyClass obj = dataContext.GetTable().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().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框架中留下如此巨大的鸿沟,这简直是不可想象的。 上面的代码是我正在做的事情的一个愚蠢的版本,我可能错过了一些细微之处。 简而言之,如果您进行自己的实验来validation我的上述发现是否正确,我将不胜感激。 或者,必须有某种“秘密开关”使Linq对并发数据更新具有强大的function。 但是什么?

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

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

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

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

首先,DataContext的预期对象生命周期应该与UnitOfWork匹配。 如果你长时间坚持一个,那么你将不得不更加努力地工作,因为这个课程的设计不是那么用的。

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

第三,DataContext不知道您的内存中更改是在数据库更改之前还是之后,因此如果没有一些指导,就无法裁断并发冲突。 它看到的只是:

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

好的,现在问题已经陈述,这里有几种方法可以解决它。

你可以扔掉DataContext并重新开始。 这对某些人来说有点沉重,但至少它很容易实现。

您可以通过调用DataContext.Refresh(RefreshMode, target) ( 在“备注”部分中引用具有许多良好并发链接的文档 DataContext.Refresh(RefreshMode, target)来请求使用数据库值刷新原始实例或已更改/可更改实例。 这将带来客户端的更改,并允许您的代码确定最终结果应该是什么。

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

将DataContext的ObjectTrackingEnabled属性设置为false。

当ObjectTrackingEnabled设置为true时,DataContext的行为类似于工作单元 。 它会将任何对象加载到内存中,以便它可以跟踪对它的更改。 DataContext必须在最初加载对象时记住该对象,以了解是否已进行任何更改。

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

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

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

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