模型創建分為正向工程(CodeFirst)與反向工程(DbFirst). 正向工程的模型配置也可以創建任意的資料庫關係對象,如:欄位,欄位說明,表,索引,外鍵等等。 可在派生上下文中替代 OnModelCreating 方法,並使用 ModelBuilder API 來配置模型。 此配置方法最為有效 ...
正向工程的模型配置也可以創建任意的資料庫關係對象,如:欄位,欄位說明,表,索引,外鍵等等。
可在派生上下文中替代 OnModelCreating
方法,並使用 ModelBuilder API
來配置模型。 此配置方法最為有效,並可在不修改實體類的情況下指定配置。 Fluent API 配置具有最高優先順序,並將替代約定和數據註解(特性)。
由於FluentAPI編寫起來比較麻煩,實際開發中很少手寫。
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; } }
1. 分組配置
為了減小
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);
備註
應用配置的順序是不確定的,因此僅當順序不重要時才應使用此方法。
2. 表、屬性配置
1. 表配置
指定表名
fluent API
public class BlogEntityConfig:IEntityTypeConfiguration<Blog> { public void Configure(EntityTypeBuilder<Blog> builder) { builder.ToTable("blog"); } }
數據註解
[Table("blog")] public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
指定Schema
註意,SqlServer 是可以指定schema ,但是mysql中並不存在schema ,所以不能指定
fluentAPI:
public class BlogEntityConfig:IEntityTypeConfiguration<Blog> { public void Configure(EntityTypeBuilder<Blog> builder) { builder.ToTable("blog","renwoxing");// 只能在SQLServer下指定schema } }
數據註解
[Table("blogs", Schema = "blogging")] public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
也可以統一指定所有:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultSchema("renwoxing");// 預設的schema 為dbo modelBuilder.ApplyConfigurationsFromAssembly(typeof(Blog).Assembly); }
表註釋
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().ToTable( tableBuilder => tableBuilder.HasComment("博客")); }
數據註解
[Comment("博客")] public class Blog { public int BlogId { get; set; } public string Url { get; set; } }
排除表
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<Blog>(); }
數據註解
[NotMapped] public class BlogMetadata { public DateTime LoadedFromDatabase { get; set; } }
遷移中排除
EF Core 5.0 中引入了從遷移中排除表的功能。它可將相同的實體類型映射到多個 DbContext
類型中(也就是多個DbContext中都可以使對同一個實體類型進行操作)。
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<IdentityUser>() .ToTable("AspNetUsers", t => t.ExcludeFromMigrations()); }
此配置遷移不會創建 AspNetUsers
該表,但 IdentityUser
仍包含在模型中,並且可正常使用。
如果需要再次使用遷移來管理表,則應創建不包括 AspNetUsers
的新遷移。 下一次遷移將包含對錶所做的任何更改。
導航屬性表
按照約定,上下文的 DbSet 屬性中公開的類型作為實體包含在模型中。 還包括在 OnModelCreating
方法中指定的實體類型,以及通過遞歸探索其他發現的實體類型的導航屬性找到的任何類型。
下麵的代碼示例中包含了所有類型:
-
包含
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; }// 引用導航屬性,並自動創建外鍵BlogId ,<導航屬性名><主鍵屬性名> } public class AuditEntry { public int AuditEntryId { get; set; } public string Username { get; set; } public string Action { get; set; } }
2. 屬性配置
按照約定,所有具有 Getter 和 Setter 的公共屬性都將包含在模型中。
指定列名
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.BlogId) .HasColumnName("blog_id"); }
數據註解
public class Blog { [Column("blog_id")] public int BlogId { get; set; } public string Url { get; set; } }
數據類型
fluentAPI
例如,SQL Server 將 DateTime
屬性映射到 datetime2(7)
列,將 string
屬性映射到 nvarchar(max)
列(或對於用作鍵的屬性,映射到 nvarchar(450)
)。
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>( eb => { eb.Property(b => b.Url).HasColumnType("varchar(200)"); eb.Property(b => b.Rating).HasColumnType("decimal(5, 2)"); }); }
數據註解
public class Blog { public int BlogId { get; set; } [Column(TypeName = "varchar(200)")] public string Url { get; set; } [Column(TypeName = "decimal(5, 2)")] public decimal Rating { get; set; } }
最大長度
配置最大長度可向資料庫提供程式提供有關為給定屬性選擇適當列數據類型的提示。 最大長度僅適用於數組數據類型,如 string
和 byte[]
。
備註
在向提供程式傳遞數據之前,實體框架不會執行任何最大長度的驗證。 而是由提供程式或數據存儲根據情況進行驗證。 例如,當面向 SQL Server 時,超過最大長度將導致異常,因為基礎列的數據類型不允許存儲多餘的數據。
在下麵的示例中,將最大長度配置為 500 將導致在 SQL Server 上創建 nvarchar(500)
類型的列:
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) // mysql為varchar(500),sqlserver nvarchar(500) .HasMaxLength(500); }
數據註解
public class Blog { public int BlogId { get; set; } [MaxLength(500)] public string Url { get; set; } }
必需與可選
按照約定,其 .NET 類型可包含 null 的屬性將配置為可選屬性,而 .NET 類型不能包含 null 的屬性將配置為必需屬性。 例如,所有具有 .NET 值類型 (int``decimal
、bool
等) 的屬性都配置為必需,並且具有可為 null 的 .NET 值類型的所有屬性 (int?``decimal?
、bool?
等) 配置為可選。
C# 8 引入了一項名為
-
如果禁用可為 null 的引用類型,則使用 .NET 引用類型的所有屬性都按約定 ((例如
string
) )配置為可選。 -
如果啟用了可為 null 的引用類型,則基於屬性的 .NET 類型的 C# 為 Null 性來配置屬性:
string?
將配置為可選屬性,但string
將配置為必需屬性。
以下示例顯示了具有必需屬性和可選屬性的實體類型,禁用並啟用可為 null 的引用功能:
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); // 表示必須有值才能入庫,如果未加,則表示可選 }
數據註解
public class CustomerWithoutNullableReferenceTypes { public int Id { get; set; } [Required] public string FirstName { get; set; } [Required] public string LastName { get; set; } public string MiddleName { get; set; } // 可選非必填 }
列註釋
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasComment("博客地址"); }
數據註解
public class Blog { public int BlogId { get; set; } [Comment("博客地址")] public string Url { get; set; } }
3. 表約束、索引
1. 主鍵約束
預設主鍵
根據約定,名為 Id
或 <type name>Id
的屬性將被配置為實體的主鍵。
internal class Car { public string Id { get; set; } // 約定為主鍵 public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } } internal class Truck { public string TruckId { get; set; } // 約定為主鍵 public string Make { get; set; } public string Model { get; set; } }
指定主鍵
可將單個屬性配置為實體的主鍵,如下所示:
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => c.LicensePlate); }
數據註解
internal class Car { [Key] public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } }
聯合主鍵
還可將多個屬性配置為實體的鍵 - 這稱為組合鍵。
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => new { c.State, c.LicensePlate }); }
數據註解
屬性
[PrimaryKey]
是在 EF Core 7.0 中引入的。 在舊版本中使用 Fluent API。
[PrimaryKey(nameof(State), nameof(LicensePlate))] internal class Car { public string State { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } }
主鍵名稱
根據約定,在關係資料庫上,主鍵使用名稱 PK_<type name>
進行創建。也 可按如下方式配置主鍵約束的名稱:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasKey(b => b.BlogId) .HasName("PrimaryKey_BlogId");// 預設名稱是:pk_blog }
2. 值自動生成
預設值約束
在關係資料庫中,可以為列配置預設值;如果插入的行沒有該列的值,則將使用預設值。
可以在屬性上配置預設值:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Rating) .HasDefaultValue(3); }
還可以指定用於計算預設值的 SQL 片段:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Created) .HasDefaultValueSql("getdate()"); }
主鍵自增
按照約定,如果應用程式未提供值,則將類型為 short、int、long 或 Guid 的非複合主鍵設置為針對插入的實體自動生成值。
數據類型的主鍵值會設置為自增長。也可以使用如下語句取消自增長
modelBuilder.Entity<Blog>() .Property(b => b.BlogId).ValueGeneratedNever(); // 取消自動增長
也可以顯示配置值:
fluentAPI
// 添加時生成值 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Inserted) // .HasDefaultValue(DateTime.Now) // SqlServer下必須添加這句話 .ValueGeneratedOnAdd(); // 一般用於主鍵自增 }
數據註解
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public DateTime Inserted { get; set; } }
非主鍵值生成
同樣,可以將屬性配置為在添加或更新時生成其值:
fluentAPI
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.LastUpdated) .ValueGeneratedOnAddOrUpdate(); // mysql 下可以自動生成,SQLServer下不行 }
數據註解
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime LastUpdated { get; set; } }
警告
與預設值或計算列不同,我們沒有指定值的生成方式;這取決於所使用的資料庫提供程式。 資料庫提供程式可能會自動為某些屬性類型設置值生成,但其他屬性類型可能需要你手動設置值的生成方式。
例如,在 SQL Server 上,如果 GUID 屬性配置為在添加時生成值,提供程式會自動在客戶端執行值生成,並使用演算法生成最佳順序 GUID 值。 但是,在 DateTime 屬性上指定
一個常見的請求是讓資料庫列包含第一次插入列的日期/時間(添加時生成的值)或上次更新列的日期/時間(添加或更新時生成的值)。 由於可通過各種策略執行此操作,因此 EF Core 提供程式通常不會為日期/時間列自動設置值生成 - 你必須自行配置。
同樣,配置為在添加或更新時生成值並標記為併發標記的 byte[] 屬性將設置為 rowversion 數據類型,以便在資料庫中自動生成值。 但是,指定
3. 外鍵關係
預設情況下,如果在類型上發現導航屬性,將創建關係。 如果當前資料庫提供程式無法將屬性指向的類型映射為標量類型,則屬性被視為導航屬性。
有許多用於描述關係的術語
-
依賴實體:這是包含外鍵屬性的實體。 有時是指關係的“子級”。
-
主體實體:這是包含主鍵/備選鍵屬性的實體。 有時是指關係的“父級”。
-
主體鍵:唯一標識主體實體的屬性。 它可能是主鍵,也可能是備選鍵。
-
外鍵:依賴實體中用於存儲相關實體的主體鍵值的屬性。
-
導航屬性:在引用相關實體的主體實體和/或依賴實體上定義的屬性。
-
集合導航屬性:包含對許多相關實體的引用的導航屬性。
-
引用導航屬性:保留對單個相關實體的引用的導航屬性。
-
反嚮導航屬性:討論特定導航屬性時,此術語是指關係另一端上的導航屬性。
-
-
自引用關係:依賴實體類型與主體實體類型相同的關係。
以下代碼展示了 Blog
和 Post
之間的一對多關係
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 int BlogId { get; set; } public Blog Blog { get; set; } }
-
Post
是依賴實體 -
Blog
是主體實體 -
Blog.BlogId
是主體鍵(在此示例中,它是主鍵) -
Post.BlogId
是外鍵 -
Post.Blog
是引用導航屬性 -
Blog.Posts
是集合導航屬性 -
Post.Blog
是Blog.Posts
的反嚮導航屬性(反之亦然)
預設外鍵關係
關係的最常見模式是在關係的兩端定義導航屬性,併在依賴實體類中定義外鍵屬性。
-
如果在兩種類型之間找到了一對導航屬性,則它們將被配置為相同關係的反嚮導航屬性。
-
如果依賴實體包含一個名稱與以下模式之一匹配的屬性,則該屬性將被配置為外鍵:
-
<navigation property name><principal key property name> <navigation property name>Id <principal entity name><principal key property name> <principal entity name>Id
-
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 int BlogId { get; set; } public Blog Blog { get; set; } // 下麵這種情況,BlogEntityId 也會生成外鍵 //public int BlogEntityId { get; set; } //public Blog? BlogEntity { get; set; } }
本示例將使用突出顯示的屬性來配置關係。
備註
如果屬性是主鍵或屬性的類型與主體鍵不相容,則不會將其配置為外鍵。