3

EF Core 配置模型 - 芦荟柚子茶

 2 years ago
source link: https://www.cnblogs.com/clis/p/16267747.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文的第一节,会概述配置模型的作用(对数据模型的补充描述)。

第二节描述两种配置方式,即:数据注释(data annotations)和 Fluent API 方式。

第三节开始,主要是将常用的配置记录下来,以便翻查。

数据实体(Entity)的类名、属性等,称之为约定(conventions),约定主要是为了定义数据模型(Model)的形状。

但是光靠约定可能不足以完整描述数据模型,有时我们的数据模型与我们的数据实体可能也有差异,这时,就可以通过数据注释(data annotations)和 Fluent API 补充,具体请参考EF Core官方文档:创建并配置模型

2 配置方式

2.1 数据注释(data annotations)

直接在数据实体上打上对应的标签,如下例子中,标识表名为 Blogs,Url 属性不能为 null

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

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

注意:数据注释的方式的优先级高于约定(conventions)但低于 Fluent API,即数据注释的方式会被 Fluent API 覆盖。

2.2 Fluent API

对描述数据模型(Model)具有最高优先级。

通过在派生的 DbContext 中,重写 OnModelCreating 方法,并使用 ModelBuilder API 来配置模型。

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

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 写法1:链式配置
        modelBuilder.Entity<Blog>()
            .ToTable("Blogs")
            .Property(b => b.Url).IsRequired();
        
        // 写法2:委托函数配置
        modelBuilder.Entity<Post>(eb => 
        {
            eb.ToTable("Posts");
            eb.Property(b => b.Title).IsRequired();
        });
    }
}

2.2.1 分组配置

可以实现类似于批量配置,具体请参考分组配置

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

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

3 配置数据模型

3.1 在模型中包含类型

在上下文中包含于 DbSet 的类意味着它包含在 EF Core 的模型中;我们通常将这些类称为实体。 EF Core 可以向数据库中读写实体实例,如果使用的是关系数据库,EF Core 可以通过迁移为实体创建表。

3.1.1 迁移时,创建表的情况

使用 EF Core 添加迁移时,哪些实体会被创建表呢?包含以下三种情况:

  1. 在 DbContext 的 DbSet 属性中公开的实体类
  2. 在 DbContext 的 OnModelCreating 方法中指定的实体类
  3. 以上两种情况的实体类内,通过递归探索导航属性发现的实体类

下面是一个官方示例:

下面的代码示例中,数据模型中包含的实体类有:

  • 包含 Blog,因为它在上下文的 DbSet 属性中公开。
  • 包含 Post,因为它是通过 Blog.Posts 导航属性发现的。
  • 包含 AuditEntry因为它是 OnModelCreating 中指定的。
internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

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

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

    public List<Post> Posts { get; set; } // 导航属性
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

public class AuditEntry
{
    public int AuditEntryId { get; set; }
    public string Username { get; set; }
    public string Action { get; set; }
}

3.2 配置实体类型(Entity types)

3.2.1 数据注释(data annotations)

//从模型(model)中排除类型(class)
[NotMapped]

//指定表名称
[Table("blogs")]
//指定架构(scheme)
[Table("blogs", Schema = "blogging")]

//表注释
[Comment("Blogs managed on the website")]

3.2.2 Fluent API

//从模型(model)中排除类型(class)
modelBuilder.Ignore<BlogMetadata>();
//从迁移中排除,生成迁移将不会包含 表AspNetUsers,但 IdentityUser 仍在模型中
modelBuilder.Entity<IdentityUser>().ToTable("AspNetUsers", t => t.ExcludeFromMigrations());

//指定表名称
modelBuilder.Entity<Blog>().ToTable("blogs");
//指定架构(scheme)
modelBuilder.Entity<Blog>().ToTable("blogs", schema: "blogging");
//通用配置:默认架构名
modelBuilder.HasDefaultSchema("blogging");

//视图映射
//映射到视图将删除默认表映射,但从 EF 5.0 开始,实体类型也可以显式映射到表。 在这种情况下,查询映射将用于查询,表映射将用于更新。
modelBuilder.Entity<Blog>().ToView("blogsView", schema: "blogging");

//表注释
modelBuilder.Entity<Blog>().HasComment("Blogs managed on the website");

//表值函数映射
modelBuilder.Entity<BlogWithMultiplePosts>().HasNoKey().ToFunction("BlogsWithMultiplePosts");

//共享类型实体(Shared-type entity types)
//不理解
//https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-types?tabs=data-annotations#shared-type-entity-types

3.3 配置实体属性(Entity properties)

3.3.1 数据注释(data annotations)

//排除属性
[NotMapped]
//备注
[Comment("The URL of the blog")]

[Column("blog_id")]
[Column(TypeName = "varchar(200)")]
[MaxLength(500)]

//精度和小数位
[Precision(14, 2)]
public decimal Score { get; set; }
[Precision(3)]
public DateTime LastUpdated { get; set; }

//nvarchar 表示 Unicode 数据,varchar 表示非 Unicode 数据
[Unicode(false)]
[Required]

//列排序
//默认情况下,在使用迁移创建表时,EF Core 首先为主键列排序,然后为实体类型和从属类型的属性排序,最后为基类型中的属性排序。(顺序:主键 >> 属性 >> 基类属性)
//在一般情况下,大多数数据库仅支持在创建表时对列进行排序。 这意味着不能使用列顺序特性对现有表中的列进行重新排序。
[Column(Order = 0)]
[Column(Order = 1)]

3.3.2 Fluent API

//排除属性
modelBuilder.Entity<Blog>().Ignore(b => b.LoadedFromDatabase);
//备注
modelBuilder.Entity<Blog>().Property(b => b.Url).HasComment("The URL of the blog");

modelBuilder.Entity<Blog>().Property(b => b.BlogId).HasColumnName("blog_id");
modelBuilder.Entity<Blog>().Property(b => b.Url).HasColumnType("varchar(200)");
modelBuilder.Entity<Blog>().Property(b => b.Url).HasMaxLength(500);

//精度和小数位
modelBuilder.Entity<Blog>().Property(b => b.Score).HasPrecision(14, 2);
modelBuilder.Entity<Blog>().Property(b => b.LastUpdated).HasPrecision(3);

//nvarchar 表示 Unicode 数据,varchar 表示非 Unicode 数据
modelBuilder.Entity<Book>().Property(b => b.Isbn).IsUnicode(false);
modelBuilder.Entity<Blog>().Property(b => b.Url).IsRequired();

//可以定义文本列的排序规则,以确定如何比较和排序。 
//排序规则:https://docs.microsoft.com/zh-cn/ef/core/miscellaneous/collations-and-case-sensitivity
//例如,以下代码片段将 SQL Server 列配置为不区分大小写
modelBuilder.Entity<Customer>().Property(c => c.Name)
    .UseCollation("SQL_Latin1_General_CP1_CI_AS");

modelBuilder.Entity<Employee>().Property(b => b.Id).HasColumnOrder(0);
modelBuilder.Entity<Employee>().Property(b => b.FirstName).HasColumnOrder(1);

3.4 配置主键、外键、索引

3.4.1 数据注释

//主键
[Key]
//无主键
[Keyless]

//外键
[ForeignKey]
//反向属性:https://docs.microsoft.com/zh-cn/ef/core/modeling/relationships?tabs=data-annotations%2Cfluent-api-simple-key%2Csimple-key#manual-configuration
[InverseProperty("Author")]

//索引
[Index(nameof(Url))]
public class Blog 
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
//复合索引
[Index(nameof(FirstName), nameof(LastName))]
//唯一索引
[Index(nameof(Url), IsUnique = true)]
//索引名称
[Index(nameof(Url), Name = "Index_Url")]

3.4.2 Fluent API

//主键
modelBuilder.Entity<Car>().HasKey(c => c.LicensePlate);
//复合主键
modelBuilder.Entity<Car>().HasKey(c => new { c.State, c.LicensePlate });
//无主键
modelBuilder.Entity<BlogPostsCount>().HasNoKey();

//备选键:https://docs.microsoft.com/zh-cn/ef/core/modeling/keys?tabs=fluent-api#alternate-keys
//备选键 HasPrincipalKey
modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogUrl)
            .HasPrincipalKey(b => b.Url);
//将单个属性配置为备选键
modelBuilder.Entity<Car>()
        .HasAlternateKey(c => c.LicensePlate);
//复合备选键
modelBuilder.Entity<Car>()
        .HasAlternateKey(c => new { c.State, c.LicensePlate });
//配置备选键的索引和唯一约束的名称
modelBuilder.Entity<Car>()
        .HasAlternateKey(c => c.LicensePlate)
        .HasName("AlternateKey_LicensePlate");

//配置关系
modelBuilder.Entity<Post>()
    		.HasOne(p => p.Blog)
    		.WithMany(b => b.Posts);
//配置外键
modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogForeignKey);
//配置外键约束的名称
modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .HasForeignKey(p => p.BlogId)
        .HasConstraintName("ForeignKey_Post_Blog");
//关系:https://docs.microsoft.com/zh-cn/ef/core/modeling/relationships

//索引
modelBuilder.Entity<Blog>().HasIndex(b => b.Url);
modelBuilder.Entity<Person>().HasIndex(p => new { p.FirstName, p.LastName });
//唯一索引
modelBuilder.Entity<Blog>().HasIndex(b => b.Url).IsUnique();
//索引名称
modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasDatabaseName("Index_Url");
//索引筛选器:https://docs.microsoft.com/zh-cn/ef/core/modeling/indexes?tabs=fluent-api#index-filter
modelBuilder.Entity<Blog>().HasIndex(b => b.Url).HasFilter("[Url] IS NOT NULL");
//包含列:SQL Server 的 Include 关键字
modelBuilder.Entity<Post>().HasIndex(p => p.Url)
        .IncludeProperties(p => new { p.Title, p.PublishedOn });
//检查约束
modelBuilder.Entity<Product>().HasCheckConstraint("CK_Prices", "[Price] > [DiscountedPrice]", c => c.HasName("CK_Product_Prices"));

3.5 值转换(value conversions)

3.5.1 基本配置

假设将一个枚举和实体类型定义为:

public class Rider
{
    public int Id { get; set; }
    public EquineBeast Mount { get; set; }
}

public enum EquineBeast
{
    Donkey,
    Mule,
    Horse,
    Unicorn
}

可以将枚举值(如字符串 "Donkey"、"Mule"等)存储在数据库中。

需要配置两个函数:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(
            v => v.ToString(),
            v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}

3.5.2 批量配置

public class CurrencyConverter : ValueConverter<Currency, decimal>
{
    public CurrencyConverter()
        : base(
            v => v.Amount,
            v => new Currency(v))
    {
    }
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
        .Properties<Currency>()
        .HaveConversion<CurrencyConverter>();
}

3.5.3 ValueConverter 类

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var converter = new ValueConverter<EquineBeast, string>(
        v => v.ToString(),
        v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));

    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion(converter);
}

3.5.4 内置转换器

下面是一个示例,更多的请翻查官方文档:内置转换器

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Rider>()
        .Property(e => e.Mount)
        .HasConversion<string>();
}

4 数据种子(data seeding)

OnModelCreating 中配置种子数据:

modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });

匿名对象:

modelBuilder.Entity<Post>().HasData(
    new { BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2" });
modelBuilder.Entity<Post>().OwnsOne(p => p.AuthorName).HasData(
    new { PostId = 1, First = "Andriy", Last = "Svyryd" },
    new { PostId = 2, First = "Diego", Last = "Vega" });

EF Core官方文档:创建模型


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK