样板代码
builder.Property(p=>p.RowVersion)
.IsRowVersion()
.IsRequired()
//.IsConcurrencyToken();C#很多开发者在使用 EF Core 时都会在这里产生困惑。主要区别:
因为 .IsRowVersion() 包含了 .IsConcurrencyToken() 的所有功能,并且比它更高级、更自动化。
可以把它们理解为“手动挡”和“自动挡”的区别:
1. IsConcurrencyToken() (手动挡)
如果你只写 .IsConcurrencyToken(),你是在告诉 EF Core:“把这个字段作为并发令牌,在生成 UPDATE 语句的 WHERE 条件里带上它”。致命缺点:数据库不会帮你自动更新这个字段的值。这意味着每次你修改商品信息时,你都必须在 C# 代码里手动给它赋一个新的值(比如 product.RowVersion = Guid.NewGuid().ToByteArray())。如果不手动赋新值,并发控制就会失效。
2. IsRowVersion() (自动挡)
.IsRowVersion() 在 EF Core 的底层源码中,等价于: IsConcurrencyToken() + ValueGeneratedOnAddOrUpdate()
它不仅告诉 EF Core 这是一个并发令牌,还告诉数据库:“每次这一行数据发生 UPDATE 时,请数据库引擎自动为这个字段生成一个全新的值。” 这样,你在 C# 代码里完全不需要去管 RowVersion 的值,数据库会在底层默默帮你完成所有的防冲突印记更新。
💡 架构师终极避坑指南:针对 MySQL 8.4.9 环境
我们项目使用的是 MySQL 8.4 数据库(通过 Pomelo.EntityFrameworkCore.MySql 驱动),有一个极其重要的数据库差异特性:
byte[] 类型的 RowVersion 是 SQL Server 的“特产”。 在 SQL Server 中,有一个专门的数据类型叫 rowversion (或 timestamp),完美对应 C# 的 byte[]。但是,MySQL 原生并没有这个数据类型!
如果在 MySQL 中强制使用 byte[] 作为 IsRowVersion(),EF Core 迁移时可能会报错,或者需要你手写复杂的触发器(Trigger)来模拟自动更新。
MySQL 下的企业级最佳实践
在 MySQL 中,原生支持自动更新并发令牌的数据类型是 TIMESTAMP(6)。对应到 C# 中,我们应该使用 DateTime 或 DateTimeOffset。
因此,想在 MySQL 环境下完美运行,需要对的 Product 做一次针对性的微调:
第 1 步:修改实体 (Product.cs) 把 byte[] 改为 DateTime:
// 把原来的 public byte[] RowVersion { get; private set; } = [];
// 改为:
public DateTime RowVersion { get; private set; }C#第 2 步:修改配置 (ProductConfiguration.cs) 配置代码本身不需要变,还是使用 .IsRowVersion(),但因为实体类型变了,Pomelo MySQL 驱动在底层会极其聪明地把它翻译成 MySQL 的原生并发特性:
builder.Property(p => p.RowVersion)
.IsRowVersion(); C#当执行数据库迁移 (dotnet ef migrations add ...) 时,你会看到生成的 SQL 语句完美适配了 MySQL 的自动更新特性: RowVersion TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)
这就是为什么选用 .IsRowVersion() 而不是 .IsConcurrencyToken() 的根本原因——它能完美激活底层数据库的原生自动化能力!
