一.概述 EF實體關係定義了兩個實體互相關聯起來(主體實體和依賴實體的關係,對應資料庫中主表和子表關係)。 在關係型資料庫中,這種表示是通過外鍵約束來體現。本篇主要講一對多的關係。先瞭解下描述關係的術語。 (1) 依賴實體: 這是包含外鍵屬性的實體(子表)。有時稱為 child 。 (2) 主體實體 ...
一.概述
EF實體關係定義了兩個實體互相關聯起來(主體實體和依賴實體的關係,對應資料庫中主表和子表關係)。 在關係型資料庫中,這種表示是通過外鍵約束來體現。本篇主要講一對多的關係。先瞭解下描述關係的術語。
(1) 依賴實體: 這是包含外鍵屬性的實體(子表)。有時稱為 child 。
(2) 主體實體: 這是包含主/備用鍵屬性的實體(主表)。 有時稱為 parent。
(3) 外鍵:依賴實體(子表)中的屬性,用於存儲主表的主鍵屬性的值。
(4) 主鍵: 唯一標識的主體實體(主表)的屬性。 這可能是 primary key 或備用鍵。
(5) 導航屬性: 包含對相關實體引用,在的主體和或依賴實體上定義的屬性。
集合導航屬性: 一個導航屬性,對多個相關實體的引用。
引用導航屬性: 一個導航屬性,對單個相關實體的引用。
下麵示例代碼來說明Blog和Post之間的一對多關係。其中 Post
是依賴實體;Blog
是主體實體;Post.BlogId
是外鍵;Blog.BlogId
是主鍵;Post.Blog引用導航屬性;Blog.Posts集合導航屬性。
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; } }
二.約定
按照約定,當在實體類型上發現導航屬性時,將創建關係。如果屬性所指向的類型不能被當前資料庫提供程式映射為標量類型,則將其視為導航屬性。下麵用三個示例來說明通過約定創建的主從實體關係。
2.1 完全定義的關係
關係最常見的模式是在關係的兩端定義導航屬性,併在依賴實體類中定義外鍵屬性。
(1)如果兩個類型之間找到一對導航屬性,則它們將被配置為同一關係的反轉導航屬性。
(2)如果依賴實體包含名為的屬性<primary key property name>
, <navigation property name><primary key property name>
,或<principal entity name><primary key property name>
然後它將配置為外鍵。
下麵代碼示例是完全定義的關係的實體,通過約定來創建主從實體關係。
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; } //通過約定(<primary key property name>)創建外鍵 public int BlogId { get; set; } //反轉導航屬性 public Blog Blog { get; set; } }
使用EF基於數據模型(Blog和Post實體)創建資料庫。生成後,查看數據表的關係映射,如下圖所示:
2.2 沒有外鍵屬性
雖然建議在依賴實體類中定義外鍵屬性,但這不是必需的。如果沒有找到外鍵屬性,那麼將引入一個名為<navigation property name> > principal key property name>的隱藏外鍵屬性(上篇有介紹)。在下麵代碼中,依賴實體Post中沒有顯示定義外鍵BlogId。
public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; } }
2.3 單一導航屬性
僅包含一個導航屬性(沒有反轉導航屬性,也沒有外鍵屬性)就足以擁有約定定義的關係。還可以有一個導航屬性和一個外鍵屬性。代碼如下所示產:
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; } }
三. 數據註釋
有兩個數據註釋可用於配置關係,[ForeignKey]和[InverseProperty]。
3.1 ForeignKey可以將指定屬性設置為外鍵屬性。 這種設置通常是外鍵屬性不被約定發現時,顯示設置。如下麵代碼示例, 在依賴實體Post中將BlogForeignKey指定為外鍵。代碼中生成的主從實體關係與上面的約定示例是一樣的。
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 BlogForeignKey { get; set; } [ForeignKey("BlogForeignKey")] public Blog Blog { get; set; } }
下麵使用EF基於數據模型(Blog和Post實體)創建資料庫。生成後,查看數據表的關係映射,如下圖所示:
3.2 InverseProperty配置依賴實體和主體實體上的導航屬性如何配對。當兩個實體類型之間有一對以上的導航屬性時,通常會這樣做。如下麵代碼示例:
public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; } public User Author { get; set; } public User Contributor { get; set; } } public class User { public string UserId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [InverseProperty("Author")] public List<Post> AuthoredPosts { get; set; } [InverseProperty("Contributor")] public List<Post> ContributedToPosts { get; set; } }
下麵使用EF基於數據模型(User和Post實體)創建資料庫。生成後,查看數據表的關係映射,如下圖所示:
四. Fluent API
要在Fluent API中配置關係,首先要確定組成關係的導航屬性。HasOne或HasMany標識開始配置的實體類型上的導航屬性。然後調用WithOne
或WithMany
來標識反導航。HasOne/WithOne用於引用導航屬性,而HasMany/WithMany用於集合導航屬性。
在EF基於現有資料庫進行反向工程時,根據資料庫將自動生成DbContext上下文類,裡面重寫了OnConfiguring方法。下麵示例是一個MyContext上下文類,在OnModelCreating方法中確定了實體的關係。
4.1 完全定義的關係
class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasOne(p => p.Blog) //Post類有一個Blog引用導航屬性 .WithMany(b => b.Posts);//Blog類有一個Posts反導航集合 } } 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; } }
4.2 單一導航屬性
如果只有一個導航屬性,那麼就會出現無參數重載的WithOne以及WithMany。這表明在關係的另一端有一個概念上的引用或集合,但是實體類中不包含導航屬性。
class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .HasMany(b => b.Posts)//blog類有一個集合導航屬性 .WithOne(); } } 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; } }
下麵使用EF基於數據模型(User和Post實體)創建資料庫。生成後,查看數據表的關係映射,如下圖所示:
4.3 外鍵
可以使用 Fluent API 配置哪些屬性應用作給定關係外鍵屬性。
class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>() .HasOne(p => p.Blog) .WithMany(b => b.Posts) .HasForeignKey(p => p.BlogForeignKey); } } 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 BlogForeignKey { get; set; } public Blog Blog { get; set; } }
以下代碼列表演示如何配置複合外鍵。
class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>() .HasKey(c => new { c.State, c.LicensePlate }); modelBuilder.Entity<RecordOfSale>() .HasOne(s => s.Car) .WithMany(c => c.SaleHistory) .HasForeignKey(s => new { s.CarState, s.CarLicensePlate }); } } public class Car { public string State { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } public List<RecordOfSale> SaleHistory { get; set; } } public class RecordOfSale { public int RecordOfSaleId { get; set; } public DateTime DateSold { get; set; } public decimal Price { get; set; } public string CarState { get; set; } public string CarLicensePlate { get; set; } public Car Car { get; set; } }
總結:關於實體關係,還講到了“必需和可選的關係”、“級聯刪除”。以及關係模式中的“一對一關係”、“多對多關係",這些以後用到再參考文檔。 個人認為在傳統開發中,以建庫建表優先的情況下,不會去設置數據表的外鍵關係,這種關係是由編程去控制。 這樣對資料庫進行反向工程時,也不會生成有關係的主從實體模型。
參考文獻:
官方文檔:EF 實體關係