索引是数据库性能优化中最基础也最有效的手段之一。在EF Core中进行Code First开发时,实体类的设计直接决定了最终生成的数据库表结构,索引也不例外。EF Core从早期版本开始就支持通过Fluent API配置索引,后续版本又引入了更灵活的特性,比如筛选索引、降序索引以及.NET新版的Index数据注解。本文将系统地讲解这些配置方式,并结合实际场景分析各自的使用时机。

使用Fluent API的HasIndex方法创建索引
Fluent API是EF Core中配置索引最主流的方式,在OnModelCreating方法中通过HasIndex完成。最简单的单列索引写法如下:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.HasDatabaseName("IX_Users_Email");
}
这段代码会在Users表的Email列上创建一个普通索引,HasDatabaseName用于指定索引在数据库中的名称。如果不显式指定,EF Core会按照默认约定生成名称,格式一般是IX_表名_列名。
如果需要创建复合索引,也就是基于多个列的联合索引,只需在lambda表达式中传入一个匿名对象:
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.UserId, o.CreateTime })
.HasDatabaseName("IX_Orders_UserId_CreateTime");
复合索引的列顺序非常重要。数据库遵循最左前缀原则,查询条件中只有包含索引的第一列时才能有效利用该索引。上面的例子中,按UserId查询或按UserId加CreateTime组合查询都可以走索引,但单独按CreateTime查询则无法使用。因此在设计复合索引时,应当把等值查询常用的列放在前面,范围查询的列放在后面。
唯一索引的配置只需在HasIndex之后链式调用IsUnique:
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique();
唯一索引不仅起到加速查询的作用,还会在数据库层面强制约束值的唯一性。插入重复的Email时,数据库会抛出唯一键冲突异常。这在业务上非常实用,比如防止用户名重复注册、防止订单号重复生成等场景。
使用数据注解方式创建索引
在较新版本的EF Core中,可以直接在实体类上使用[Index]特性,这种方式写起来更加直观,减少了在OnModelCreating中堆积大量配置代码的情况:
[Index(nameof(Email), Name = "IX_Users_Email")]
[Index(nameof(UserName), IsUnique = true)]
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
一个类上可以叠加多个[Index]特性来创建多个索引。复合索引则通过传入多个属性名实现,写作[Index(nameof(A), nameof(B))]。这种方式适合索引配置比较简单、希望配置和实体定义放在一起维护的团队。
需要注意的是,[Index]特性是在实体类级别应用的,而不是属性级别。这一点和[Required]、[MaxLength]这类属性级注解不同,初学者容易搞混。另外,如果项目需要兼容较老的EF Core版本,数据注解方式可能不可用,此时仍需回归Fluent API。
两种方式如何选择?一般来说,简单的单列索引和唯一索引用数据注解足够了;但涉及筛选条件、排序方向、填充因子等高级配置时,Fluent API的表达能力更强。实际项目中两者混用也很常见,关键是在团队内部保持风格一致。
进阶配置:筛选索引与降序索引
筛选索引是SQL Server等数据库提供的特性,只对表中满足条件的行建立索引,能够减少索引体积并提升写入性能。一个典型场景是软删除:表中大量数据带有IsDeleted标记,而实际查询几乎总是过滤掉已删除数据,这时可以只对未删除的行建索引:
modelBuilder.Entity<Order>()
.HasIndex(o => o.OrderNo)
.IsUnique()
.HasFilter("[IsDeleted] = 0");
如果不希望创建筛选索引,可以调用HasFilter(null)显式清除筛选条件。要注意筛选表达式写的是SQL片段而不是C#表达式,不同数据库的语法有差异,跨数据库的项目需要特别留意可移植性问题。
从EF Core开始支持降序索引配置,IsDescending方法允许指定索引中各列的排序方向:
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.UserId, o.CreateTime })
.IsDescending(false, true);
上面的配置表示UserId升序、CreateTime降序。当业务查询经常按时间倒序排列,例如展示用户最新的订单列表时,降序索引可以让数据库避免额外的排序操作,直接按索引顺序返回结果。
索引配置的实践建议
配置完索引后别忘了生成迁移让配置真正落地到数据库:
dotnet ef migrations add AddOrderIndexes dotnet ef database update
索引并非越多越好。每加一个索引,插入和更新操作都要额外维护索引结构,写入性能会下降,占用的存储空间也会增加。建议优先为高频查询条件中的列、外键列以及排序和分组涉及的列建立索引,并通过执行计划或慢查询日志验证索引是否真的被命中。
还有一个容易踩的坑:EF Core默认不会为外键自动创建索引(SQL Server关系型数据库中EF Core实际上会为外键创建索引,但某些数据库提供程序不会)。因此显式为外键列配置索引是一个好习惯,尤其是经常按外键做关联查询的表。
最后提醒一点,字符串列上的索引长度受数据库限制,比如MySQL的InnoDB索引有长度上限,过长的字符串列建议配合HasMaxLength约束列长度,否则迁移可能失败或索引无法生效。索引设计应该在建模阶段就认真规划,而不是等到线上出现性能问题再补救。
EF Core索引配置Fluent APIHasIndex修改时间:2026-09-03 13:38:49