在C# EF Core开发中,当多个用户或线程同时修改同一条数据库记录时,后提交的操作可能会覆盖先提交操作的修改,这就是并发冲突。RowVersion是数据库层面提供的行版本控制方案,EF Core可以通过该字段自动检测并发修改,避免数据不一致问题。

并发冲突的产生场景
假设用户A和用户B同时查询到同一条商品记录,商品库存为10。用户A先提交修改,将库存减为8并保存成功。随后用户B基于自己查询到的库存10进行修改,将库存减为9并保存,此时就会覆盖用户A的修改结果,导致库存数据异常。这种场景就是典型的并发冲突。
RowVersion的实现原理
RowVersion是SQL Server中的时间戳类型字段,数据库会在每次更新对应行记录时,自动更新该字段的值。当EF Core执行更新操作时,会将查询时获取的RowVersion值作为更新条件的一部分,如果数据库中的RowVersion值和查询时的值不一致,说明记录已经被其他操作修改过,更新语句会影响0行数据,EF Core就会抛出并发异常。
EF Core中配置RowVersion
首先需要在实体类中定义RowVersion属性,通常使用byte数组类型,并通过特性或Fluent API标记为并发令牌。
实体类定义
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Stock { get; set; }
// 定义RowVersion属性
[Timestamp]
public byte[] RowVersion { get; set; }
}
Fluent API配置方式
如果不想使用数据注解,也可以通过Fluent API在DbContext中配置:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 配置RowVersion为并发令牌
modelBuilder.Entity<Product>()
.Property(p => p.RowVersion)
.IsRowVersion();
}
}
处理并发冲突
当并发冲突发生时,EF Core会抛出DbUpdateConcurrencyException异常,我们可以通过捕获该异常来处理冲突逻辑,常见的处理方式包括重试操作、提示用户数据已被修改等。
基础并发异常处理示例
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
public class ProductService
{
private readonly AppDbContext _dbContext;
public ProductService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public void UpdateProductStock(int productId, int reduceCount)
{
try
{
// 查询商品
var product = _dbContext.Products.FirstOrDefault(p => p.Id == productId);
if (product == null)
{
Console.WriteLine("商品不存在");
return;
}
// 修改库存
product.Stock -= reduceCount;
// 保存修改,若发生并发冲突会抛出异常
_dbContext.SaveChanges();
Console.WriteLine("库存修改成功");
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine("数据已被其他用户修改,请重新查询后再操作");
// 可以获取当前数据库中的最新值
var entry = ex.Entries.Single();
var databaseValues = entry.GetDatabaseValues();
if (databaseValues != null)
{
Console.WriteLine($"最新库存为:{databaseValues["Stock"]}");
}
}
}
}
自动重试的并发冲突处理
如果希望冲突时自动重试操作,可以编写重试逻辑:
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
public class ProductService
{
private readonly AppDbContext _dbContext;
public ProductService(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public void UpdateProductStockWithRetry(int productId, int reduceCount, int maxRetryCount = 3)
{
int retryCount = 0;
while (retryCount < maxRetryCount)
{
try
{
var product = _dbContext.Products.FirstOrDefault(p => p.Id == productId);
if (product == null)
{
Console.WriteLine("商品不存在");
return;
}
product.Stock -= reduceCount;
_dbContext.SaveChanges();
Console.WriteLine("库存修改成功");
return;
}
catch (DbUpdateConcurrencyException)
{
retryCount++;
if (retryCount == maxRetryCount)
{
Console.WriteLine("多次重试后仍发生并发冲突,请稍后再试");
return;
}
// 重置上下文的跟踪状态,重新查询数据
_dbContext.ChangeTracker.Clear();
}
}
}
}
注意事项
- RowVersion字段不需要手动赋值,数据库会自动维护其值,EF Core也会自动处理该字段的查询和更新逻辑。
- 如果使用其他数据库,比如MySQL,可以使用
timestamp类型或者自增版本号字段实现类似功能,配置方式略有不同。 - 并发冲突处理需要根据业务场景选择合适的策略,不是所有场景都适合自动重试,比如涉及资金的操作需要更谨慎的处理逻辑。
EF_CoreRowVersion并发冲突C#数据库修改时间:2026-06-26 06:42:14