EF Core 中配置唯一约束,核心目标是让数据库层面强制保证某字段或字段组合不重复。最常用且推荐的方式是 HasIndex().IsUnique(),而 HasAlternateKey 虽然语义上更贴近“备用唯一标识”,但实际使用中容易踩坑——比如迁移不生成约束、运行时行为受限等。
优先用 HasIndex().IsUnique() 创建唯一索引
这是最稳定、最直观、也最符合数据库原生语义的做法。它会在数据库中创建 UNIQUE 索引,支持空值(NULL 允许多个,除非加 NOT NULL 约束),且迁移能正确识别并生成 SQL。
单列唯一:在OnModelCreating中写 modelBuilder.Entity
.HasIndex(u => u.Email)
.IsUnique()
.HasDatabaseName("IX_Users_Email");
.HasIndex(p => new { p.CategoryId, p.Sku })
.IsUnique()
.HasDatabaseName("IX_Products_CategoryId_Sku");
.HasIndex(o => o.OrderNumber)
.IsUnique()
.HasFilter("[IsDeleted] = 0");
HasAlternateKey 不等于“加唯一约束”,慎用
HasAlternateKey的设计初衷是定义“可作为外键引用的目标键”,它隐含两个关键行为:一是自动添加唯一索引,二是将对应属性标记为 不可更新(concurrency token 或只读语义)。这会导致 SaveChanges 失败或意外抛异常,尤其在更新含备用键的实体时。 它确实会生成 UNIQUE 约束,但迁移名称固定为
AK_Entity_Property,不易自定义 若仅需唯一性校验,不涉及外键引用,就不要用它 常见误用:给 Email 字段设 Alternate Key 后,更新用户信息时报错“不能修改 Alternate Key 属性”
数据注解方式:简单但灵活性低
用
[Index]特性声明,适合快速原型或简单场景: [Index(nameof(Email), IsUnique = true)]
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public string Name { get; set; }
} 优点:代码简洁,无需改
OnModelCreating缺点:不支持筛选索引、无法命名索引、多列组合需用
nameof拼接,可读性差 注意:
[Index]是 EF Core 5+ 才支持的特性,旧版本不识别
验证和调试技巧
唯一约束是否生效,不能只看 C# 代码,要确认三件事:
执行dotnet ef migrations add AddUniqueIndex后,检查生成的迁移文件中是否有
CreateIndex且含
unique: true应用迁移后,连接数据库查
sys.indexes(SQL Server)或
pg_indexes(PostgreSQL),确认索引类型为 UNIQUE 手动插入重复数据测试,应收到数据库级异常(如 SqlException 错误号 2601/2627),而不是 EF 静默失败
基本上就这些。用
HasIndex().IsUnique()就够了,清晰、可控、少陷阱。
