这是我做过的一个小实验:
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对并发数据更新具有强大的功能。但是什么?
答案 0 :(得分:5)
这不是我之前遇到过的问题(因为我不倾向于长时间保持DataContexts开放),但看起来像其他人一样:
http://www.rocksthoughts.com/blog/archive/2008/01/14/linq-to-sql-caching-gotcha.aspx
答案 1 :(得分:5)
LinqToSql有各种各样的工具来处理并发问题。
然而,第一步是承认有一个并发问题需要解决!
首先,DataContext的预期对象生命周期应该与UnitOfWork匹配。如果你长时间坚持一个,那么你将不得不更加努力地工作,因为这个课程不是那么用的。
其次,DataContext跟踪每个对象的两个副本。一个是原始状态,一个是已更改/可更改状态。如果您要求Id = 1的MyClass,它会返回上次给你的相同实例,这是更改/可更改版本...而不是原始版本。它必须这样做以防止内存实例中的并发问题... LinqToSql不允许一个DataContext知道两个可更改的MyClass版本(Id = 1)。
第三,DataContext不知道您的内存中更改是在数据库更改之前还是之后,因此如果没有一些指导,就无法判断并发冲突。它看到的只是:
好的,既然已经说明了问题,可以采用以下几种方法来处理它。
你可以扔掉DataContext并重新开始。这对某些人来说有点沉重,但至少它很容易实现。
您可以通过调用DataContext.Refresh(RefreshMode, target)
(reference docs with many good concurrency links in the "Remarks" section)来请求使用数据库值刷新原始实例或已更改/可更改的实例。这将带来客户端的更改,并允许您的代码确定最终结果应该是什么。
您可以在dbml(ColumnAttribute.UpdateCheck)中关闭并发检查。这会禁用乐观并发,并且您的代码将踩踏其他任何人的更改。也很重,也很容易实现。
答案 2 :(得分:2)
将DataContext的ObjectTrackingEnabled属性设置为false。
当ObjectTrackingEnabled设置为true时,DataContext的行为类似于Unit of Work。它会将任何对象加载到内存中,以便它可以跟踪对它的更改。 DataContext必须在最初加载对象时记住该对象,以了解是否已进行任何更改。
如果您在只读方案中工作,则应关闭对象跟踪。它可以提高性能。
如果您不在只读方案中工作,那么我不确定您为什么希望它以这种方式工作。如果您进行了编辑,那么为什么要从数据库中提取修改后的状态呢?
答案 3 :(得分:1)
LINQ to SQL使用标识映射设计模式,这意味着它将始终返回给定主键的对象的同一实例(除非您关闭对象跟踪)。
如果你不想让第一个实例干扰第一个实例,那么解决方案就是使用第二个数据上下文,如果你这样做,则刷新第一个实例。