EF Core 中配置唯一约束应优先使用 HasIndex().IsUnique(),它稳定、直观且支持单列、多列及带过滤条件的唯一索引;HasAlternateKey 易引发更新异常,仅适用于需作为外键引用的场景;数据注解 [Index] 简洁但灵活性低。

EF Core 中配置唯一约束,核心目标是让数据库层面强制保证某字段或字段组合不重复。最常用且推荐的方式是 HasIndex().IsUnique(),而 HasAlternateKey 虽然语义上更贴近“备用唯一标识”,但实际使用中容易踩坑——比如迁移不生成约束、运行时行为受限等。
优先用 HasIndex().IsUnique() 创建唯一索引
这是最稳定、最直观、也最符合数据库原生语义的做法。它会在数据库中创建 UNIQUE 索引,支持空值(NULL 允许多个,除非加 NOT NULL 约束),且迁移能正确识别并生成 SQL。
- 单列唯一:在
OnModelCreating中写
.HasIndex(u => u.Email)
.IsUnique()
.HasDatabaseName("IX_Users_Email");
- 多列组合唯一:例如一个用户在一个分类下只能有一条 SKU 记录
.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] 特性声明,适合快速原型或简单场景:
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() 就够了,清晰、可控、少陷阱。










