You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server与EF Core中更新时自动递增字段的实现问题

实现EF Core基于INTEGER Version列的乐观并发控制

这个思路非常合理!基于基类统一处理Version列的并发控制,能避免重复代码,完美适配你所有表的约定。我来给你梳理具体的实现步骤和注意事项:

1. 创建带Version列的抽象基类

首先定义一个抽象基类,包含所有实体共有的Version列,并标记它为并发检查列:

public abstract class BaseEntity
{
    // 假设所有实体都有Id主键,可根据你的实际调整
    public int Id { get; set; }
    
    // 标记该列为并发检查字段
    [ConcurrencyCheck]
    public int Version { get; set; }
}

这里用[ConcurrencyCheck]而不是[Timestamp],因为[Timestamp]是专门针对SQL Server的rowversion(原timestamp)类型设计的,它是自动生成的只读字段,不适合你手动递增的场景。[ConcurrencyCheck]会告诉EF Core在更新/删除时,把这个列的值作为条件的一部分,确保只有当数据库中的值和实体中的值一致时才会执行操作。

2. 让业务实体继承基类

所有需要遵循Version约定的实体都继承这个基类,比如:

public class Product : BaseEntity
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Order : BaseEntity
{
    public DateTime OrderDate { get; set; }
    public decimal TotalAmount { get; set; }
}

3. 重写DbContext的SaveChanges方法统一处理Version递增

在你的DbContext中重写SaveChanges和SaveChangesAsync方法,自动为所有修改的实体递增Version值:

public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }

    // 构造函数、OnConfiguring等配置代码省略...

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        IncrementVersionForModifiedEntities();
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

    public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
    {
        IncrementVersionForModifiedEntities();
        return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    }

    private void IncrementVersionForModifiedEntities()
    {
        // 筛选出所有状态为Modified的BaseEntity派生类实体
        var modifiedEntities = ChangeTracker.Entries<BaseEntity>()
            .Where(entry => entry.State == EntityState.Modified);

        foreach (var entry in modifiedEntities)
        {
            // 递增Version值
            entry.Entity.Version += 1;
            // 手动标记Version属性为已修改,确保EF Core会将其包含在更新语句中
            entry.Property(e => e.Version).IsModified = true;
        }
    }
}

这段代码会在保存更改前,自动找到所有被修改的实体,把它们的Version加1,并且确保EF Core会更新这个字段。

4. 处理并发冲突

当两个请求同时更新同一行数据时,EF Core会因为Version不匹配抛出DbUpdateConcurrencyException。你需要在业务代码中捕获这个异常,处理冲突场景:

public async Task UpdateProductPrice(int productId, decimal newPrice)
{
    try
    {
        var product = await _dbContext.Products.FindAsync(productId);
        if (product == null)
        {
            throw new KeyNotFoundException("产品不存在");
        }

        product.Price = newPrice;
        await _dbContext.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        var entry = ex.Entries.Single();
        var databaseValues = await entry.GetDatabaseValuesAsync();

        if (databaseValues == null)
        {
            // 数据已被其他用户删除
            Console.WriteLine("该产品已被其他用户删除,无法完成更新");
        }
        else
        {
            var currentProduct = (Product)entry.Entity;
            var databaseProduct = (Product)databaseValues.ToObject();
            
            // 这里可以根据业务需求处理,比如提示用户数据已更新,或者合并修改
            Console.WriteLine($"更新失败:该产品已被其他用户修改,当前版本为{databaseProduct.Version},您的版本为{currentProduct.Version},请刷新后重试");
        }
    }
}

额外注意事项

  • 新增实体的Version初始值:新增实体时Version可以默认设为0,因为新增操作不存在并发冲突(还没有其他用户能修改它)。
  • 删除操作的并发检查:当删除实体时,EF Core也会检查Version列,如果数据库中的Version和实体中的不一致,同样会抛出DbUpdateConcurrencyException,这符合乐观并发的预期,避免删除已经被修改的数据。
  • 迁移配置:如果用EF Core迁移生成数据库表,它会自动将Version列创建为INT类型,不需要额外配置。

内容的提问来源于stack exchange,提问作者Valuator

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:35:59