创建和配置模型

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

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

小窍门

可以在 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();
    }
}

然后,只需从OnModelCreatingConfigure调用该方法即可。

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

应用程序集中的所有配置

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

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

注释

将应用配置的顺序是未定义的,因此,仅当顺序无关紧要时,才应使用此方法。

使用 EntityTypeConfigurationAttribute 于实体类型上

可以选择将EntityTypeConfigurationAttribute放置在实体类型上,让 EF Core 能够查找和使用适当的配置,而不是显式调用Configure。 例如:

[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 现成无法识别的模式。

小窍门

下面显示的代码来自 ModelBuildingConventionsSample.cs

删除现有约定

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

小窍门

如果模型不使用映射属性(即数据注释)进行配置,则可以安全地删除名称结尾 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

如果需要,仍可以通过使用IndexAttribute或在OnModelCreating中进行配置,为外键列显式创建索引。

调试视图

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

从 Visual Studio 调试器访问模型生成器调试视图

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

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

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

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