Entity Framework Core 并发冲突的 解决方案
Entity Framework Core 并发冲突的 解决方案
在.NET Core 技术栈的小伙伴们相信 使用微软官方推出的EF Core(Entity Framework Core)的ORM 解决方案的时候,在SaveChanges() 的时候 会有这样的疑惑:如果有两个不同的线程去执行save 操作的时候,会不会造成相同的数据更改被覆盖的情况呢?
解决思路
•SaveChange() 的时候给代码加 lock。•ORM 框架提供并发冲突的支持。•数据库增加 “最后修改时间LastUpdateTime”。•数据库(MySql SqlServer 等等) 的事务隔离。
出于框架和易用性的角度来考虑,咱们选择第二个(ORM 框架提供并发冲突的支持)
Entity Framework Core 内置提供了一种基于异常抛出的解决方案来解决并发冲突的问题,那么我们来思考下这个EF Core 框架的原理是怎么样帮怎么检测这种情况的呢?
注意,本文所提到的“EF Core”与“Entity Framework Core”,是同一个意思。
原理分析
EF Core是如何检测当前线程修改的值是不是最新?
有三组值可以来验证是否当前修改的值为最新:
•“当前值” 是当前线程修改后的值。•“原始值” 是当前线程修改前的值。•“数据库值” 是已经存在数据库的值。
通过以上的获取到的三个值就可以验证当前修改的值是否为数据库中最新的值了。
框架解决方案
1.在SaveChanges期间 EF Core 框架会捕获异常 DbUpdateConcurrencyException。
2.在DbUpdateConcurrencyException.Entries为受到影响的实体准备一组新更改。
3.从数据库中读取新的值去更改。
4.重新SaveChanges ,知道不发生任何并发重提。
代码示例
using var context = new PersonContext();
// 查找一个实体
var person = context.People.Single(p => p.PersonId == 1);
person.PhoneNumber = "555-555-5555";
// 修改当前实体
context.Database.ExecuteSqlRaw(
"UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");
var saved = false;
while (!saved)
{
try
{
// 保存至数据库
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Person)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: 获取最新值,重新赋值
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
还有一个需要提醒的是
在某些可能会出现并发冲突的属性上增加 [ConcurrencyCheck] 特性,在修改的时候才会触发 DbUpdateConcurrencyExption 异常,才可以处理并发冲突。
欢迎关注我的公众号,原创技术文章第一时间推送。


浙公网安备 33010602011771号