实体框架的原子增量
Posted
技术标签:
【中文标题】实体框架的原子增量【英文标题】:Atomic Increment with Entity Framework 【发布时间】:2013-08-01 00:40:30 【问题描述】:我有一个使用 Entity Framework 4.0 访问的 mysql 服务器。在数据库中,我有一个名为 Works 的表,其中一些重要。我用 Asp.net 开发网站。该表可同时访问更多用户。这种情况会导致错误的增量问题。
我的代码是这样的:
dbEntities myEntity = new dbEntities();
var currentWork = myEntity.works.Where(xXx => xXx.RID == 208).FirstOrDefault();
Console.WriteLine("Access work");
if (currentWork != null)
Console.WriteLine("Access is not null");
currentWork.WordCount += 5;//Default WordCount is 0
Console.WriteLine("Count changed");
myEntity.SaveChanges();
Console.WriteLine("Save changes");
Console.WriteLine("Current Count:" + currentWork.WordCount);
如果多个线程同时访问数据库,则只保留最后一次更改。
电流输出:
t1:线程一 - t2:线程二
t1:访问工作
t2:访问工作
t2: 访问不为空
t1: 访问不为空
t1:计数改变
t2:计数改变
t1:保存更改
t2:保存更改
t1:当前计数:5
t2:当前计数:5
预期输出:
t1:访问工作
t2:访问工作
t2: 访问不为空
t1: 访问不为空
t1:计数改变
t2:计数改变
t1:保存更改
t2:保存更改
t1:当前计数:5
t2:当前计数:10
我知道为什么会出现这个问题,因为这段代码不是原子的。怎么转原子操作?
【问题讨论】:
我知道在 MsSql 中你可以进行事务,你可以在 MySql 中做同样的事情吗?我也知道SaveChanges()
允许使用布尔值,但我认为这在这种情况下不会有帮助。
你能试试这个吗,lock (currentWork ) Console.WriteLine("Access is not null"); currentWork.WordCount += 5;//Default WordCount is 0 Console.WriteLine("Count changed"); myEntity.SaveChanges(); Console.WriteLine("Save changes");
我已经有一段时间没有做像样的线程了
哦,顺便说一句:一个好技巧。您可以将where
条件放在first or default
中,然后完全删除where
。
@gunr2171 这个技巧有助于提高性能吗?或者技巧可以帮助提高查询的可读性?
@Dreamcatcher,我认为它使它更具可读性,但我不确定性能。我认为它会为你做where
。当您有大量查询时很有用。
【参考方案1】:
使用实体框架,您无法将其设为“原子”操作。您有以下步骤:
-
从数据库加载实体
更改内存中的计数器
将更改的实体保存到数据库
在这些步骤之间,另一个客户端可以从数据库中加载仍然具有旧值的实体。
处理这种情况的最佳方法是使用乐观并发。这基本上意味着如果计数器不再与您在步骤 1 中加载实体时的相同,则步骤 3 中的更改将不会被保存。相反,您将获得一个异常,您可以通过重新加载实体来处理该异常和重新应用更改。
工作流程如下所示:
在Work
实体中,WordCount
属性必须标记为并发令牌(在 Code-First 的情况下,注释或 Fluent API)
从数据库加载实体
更改内存中的计数器
在try-catch
块中调用SaveChanges
并捕获DbUpdateConcurrencyException
类型的异常
如果发生异常,请从数据库中重新加载 catch
块中的实体,再次应用更改并再次调用 SaveChanges
重复上一步,直到不再出现异常
在this answer 中,您可以找到此过程的代码示例(使用DbContext
)。
【讨论】:
我可以使用这个解决方案数据库优先的方法吗? @Dreamcatcher:是的。WordCount
的属性设置在设计器中有一个选项,可以将其标记为并发令牌。
@Slauma - 如果更改必须在没有用户反馈的情况下发生?例如,客户订购产品,库存需要减少?
@Sam:在我描述的工作流程中,不涉及或不需要用户反馈。你为什么这么认为?也许我误解了你的问题......
那么,当它捕捉到并发异常时你会怎么做呢?您是否跟踪了哪些字段发生了更改以及更改了多少,以便您可以重新加载实体并重试?【参考方案2】:
如果您在单个进程中托管您的网站,下一个方法将起作用(它不适用于网络农场或网络花园):
private static readonly Locker = new object();
void Foo()
lock(Locker)
dbEntities myEntity = new dbEntities();
var currentWork = myEntity.works.Where(xXx => xXx.RID == 208).FirstOrDefault();
Console.WriteLine("Access work");
if (currentWork != null)
Console.WriteLine("Access is not null");
currentWork.WordCount += 5;//Default WordCount is 0
Console.WriteLine("Count changed");
myEntity.SaveChanges();
Console.WriteLine("Save changes");
Console.WriteLine("Current Count:" + currentWork.WordCount);
您还能做什么,通过 ObjectContext 使用原始 SQL 查询:
if (currentWork != null)
Console.WriteLine("Access is not null");
myEntity.ExecuteStoredCommand("UPDATE works SET WordCount = WordCount + 5 WHERE RID = @rid", new MySqlParameter("@rid", MySqlDbType.Int32)Value = 208)
Console.WriteLine("Count changed");
var record = myEntity.works.FirstOrDefault(xXx => xXx.RID == 208);
if(record != null)
Console.WriteLine("Current Count:" + record .WordCount);
【讨论】:
我以后会用webfarm,以后lock方法可能会出问题。我想 ExecuteStoreCommand 但我想用 EF 编写完整的代码。以上是关于实体框架的原子增量的主要内容,如果未能解决你的问题,请参考以下文章