创建并配置模型

EF Core 使用元数据模型来描述如何将应用程序的实体类型映射到基础数据库。 此模型是使用一组约定构建的,这些约定是寻找常见模式的启发式方法。 然后,可以使用映射特性(也称为数据注释)自定义模型和/或在 OnModelCreating 中调用 ModelBuilder 方法(也称为 Fluent API),这两者都将替代约定执行的配置。

大多数配置可以应用于面向任何数据存储的模型。 提供程序还可以启用特定于特定数据存储的配置,也可以忽略不支持或不适用的配置。 有关提供程序特定配置的文档,请参阅数据库提供程序部分。

提示

可在 GitHub 中查看此文章的示例

使用 fluent API 配置模型

可在派生上下文中替代 OnModelCreating 方法,并使用 Fluent API 来配置模型。 此配置方法最为有效,并可在不修改实体类的情况下指定配置。 Fluent API 配置具有最高优先级,并将替代约定和数据注释。 配置按调用方法的顺序应用,如果存在任何冲突,最新调用将替代以前指定的配置。

using Microsoft.EntityFrameworkCore;

namespace EFModeling.EntityProperties.FluentAPI.Required;

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    #region Required
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.Url)
            .IsRequired();
    }
    #endregion
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

提示

若要将相同的配置应用于模型中的多个对象,请参阅批量配置

分组配置

为了减小 OnModelCreating 方法的大小,可以将实体类型的所有配置提取到实现 IEntityTypeConfiguration<TEntity> 的单独类中。

public class BlogEntityTypeConfiguration : IEntityTypeConfiguration<Blog>
{
    public void Configure(EntityTypeBuilder<Blog> builder)
    {
        builder
            .Property(b => b.Url)
            .IsRequired();
    }
}

然后,只需从 OnModelCreating 调用 Configure 方法。

new BlogEntityTypeConfiguration().Configure(modelBuilder.Entity<Blog>());

应用程序集中的所有配置

可以在给定程序集中应用实现 IEntityTypeConfiguration 的类型中指定的所有配置。

modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogEntityTypeConfiguration).Assembly);

注意

应用配置的顺序是不确定的,因此仅当顺序不重要时才应使用此方法。

对实体类型使用 EntityTypeConfigurationAttribute

与其显式调用 Configure,不如改为在实体类型上放置 EntityTypeConfigurationAttribute,以便 EF Core 可以查找并使用适当的配置。 例如:

[EntityTypeConfiguration(typeof(BookConfiguration))]
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Isbn { get; set; }
}

此特性意味着,每当模型中包含 Book 实体类型时,EF Core 都将使用指定的 IEntityTypeConfiguration 实现。 实体类型包含在使用普通机制其中一种机制的模型中。 例如,通过为实体类型创建 DbSet<TEntity> 属性:

public class BooksContext : DbContext
{
    public DbSet<Book> Books { get; set; }

    //...

或者将其注册到 OnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>();
}

注意

程序集中不会自动发现 EntityTypeConfigurationAttribute 类型。 实体类型必须添加到模型中,然后才能在该实体类型上发现特性。

使用数据注释来配置模型

也可将某些特性(称为数据注释)应用于类和属性。 数据注释会替代约定,但会被 Fluent API 配置替代。

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace EFModeling.EntityProperties.DataAnnotations.Annotations;

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
}

[Table("Blogs")]
public class Blog
{
    public int BlogId { get; set; }

    [Required]
    public string Url { get; set; }
}

内置约定

EF Core 包括许多默认启用的模型生成约定。 可以在实现 IConvention 接口的类列表中找到所有这些约定。 但是,该列表不包括第三方数据库提供程序和插件引入的约定。

应用程序可以删除或替换这些约定中的任何一个,并添加新的自定义约定,这些约定可对 EF 未立即识别的模式应用配置。

提示

下面显示的代码来自

删除现有约定

有时,其中一个内置约定可能不适用于你的应用程序,在这种情况下,可以将其删除。

提示

如果模型不使用映射特性(又名数据注释)进行配置,则可以安全地删除名称以 AttributeConvention 结尾的所有约定,以加快模型生成速度。

示例:不要为外键列创建索引

通常,为外键 (FK) 列创建索引是有意义的,因此有一个内置约定:ForeignKeyIndexConvention。 查看与 BlogAuthor 有关系的 Post 实体类型的模型调试视图,可以看到创建了两个索引 - 一个用于 BlogId FK,另一个用于 AuthorId FK。

  EntityType: Post
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      AuthorId (no field, int?) Shadow FK Index
      BlogId (no field, int) Shadow Required FK Index
    Navigations:
      Author (Author) ToPrincipal Author Inverse: Posts
      Blog (Blog) ToPrincipal Blog Inverse: Posts
    Keys:
      Id PK
    Foreign keys:
      Post {'AuthorId'} -> Author {'Id'} ToDependent: Posts ToPrincipal: Author ClientSetNull
      Post {'BlogId'} -> Blog {'Id'} ToDependent: Posts ToPrincipal: Blog Cascade
    Indexes:
      AuthorId
      BlogId

但是,索引有开销,可能并不总是适合为所有 FK 列创建索引。 为此,可以在生成模型时删除 ForeignKeyIndexConvention

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}

现在,从 Post 模型的调试视图来看,我们发现尚未创建 FK 上的索引:

  EntityType: Post
    Properties:
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      AuthorId (no field, int?) Shadow FK
      BlogId (no field, int) Shadow Required FK
    Navigations:
      Author (Author) ToPrincipal Author Inverse: Posts
      Blog (Blog) ToPrincipal Blog Inverse: Posts
    Keys:
      Id PK
    Foreign keys:
      Post {'AuthorId'} -> Author {'Id'} ToDependent: Posts ToPrincipal: Author ClientSetNull
      Post {'BlogId'} -> Blog {'Id'} ToDependent: Posts ToPrincipal: Blog Cascade

如果需要,仍可使用 IndexAttributeOnModelCreating 中的配置为外键列显式创建索引。

调试视图

可以在 IDE 的调试器中访问模型生成器调试视图。 例如,在 Visual Studio 中:

Accessing the model builder debug view from the Visual Studio debugger

也可以直接通过代码访问它,例如,将调试视图发送到控制台:

Console.WriteLine(context.Model.ToDebugString());

调试视图具有短视图形式和长视图形式。 长视图形式还包括所有注释,如果需要查看关系元数据或特定于提供程序的元数据,这些注释可能很有用。 还可以从代码访问长视图:

Console.WriteLine(context.Model.ToDebugString(MetadataDebugStringOptions.LongDefault));