读本文前先掌握乐观锁和悲观锁的概念
使用乐观锁处理并发冲突
数据库并发指的是多个进程或用户同时访问或更改数据库中的相同数据的情况。 并发控制指的是用于在发生并发更改时确保数据一致性的特定机制。
EF Core 实现乐观并发控制,这意味着它将允许多个进程或用户独立进行更改而不产生同步或锁定的开销。 在理想情况下,这些更改将不会相互干扰,因此都能够成功。 在最坏的情况下,两个或更多进程将尝试进行冲突更改,其中只有一个进程应该成功。
并发控制在 EF Core 中的工作原理
配置为并发令牌的属性用于实现乐观并发控制:每当在 SaveChanges
期间执行更新或删除操作时,会将数据库上的并发令牌值与通过 EF Core 读取的原始值进行比较。
- 如果这些值匹配,则可以完成该操作。
- 如果这些值不匹配,EF Core 会假设另一个用户已执行冲突操作,并中止当前事务。
另一个用户已执行与当前操作冲突的操作的情况称为并发冲突。
数据库提供程序负责实现并发令牌值的比较。
在关系数据库上,EF Core 包括对任何 UPDATE
或 DELETE
语句的 WHERE
子句中的并发令牌值的检查。 执行这些语句后,EF Core 会读取受影响的行数。
如果未影响任何行,将检测到并发冲突,并且 EF Core 会引发 DbUpdateConcurrencyException
。
例如,我们可能希望将 Person
上的 LastName
配置为并发令牌。 则针对用户的任何更新操作将包括 WHERE
子句中的并发检查:
SQL复制
UPDATE [Person] SET [FirstName] = @p1
WHERE [PersonId] = @p0 AND [LastName] = @p2;
解决并发冲突
继续前面的示例,如果一个用户尝试保存对 Person
所做的某些更改,但另一个用户已更改 LastName
,则将引发异常。
此时,应用程序可能只需通知用户由于发生冲突更改而导致更新未成功,然后继续操作。 但可能需要提示用户确保此记录仍表示同一实际用户并重试该操作。
此过程是解决并发冲突的一个示例。
解决并发冲突涉及将当前 DbContext
中挂起的更改与数据库中的值进行合并。 要合并的值将根据应用程序的不同而有所不同,并且可能由用户输入指示。
有三组值可用于帮助解决并发冲突:
- “当前值”是应用程序尝试写入数据库的值。
- “原始值”是在进行任何编辑之前最初从数据库中检索的值。
- “数据库值”是当前存储在数据库中的值。
处理并发冲突的常规方法是:
- 在
SaveChanges
期间捕获DbUpdateConcurrencyException
。 - 使用
为受影响的实体准备一组新更改。
- 刷新并发令牌的原始值以反映数据库中的当前值。
- 重试该过程,直到不发生任何冲突。
在下面的示例中,将 和
设置为并发令牌。 在包括应用程序特定逻辑以选择要保存的值的位置处有一条
// TODO:
注释。
注意需要在实体类需要进行乐观锁的属性标记为[ConcurrencyCheck]
[Table("Infrastructure_User")]
public class Infrastructure_User
{
[Key]
[Required]
[Column("userId")]
public int UserId { get; set; }
//需要进行乐观锁比较的标记
[ConcurrencyCheck]
[Column("displayName")]
public string DisplayName { get; set; }
[Column("login")]
public string Login { get; set; }
}
using ;
using System;
using ;
namespace EFLearn
{
class Program
{
static async Main(string[] args)
{
ApplicationDbContext context = new ApplicationDbContext();
var items = ();
= "管理员2";
("update infrastructure_User set DisplayName='管理员1' where userid=1");
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in )
{
if ( is Infrastructure_User)
{
var proposedValues = ;
var databaseValues = ();
foreach (var property in )
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
}
(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ );
}
}
}
}
("执行结束");
}
}
}
下一篇 C#【EF Core框架】进行数据库事务处理/lichao19897314/article/details/122881390