• EF Core 配置模型


    0 前言

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

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

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


    1 概述

    数据实体(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官方文档:创建模型

  • 相关阅读:
    Wireshark过滤器语法
    【头歌C语言程序设计】指针及其应用
    2023蓝帽杯半决赛电子取证+CTF部分题解
    学习笔记 MySQL面试题积累(重要) 【实时更新】
    Linxu Java环境配置Tomcat离线安装与启动
    react09 hooks(useState)
    如何将html转化为pdf
    2022最新总结一线大厂Java八股文合集,堪称历史最强
    Unity3D ugui获取ui控件屏幕坐标
    Webmin -- Sheduled Commands
  • 原文地址:https://www.cnblogs.com/clis/p/16267747.html