[TOC]
相关函数说明
在 Entity Framework Core(EF Core)中,Fluent API 是通过重写 DbContext.OnModelCreating(ModelBuilder modelBuilder) 方法,使用 ModelBuilder 和 EntityTypeBuilder<T> 提供的链式方法来配置实体与数据库之间的映射关系。
相比 Data Annotations,Fluent API 功能更强大、更灵活、更集中,尤其适合复杂场景(如继承、全局过滤、复杂关系、索引、默认值等)。
✅ 一、常用 Fluent API 配置分类及函数速查
1️⃣ 表与架构(Table & Schema)
| 方法 | 说明 |
|---|---|
ToTable("TableName") | 指定表名 |
ToTable("TableName", "SchemaName") | 指定表名和 Schema |
HasComment("...") | 为表添加注释(生成到数据库) |
csharp
entity.ToTable("Sys_User", "auth");
entity.HasComment("系统用户表");2️⃣ 主键(Primary Key)
| 方法 | 说明 |
|---|---|
HasKey(e => e.Id) | 显式指定主键(当不是 Id 时) |
HasKey(e => new { e.UserId, e.RoleId }) | 复合主键 |
csharp
entity.HasKey(e => e.UserId); // 单主键
entity.HasKey(e => new { e.UserId, e.RoleId }); // 联合主键💡 如果属性名为
Id或<ClassName>Id,EF Core 默认就是主键,无需配置。
3️⃣ 列配置(Column)
| 方法 | 说明 |
|---|---|
Property(e => e.Name).HasColumnName("user_name") | 指定列名 |
.HasColumnType("varchar(100)") | 指定数据库类型 |
.HasMaxLength(100) | 设置最大长度(字符串/byte[]) |
.IsRequired() | 设置为非空(NOT NULL) |
.HasDefaultValue("default") | 设置数据库默认值 |
.HasDefaultValueSql("GETUTCDATE()") | 设置默认值 SQL 表达式 |
.ValueGeneratedNever() | 值由程序提供(不自动生成) |
.ValueGeneratedOnAdd() | 插入时生成(如自增) |
.ValueGeneratedOnAddOrUpdate() | 插入或更新时生成(如时间戳) |
.HasComment("用户名") | 列注释 |
csharp
entity.Property(e => e.Email)
.HasColumnName("email")
.HasColumnType("nvarchar(255)")
.IsRequired()
.HasDefaultValue("unknown@example.com")
.HasComment("用户邮箱");4️⃣ 索引(Index)
| 方法 | 说明 |
|---|---|
HasIndex(e => e.Email) | 创建普通索引 |
.IsUnique() | 设置为唯一索引 |
HasIndex(e => new { e.TenantId, e.Code }) | 复合索引 |
csharp
entity.HasIndex(e => e.PhoneNumber).IsUnique();
entity.HasIndex(e => new { e.CompanyId, e.EmployeeNo });⚠️ EF Core 5+ 支持在
OnModelCreating中直接定义索引,无需迁移额外操作。
5️⃣ 关系配置(Relationships)
一对多(One-to-Many)
csharp
// User 有多个 Order
modelBuilder.Entity<User>()
.HasMany(u => u.Orders)
.WithOne(o => o.User)
.HasForeignKey(o => o.UserId)
.OnDelete(DeleteBehavior.Cascade);一对一(One-to-One)
csharp
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);多对多(Many-to-Many)——EF Core 5+
csharp
modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany(c => c.Students)
.UsingEntity(j => j.ToTable("StudentCourse")); // 中间表名对于显式中间表(如你的
UserRoleEntity),应使用两个一对多关系。
6️⃣ 全局查询过滤器(Global Query Filters)
常用于软删除、多租户:
csharp
modelBuilder.Entity<Product>()
.HasQueryFilter(p => p.IsDeleted == false);
modelBuilder.Entity<TenantEntity>()
.HasQueryFilter(t => t.TenantId == CurrentTenant.Id);7️⃣ 继承映射(TPH / TPT / TPC)
- TPH(默认):单表继承csharp
modelBuilder.Entity<Animal>().HasDiscriminator<string>("Type"); - TPT:每类型一张表(EF Core 5+)csharp
modelBuilder.Entity<Cat>().ToTable("Cats"); modelBuilder.Entity<Dog>().ToTable("Dogs");
8️⃣ 忽略属性或实体
| 方法 | 说明 |
|---|---|
Ignore(e => e.TempValue) | 忽略某个属性 |
modelBuilder.Ignore<AuditLog>() | 忽略整个实体 |
csharp
entity.Ignore(e => e.FullName); // 不映射到数据库9️⃣ 并发控制(Concurrency)
csharp
entity.Property(e => e.RowVersion)
.IsRowVersion(); // 自动标记为并发令牌,并设为 byte[]等价于 [Timestamp] 特性。
🔟 其他实用配置
| 方法 | 说明 |
|---|---|
HasNoKey() | 无主键实体(如视图) |
ToView("UserSummary") | 映射到数据库视图 |
HasData(...) | 种子数据(Seed Data) |
csharp
modelBuilder.Entity<Role>().HasData(
new Role { Id = 1, Name = "Admin" },
new Role { Id = 2, Name = "User" }
);✅ 二、Fluent API vs Data Annotations 对照表(部分)
| 功能 | Fluent API | Data Annotation |
|---|---|---|
| 表名 | ToTable("T") | [Table("T")] |
| 列名 | HasColumnName("col") | [Column("col")] |
| 类型 | HasColumnType("varchar(50)") | [Column(TypeName="varchar(50)")] |
| 默认值 | HasDefaultValue("x") | ❌ 不支持 |
| 索引 | HasIndex(...) | [Index](EF Core 5+) |
| 必填 | IsRequired() | [Required] |
| 主键 | HasKey() | [Key] |
| 自动生成 | ValueGeneratedOnAdd() | [DatabaseGenerated] |
✅ Fluent API 支持所有功能,Data Annotations 是其子集。
✅ 三、最佳实践建议
- 优先使用 Fluent API:配置集中、功能全、实体类干净;
- 避免混合冲突:Fluent API 优先级高于 Data Annotations;
- 复杂关系必须用 Fluent API:如多对多中间表、条件外键等;
- 种子数据、全局过滤、默认值等只能用 Fluent API。
📌 示例:完整配置一个实体(Fluent API)
csharp
modelBuilder.Entity<UserEntity>(entity =>
{
entity.ToTable("Sys_User");
entity.HasComment("系统用户表");
entity.HasKey(e => e.Id);
entity.Property(e => e.Id)
.HasColumnType("bigint")
.ValueGeneratedNever();
entity.Property(e => e.UserName)
.IsRequired()
.HasMaxLength(50)
.HasComment("登录名");
entity.Property(e => e.Email)
.IsRequired()
.HasMaxLength(100)
.HasDefaultValue("unknown@example.com");
entity.HasIndex(e => e.Email).IsUnique();
entity.Property(e => e.CreatedAt)
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
entity.HasQueryFilter(u => u.IsDeleted == false);
});如果你有具体场景(比如“如何配置多对多中间表带额外字段”),我可以给出针对性 Fluent API 写法!
重点 ⭐ 完整案例
当然可以!下面我给你一个 完整、可运行的具体例子,展示如何使用 Fluent API 配置 EF Core 实体,包括:
- 表名、列类型、主键(手动赋值)
- 必填字段、默认值、索引
- 一对多关系(User → Orders)
- 多对多关系(通过显式中间表:User ↔ Role)
- 软删除全局过滤器
- 种子数据(Seed Data)
📁 项目结构(简化)
Models/
├── User.cs
├── Role.cs
├── UserRole.cs // 显式中间表(带 Id)
DbContexts/
└── AppDbContext.cs1️⃣ 实体类(保持干净,无任何特性)
User.cs
csharp
namespace MyApp.Models;
public class User
{
public long Id { get; set; }
public string UserName { get; set; } = default!;
public string Email { get; set; } = default!;
public bool IsDeleted { get; set; }
public DateTime CreatedAt { get; set; }
// 导航属性
public List<Order> Orders { get; set; } = new();
public List<UserRole> UserRoles { get; set; } = new();
}Role.cs
csharp
namespace MyApp.Models;
public class Role
{
public long Id { get; set; }
public string Name { get; set; } = default!;
public List<UserRole> UserRoles { get; set; } = new();
}UserRole.cs(显式中间表,带自己的 Id)
csharp
namespace MyApp.Models;
public class UserRole
{
public long Id { get; set; } // 中间表自己的主键
public long UserId { get; set; }
public long RoleId { get; set; }
public DateTime AssignedAt { get; set; }
// 导航
public User User { get; set; } = default!;
public Role Role { get; set; } = default!;
}Order.cs(用于一对多)
csharp
namespace MyApp.Models;
public class Order
{
public long Id { get; set; }
public long UserId { get; set; }
public string ProductName { get; set; } = default!;
public decimal Amount { get; set; }
public User User { get; set; } = default!;
}2️⃣ DbContext + Fluent API 配置
AppDbContext.cs
csharp
using Microsoft.EntityFrameworkCore;
using MyApp.Models;
namespace MyApp.Contexts;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
public DbSet<Role> Roles => Set<Role>();
public DbSet<UserRole> UserRoles => Set<UserRole>();
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ===== User 配置 =====
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("Sys_User");
entity.HasKey(u => u.Id);
entity.Property(u => u.Id).HasColumnType("bigint").ValueGeneratedNever();
entity.Property(u => u.UserName)
.IsRequired()
.HasMaxLength(50);
entity.Property(u => u.Email)
.IsRequired()
.HasMaxLength(100);
entity.Property(u => u.CreatedAt)
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
entity.HasIndex(u => u.Email).IsUnique();
// 软删除全局过滤器(这里先在实体上加,也可以放外面)
entity.HasQueryFilter(u => !u.IsDeleted);
});
// ===== Role 配置 =====
modelBuilder.Entity<Role>(entity =>
{
entity.ToTable("Sys_Role");
entity.HasKey(r => r.Id);
entity.Property(r => r.Id).HasColumnType("bigint").ValueGeneratedNever();
entity.Property(r => r.Name).IsRequired().HasMaxLength(30);
});
// ===== UserRole 中间表配置 =====
modelBuilder.Entity<UserRole>(entity =>
{
entity.ToTable("Sys_User_Role");
entity.HasKey(ur => ur.Id); // 中间表有自己的主键
entity.Property(ur => ur.Id).HasColumnType("bigint").ValueGeneratedNever();
entity.Property(ur => ur.UserId).HasColumnType("bigint");
entity.Property(ur => ur.RoleId).HasColumnType("bigint");
entity.Property(ur => ur.AssignedAt)
.HasColumnType("datetime2")
.HasDefaultValueSql("GETUTCDATE()");
// 关系配置
entity.HasOne(ur => ur.User)
.WithMany(u => u.UserRoles)
.HasForeignKey(ur => ur.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(ur => ur.Role)
.WithMany(r => r.UserRoles)
.HasForeignKey(ur => ur.RoleId)
.OnDelete(DeleteBehavior.Cascade);
});
// ===== Order 配置(一对多)=====
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Orders");
entity.HasKey(o => o.Id);
entity.Property(o => o.Id).HasColumnType("bigint").ValueGeneratedNever();
entity.Property(o => o.UserId).HasColumnType("bigint");
entity.Property(o => o.ProductName).IsRequired().HasMaxLength(100);
entity.Property(o => o.Amount).HasColumnType("decimal(18,2)");
// 配置外键关系
entity.HasOne(o => o.User)
.WithMany(u => u.Orders)
.HasForeignKey(o => o.UserId)
.OnDelete(DeleteBehavior.Restrict); // 防止误删用户时删订单
});
// ===== 种子数据 =====
modelBuilder.Entity<Role>().HasData(
new Role { Id = 1, Name = "Admin" },
new Role { Id = 2, Name = "User" }
);
modelBuilder.Entity<User>().HasData(
new User { Id = 1001, UserName = "admin", Email = "admin@example.com", IsDeleted = false }
);
modelBuilder.Entity<UserRole>().HasData(
new UserRole { Id = 1, UserId = 1001, RoleId = 1, AssignedAt = DateTime.UtcNow }
);
}
}3️⃣ 启用自动迁移(Program.cs)
csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
// 自动应用迁移(创建/更新表)
using var scope = app.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Database.Migrate(); // 👈 自动建表 + 插入种子数据
app.MapGet("/", () => "Hello World!");
app.Run();4️⃣ 效果说明
运行项目后,EF Core 会自动:
✅ 创建以下表:
Sys_User(带唯一索引 on Email,CreatedAt 默认当前时间)Sys_RoleSys_User_Role(中间表,有自己 Id 和分配时间)Orders
✅ 设置:
- 所有
Id为bigint,且 不自增(需手动赋值,如雪花 ID) User.IsDeleted = false的记录才被查询到(软删除)Admin和User角色已插入admin用户已创建并分配 Admin 角色
✅ 支持关系查询:
csharp
var user = await context.Users
.Include(u => u.UserRoles).ThenInclude(ur => ur.Role)
.Include(u => u.Orders)
.FirstAsync(u => u.UserName == "admin");✅ 总结
这个例子展示了 Fluent API 的典型用法:
| 功能 | 实现方式 |
|---|---|
| 表名/列类型/主键 | ToTable, HasColumnType, ValueGeneratedNever |
| 必填/长度/默认值 | IsRequired, HasMaxLength, HasDefaultValueSql |
| 索引 | HasIndex(...).IsUnique() |
| 一对多 | HasOne.WithMany.HasForeignKey |
| 多对多(显式中间表) | 两个 HasOne.WithMany |
| 软删除 | HasQueryFilter |
| 种子数据 | HasData |
💡 这是企业级项目中非常常见的配置模式。
如果你需要生成 迁移文件,只需在终端执行:
bash
dotnet ef migrations add Init
dotnet ef database update这样数据库结构就完全由代码控制,安全、可追溯、可协作!
需要我帮你生成雪花 ID 工具类或封装通用仓储吗?😊