具有实体框架的原子增量
本文关键字:实体 框架 | 更新日期: 2023-09-27 18:25:35
我有一个MySQL服务器,我使用实体框架4.0访问它。在数据库中,我有一个名为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
我知道为什么要解决这个问题,因为这个代码不是原子代码。我怎样才能进行原子操作?
使用实体框架,您不能将其作为"原子"操作。你有步骤:
- 从数据库加载实体
- 更改内存中的计数器
- 将更改的实体保存到数据库
在这些步骤之间,另一个客户端可以从仍然具有旧值的数据库中加载实体。
处理这种情况的最佳方法是使用乐观并发。这基本上意味着,如果计数器不再与步骤1中加载实体时的计数器相同,则步骤3中的更改将不会保存。相反,您将获得一个异常,可以通过重新加载实体并重新应用更改来处理该异常。
工作流程如下所示:
- 在
Work
实体中,WordCount
属性必须标记为并发令牌(如果是Code-First,则为注释或Fluent API) - 从数据库加载实体
- 更改内存中的计数器
- 调用
try-catch
块中的SaveChanges
并捕获类型为DbUpdateConcurrencyException
的异常 - 如果发生异常,则从数据库重新加载
catch
块中的实体,再次应用更改并再次调用SaveChanges
- 重复最后一步,直到不再出现异常为止
在这个答案中,您可以找到此过程的代码示例(使用DbContext
)。
如果您在一个进程中托管您的网站,下一种方法将起作用(它不能与web farm或web gardsen一起工作):
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);