Skip to content

[TOC]

相关函数说明

在 Entity Framework Core(EF Core)中,Fluent API 是通过重写 DbContext.OnModelCreating(ModelBuilder modelBuilder) 方法,使用 ModelBuilderEntityTypeBuilder<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 APIData 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 是其子集。


✅ 三、最佳实践建议

  1. 优先使用 Fluent API:配置集中、功能全、实体类干净;
  2. 避免混合冲突:Fluent API 优先级高于 Data Annotations;
  3. 复杂关系必须用 Fluent API:如多对多中间表、条件外键等;
  4. 种子数据、全局过滤、默认值等只能用 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.cs

1️⃣ 实体类(保持干净,无任何特性)

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_Role
  • Sys_User_Role(中间表,有自己 Id 和分配时间)
  • Orders

✅ 设置:

  • 所有 Idbigint,且 不自增(需手动赋值,如雪花 ID)
  • User.IsDeleted = false 的记录才被查询到(软删除)
  • AdminUser 角色已插入
  • 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 工具类或封装通用仓储吗?😊