乐观锁是一种并发控制策略,它假设多个事务可以同时进行而不互相干扰,只在提交时检查是否有冲突。在 EF Core 中,可以通过以下几种方式实现乐观锁:
1. 使用并发令牌 (Concurrency Token)
这是 EF Core 推荐的实现乐观锁的方式。
实现步骤:
-
在实体类中添加一个并发令牌属性:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// 并发令牌属性
[Timestamp]
public byte[] RowVersion { get; set; }
}
或者使用 Fluent API 配置:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.RowVersion)
.IsRowVersion();
}
-
当更新实体时,EF Core 会自动检查并发令牌:
try
{
var product = context.Products.Find(1);
product.Price = 20.99m;
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
// 处理并发冲突
Console.WriteLine("并发冲突发生!");
}
2. 使用其他属性作为并发令牌
除了 rowversion/timestamp 列,任何属性都可以配置为并发令牌:
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; }
[ConcurrencyCheck]
public DateTime LastUpdated { get; set; }
}
或者使用 Fluent API:
modelBuilder.Entity<Order>()
.Property(o => o.LastUpdated)
.IsConcurrencyToken();
3. 处理并发冲突
当检测到并发冲突时,EF Core 会抛出 DbUpdateConcurrencyException,你可以捕获并处理它:
try
{
// 尝试保存
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Product)
{
// 获取当前数据库值
var databaseValues = entry.GetDatabaseValues();
// 获取原始值(用户尝试保存的值)
var proposedValues = entry.CurrentValues;
// 获取原始值(最初查询的值)
var originalValues = entry.OriginalValues;
// 根据业务逻辑决定如何处理冲突
// 例如:合并更改、提示用户等
// 刷新原始值以便下次保存
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"不支持的并发冲突类型: " + entry.Metadata.Name);
}
}
// 重试保存
context.SaveChanges();
}
注意事项
-
并发令牌属性会在每次更新时自动更新
-
使用
rowversion/timestamp类型通常是最简单高效的方式 -
对于高并发场景,乐观锁比悲观锁性能更好
-
需要妥善处理并发冲突,提供良好的用户体验


被折叠的 条评论
为什么被折叠?



