当前位置:首页 > C# > 正文

EF Core乐观并发控制详解(使用ConcurrencyToken实现安全数据更新)

在多用户同时操作数据库的场景中,如何避免数据被意外覆盖?EF Core乐观并发控制提供了一种优雅的解决方案。本文将手把手教你如何在C#项目中使用ConcurrencyToken特性来实现Entity Framework Core并发安全。

EF Core乐观并发控制详解(使用ConcurrencyToken实现安全数据更新) Core乐观并发控制 ConcurrencyToken C#并发处理 Entity Framework Core并发 第1张

什么是乐观并发控制?

乐观并发控制(Optimistic Concurrency Control)是一种假设冲突很少发生的并发策略。它不会在读取数据时加锁,而是在更新数据时检查自上次读取以来数据是否被其他用户修改过。如果发现已被修改,则抛出异常,由开发者决定如何处理。

这与悲观并发控制(如数据库行锁)不同,后者在读取时就加锁,阻止其他用户访问,适用于高冲突场景,但会降低系统吞吐量。

为什么需要EF Core乐观并发控制?

想象这样一个场景:

  • 用户A和用户B同时打开同一个产品编辑页面。
  • 用户A将产品价格从100元改为120元,并点击保存。
  • 几秒后,用户B将价格从100元改为90元,并点击保存。

如果没有并发控制,用户B的修改会直接覆盖用户A的更改,导致数据不一致。而通过C#并发处理中的乐观并发机制,系统可以在用户B保存时检测到冲突并提示用户。

如何在EF Core中启用乐观并发控制?

EF Core 提供了多种方式实现乐观并发控制,最常用的是使用 [ConcurrencyCheck] 特性或 [Timestamp](也称为行版本)。我们这里重点介绍 [ConcurrencyCheck] 配合 ConcurrencyToken 的用法。

步骤1:在实体类中添加并发令牌属性

首先,在你的实体类中添加一个用于跟踪并发的属性,并标记为 [ConcurrencyCheck]

using System.ComponentModel.DataAnnotations;public class Product{    public int Id { get; set; }        public string Name { get; set; } = string.Empty;        public decimal Price { get; set; }        [ConcurrencyCheck]    public DateTime LastModified { get; set; }}

注意:LastModified 字段将作为并发令牌。每次更新记录时,你都需要手动更新这个字段(例如设置为 DateTime.UtcNow)。

步骤2:在DbContext中配置(可选)

你也可以在 OnModelCreating 中使用 Fluent API 来配置并发令牌:

protected override void OnModelCreating(ModelBuilder modelBuilder){    modelBuilder.Entity<Product>()        .Property(p => p.LastModified)        .IsConcurrencyToken();}

这两种方式效果相同,你可以任选其一。

步骤3:处理并发冲突

当你调用 SaveChanges() 时,如果检测到并发冲突,EF Core 会抛出 DbUpdateConcurrencyException。你需要捕获并处理它。

public async Task<bool> UpdateProductAsync(Product updatedProduct){    try    {        // 更新 LastModified 时间戳        updatedProduct.LastModified = DateTime.UtcNow;                _context.Products.Update(updatedProduct);        await _context.SaveChangesAsync();        return true;    }    catch (DbUpdateConcurrencyException ex)    {        // 并发冲突发生!        foreach (var entry in ex.Entries)        {            var proposedValues = entry.CurrentValues;            var databaseValues = entry.GetDatabaseValues();            if (databaseValues == null)            {                // 数据已被删除                continue;            }            // 可以选择:            // 1. 重新加载最新数据并提示用户            // 2. 自动合并更改            // 3. 直接失败            // 示例:自动使用数据库最新值            entry.OriginalValues.SetValues(databaseValues);            entry.Property(p => p.LastModified).CurrentValue = DateTime.UtcNow;        }        // 重试保存        await _context.SaveChangesAsync();        return false; // 表示发生了冲突    }}

更推荐的方式:使用 RowVersion(时间戳)

虽然上面的方法可行,但在SQL Server等数据库中,更高效的做法是使用 rowversion(或 timestamp)列。EF Core 对此有原生支持。

public class Product{    public int Id { get; set; }    public string Name { get; set; } = string.Empty;    public decimal Price { get; set; }        [Timestamp]    public byte[] RowVersion { get; set; } = null!;}

使用 [Timestamp] 后,数据库会自动管理该字段,无需手动更新。EF Core 在更新时会自动将其加入 WHERE 条件,确保版本匹配。

总结

通过本文,你已经掌握了如何在 C# 项目中使用 EF Core乐观并发控制 来防止数据覆盖问题。关键点包括:

  • 使用 [ConcurrencyCheck][Timestamp] 标记并发令牌字段
  • 在更新时捕获 DbUpdateConcurrencyException
  • 根据业务需求决定如何处理冲突(重试、合并、提示用户等)

合理运用 Entity Framework Core并发机制,能显著提升你应用程序的数据一致性与用户体验。对于初学者来说,建议先从 [Timestamp] 开始尝试,它更简单且性能更好。

希望这篇关于 C#并发处理 的教程对你有所帮助!如有疑问,欢迎在评论区交流。