本文主要學習是類之間的關聯是如何映射到數據庫中形成表與表間的關系的。這種關系包括 一對多,多對多,一對一。
多重關系
Code First在處理多重性關系時應用了一系列規則。規則使用導航屬性確定多重性關系。即可以是一對導航屬性互相指定(雙向關系),也可以是單個導航屬性(單向關系)。
1、如果你的類中包含一個引用和一個集合導航屬性,Code First視為一對多關系;
2、如果你的類中僅在單邊包含導航屬性(即要么是集合要么是引用,只有一種),Code First也將其視為一對多關系;
3、如果你的類包含兩個集合屬性,Code First默認會使用多對多關系;
4、如果你的類包含兩個引用屬性,Code First會視為一對一關系;在一對一關系中,你需要提供附加信息以使Code First獲知何為主何為輔。如果沒有在類中定義外鍵屬性,Code First將設定關系為可選。
大多數多重關系配置都需要使用Fluent API。但可以使用Data Annotations來指定一些關系是必須的。只需要簡單地將 [Required] 標記放在你需要定義為必須項的引用屬性上。比如:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public byte[] Photo { get; set; } public string Introduce { get; set; } public List<Book> MyBooks { get; set; } } public class Book { public int BookId { get; set; }
//外鍵 //public int AuthorId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } [Required] public Author Author { get; set; } }
但是 Fluent API 則不同,為了達到相同的目的,必須先確定關系。有時在一端就足夠,但更多的需要對全部關系進行描述。
為了確定關系,你必須指明導航屬性。不管從哪端開始,都要使用這樣的代碼模板:
Entity.Has[Multiplicity](Property).With[Multiplicity](Property)
多重性關系可以是 Optional(一個屬性可擁有一個單個實例或沒有),Required(一個屬性必須擁有一個單個實例),Many很多的(一個屬性可以擁有一個集合或一個單個實例)。
Has方法包括如下幾個:
• HasOptional
• HasRequired
• HasMany
在多數情況還需要在Has方法后面跟隨如下With方法之一:
• WithOptional
• WithRequired
• WithMany
用FluntAPI來實現上面的關系代碼如下:
modelBuilder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.MyBooks)
注意:這里只是指定了兩個類間的關系,並沒有指定外鍵,如果打開數據庫會發現Book表已經生成了外鍵,其實這是由於,我們指定了關系后,codefirst根據默認配置自動給生成的
下面來總結一下以上這三種關系是如何在開發中使用的
一對多關系:
通過前面的學習我們已經知道了一對多的關系,如第一篇的筆記中,Author類有個集合導航屬性List<Book>, 而Book 類中還定義了一個對Author的屬性引用,因此 Code First可以確定 Book 與 Author 具有依賴關系,這時候EF會自動識別關系為我們創建外鍵關聯。
外鍵:
可以在類中,增加一個符合命名規范的字段,充當外鍵,Code First會將的該屬性設置為外鍵,不再自動創建一個外鍵。
默認配置:
命名規范:[目標類型的鍵名] 或 [目標類型名稱]+[目標類型鍵名稱] 或 [引用屬性名稱]+[目標類型鍵名稱] 的形式,如上面代碼中,在這里目標類型就是Author,相對應的命名就是:AuthorId,AuthorAuthorId,TargetAuthorId
public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } //外鍵 public int TargetAuthorId { get; set; } public Author Target { get; set; } }
我們也可以使用不符合規范的命名但需要進行手動配置:
DataAnnotation 方式:
//外鍵(參數為屬性名稱) [ForeignKey("Author")] public int FkAuthorId { get; set; } public Author Author { get; set; }
或者
public int FkAuthorId { get; set; } //外鍵 [ForeignKey("FkAuthorId")] public Author Author { get; set; }
注意:[ForeignKey] 的位置不同,后面所帶的參數是不同的
FluntAPI 方式:
modelBuilder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.MyBooks).HasForeignKey(b => b.AuthorFk);
使用逆導航屬性:
書中還提到了一個逆向屬性,這個屬性主要是應用在一個實體被多次引用的情況下。比如Book修改一下,引用兩個Author類,分別為 主要作者,參與作者,Author類中也進行修改,變成兩個集合導航屬性 主要著作和參與著作,代碼如下:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public byte[] Photo { get; set; } public string Introduce { get; set; } //主要著作 public List<Book> PrimaryBooks { get; set; } //參與著作 public List<Book> SecondaryBooks { get; set; } } public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } //主要作者 public Author Primary { get; set; } //參與作者 public Author Secondary { get; set; } }
這時候,我們運行代碼,會發現生成的Book 表如下:
竟然生成了4個外鍵,這就是因為有兩套類型一樣的導航屬性與引用屬性,Code First無法確定它們之間的對應關系,就單獨為每個屬性都創建了一個關系。這種情況我就可以通過逆向屬性來解決。
DataAnnotation 方式:
public class Author { public int AuthorId { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public byte[] Photo { get; set; } public string Introduce { get; set; } //主要著作 public List<Book> PrimaryBooks { get; set; } //參與著作 public List<Book> SecondaryBooks { get; set; } } public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } //主要作者 [InverseProperty("PrimaryBooks")] public Author Primary { get; set; } //參與作者 [InverseProperty("SecondaryBooks")] public Author Secondary { get; set; } }
FluntAPI 方式:
modelBuilder.Entity<Book>().HasOptional(b => b.Primary).WithMany(a => a.PrimaryBooks);
modelBuilder.Entity<Book>().HasOptional(b => b.Secondary).WithMany(a=>a.SecondaryBooks);
生成外鍵如下圖:
注意:雖然這種結果是我們想要的,但是是codefirst 自動給生成的,如何自定義這個外鍵名稱,未找到方法。
級聯刪除:
級聯刪除允許主記錄被刪除時相關聯的依賴性數據也被刪除,比如上面的代碼,當我們刪除某一個Author的時候,與之相關聯的Book中的記錄也會被刪除。
默認規則約定,Code First會對Required的關系設置級聯刪除。當一個級聯刪除定義后,Code First會在數據庫中為其創建級聯刪除。
級聯刪除的功能是否啟用(默認啟用),只能通過FluntAPI 方式設置:
//關閉級聯刪除 modelBuilder.Entity<Book>().HasRequired(b => b.Author).WithMany(a => a.MyBooks).HasForeignKey(b => b.AuthorFk).WillCascadeOnDelete(false);
多對多關系:
EF框架也支持多對多關系,如果兩個類分別包含對方的集合導航屬性,則認為他們是多對多關系。修改上面的代碼,在增加一個Reader類和Book類形成多對多的關系:
public class Book { public int BookId { get; set; } public string BookName { get; set; } public decimal Price { get; set; } public bool IsSellFast { get; set; } public int AuthorFk { get; set; } public Author Author { get; set; } public List<Reader> Readers { get; set; } } public class Reader { public int ReaderId { get; set; } public string ReaderName { get; set; } public List<Book> Books { get; set; } }
來看一下采用默認方式,生成的數據庫,我們會發現 自動增加了一個多對多的中間表 ReaderBooks,表中鍵默認命名為:"[目標類型名稱]_[目標類型鍵名稱]"
我們可以通過FluntAPI 對 多對多的關系進行配置:
modelBuilder.Entity<Book>().HasMany(b => b.Readers).WithMany(r => r.Books).Map(m => { m.ToTable("t_ReaderToBook"); m.MapLeftKey("BookId"); m.MapRightKey("ReaderId"); });
或者
modelBuilder.Entity<Reader>().HasMany(r => r.Books).WithMany(b => b.Readers).Map(m => { m.ToTable("t_ReaderToBook"); m.MapLeftKey("ReaderId"); m.MapRightKey("BookId"); });
再次查看數據庫:
一對一關系:
有一種關系Code First必須進行配置后才能工作,這種關系就是一對一關系。當你在模型中定義一對一關系,需要在每個類中都要使用引用導航。來看一下書中的例子:
public class Person { public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; }public PersonPhoto Photo { get; set; } } public class PersonPhoto { public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
運行代碼,但程序會發生錯誤:因為Code First無法確認哪個是依賴類,必須使用Fluent API或Data Annotations進行顯示配置。
DataAnnotation方式:
[Table("t_Persons")] public class Person { public int PersonId { get; set; } public int SocialSecurityNumber { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public PersonPhoto Photo { get; set; } } public class PersonPhoto { [Key, ForeignKey("PhotoOf")] public int PersonId { get; set; } public byte[] Photo { get; set; } public string Caption { get; set; } public Person PhotoOf { get; set; } }
FluntAPI 方式:
modelBuilder.Entity<PersonPhoto>().HasKey(p => p.PersonId);
modelBuilder.Entity<PersonPhoto>().HasRequired(p => p.PhotoOf).WithOptional(p => p.Photo);