乐观锁是一种并发控制策略,它假设多个事务可以同时进行而不互相干扰,只在提交时检查是否有冲突。在 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
类型通常是最简单高效的方式 -
对于高并发场景,乐观锁比悲观锁性能更好
-
需要妥善处理并发冲突,提供良好的用户体验