《Entity Framework 6 Recipes》中文翻譯系列 (6) -----第二章 實體數據建模基礎之使用Code First建模自引用關系


2-5 使用Code First建模自引用關系

問題

  你的數據庫中一張自引用的表,你想使用Code First 將其建模成一個包含自關聯的實體。

解決方案

  我們假設你有如圖2-14所示的數據庫關系圖的自引用表。

圖2-14 一張自引用表

  按下面的步驟為這張自引用的表及關系建模:

    1、在項目中創建一個繼承至DbContext上下文的類EF6RecipesContext。

    2、使用代碼清單2-5創建一個PictureCategoryPOCO(簡單CLR對象)實體。

      代碼單清2-5 創建一個POCO實體 PictureCategory

 1   [Table("PictureCategory",Schema="Chapter2")]   //書中沒有這一句,示例會把數據保存到dbo.PictureCategory表里,
                                //而不是Chapter2.PictureCategory里,以致不少朋友認為沒有保存成功,加上這一句就不會有問題了
    public class PictureCategory { 2 [Key] 3 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 4 public int CategoryId { get; private set; } 5 public string Name { get; set; } 6 public int? ParentCategoryId { get; private set; } 7 [ForeignKey("ParentCategoryId")] 8 public virtual PictureCategory ParentCategory { get; set; } //書中沒有virtual關鍵字,這會導致導航屬性不能加載,后面的輸出就只有根目錄!! 9 public virtual List<PictureCategory> Subcategories { get; set; } 10 public PictureCategory() { 11 Subcategories = new List<PictureCategory>(); 12 } 13 }

    3、在創建的上下文對象EF6RecipesContext中添加一個DbSet<PictureCategory>屬性。

    4、在EF6RecipesContext中重寫方法OnModelCreating配置雙向關聯(ParentCategory 和 SubCategories),如代碼清單2-6所示。

      代理清單2-6 重寫方法OnModelCreating

 

 1 public class EF6RecipesContext : DbContext {
 2         public DbSet<PictureCategory> PictureCategories { get; set; }
 3         public EF6RecipesContext()   //原文這里有誤,被寫成PictureContext()
 4             : base("name=EF6CodeFirstRecipesContext") {
 5         }
 6         protected override void OnModelCreating(DbModelBuilder modelBuilder) {
 7             base.OnModelCreating(modelBuilder);
 8             modelBuilder.Entity<PictureCategory>()
 9             .HasMany(cat => cat.SubCategories)
10             .WithOptional(cat => cat.ParentCategory);
11         }
12     }

    

原理
  數據庫的關系有以下特征:維度(degree)、多重性(multiplicity)以及方向(derection)。維度是指關系中的實體(表)的數量。一維和二維關系是常見的。三維和N維(n-Place)關系只存在於理論上。

  多重性,是指表示關系的線段兩端的實體類型(譯注:這里應該是指表,因為實體類型用於模型中)數量。你可能已經看到這樣的多重性表示,0...1(零或者一),1(一)和*(很多)。

  最后,方向可以是雙向,也可以是單向。

  實體數據模型支持當前流行數據庫的數據庫關系,它通過一個名為關聯的類型來表示。一個關聯類型可以是一維或者二維的,多重性可以是0...1,1和*,方向是雙向的。

  示例中的維度是一維(只涉及PictureCategory實體),多重性是0...1和*,方向當然是雙向。

  示例中的情況,自引用表一般指父子關系,每個父親有多個孩子,同時,一個孩子只有一個父親。因為父親這端的關系多重性是0...1而不是1.這對於孩子來說意味着它可能沒有父親。這正好可以被利用來表示根節點。一個沒有父親的節點,它是整個繼承層次的頂端。

  代碼清單2-7演示,通過遞歸從根節點開始枚舉圖片目錄。當然根節點是一個沒有父親的節點。

 1         static void RunExample() {
 2             using (var context = new EF6RecipesContext()) {
 3                 var louvre = new PictureCategory { Name = "Louvre" };
 4                 var child = new PictureCategory { Name = "Egyptian Antiquites" };
 5                 louvre.Subcategories.Add(child);
 6                 child = new PictureCategory { Name = "Sculptures" };
 7                 louvre.Subcategories.Add(child);
 8                 child = new PictureCategory { Name = "Paintings" };
 9                 louvre.Subcategories.Add(child);
10                 var paris = new PictureCategory { Name = "Paris" };
11                 paris.Subcategories.Add(louvre);
12                 var vacation = new PictureCategory { Name = "Summer Vacation" };
13                 vacation.Subcategories.Add(paris);
14                 context.PictureCategories.Add(paris);
15                 context.SaveChanges();
16             }
17             using (var context = new EF6RecipesContext()) {
18                 var roots = context.PictureCategories.Where(c => c.ParentCategory == null);
19                 roots.ForEach(root => Print(root, 0));              }
21         }
22         static void Print(PictureCategory cat, int level) {
23             StringBuilder sb = new StringBuilder();
24             Console.WriteLine("{0}{1}", sb.Append(' ', level).ToString(), cat.Name);
25             cat.Subcategories.ForEach(child => Print(child, level + 1));
26         }

   代碼清單2-7輸出顯示,根結點為Summer Vacation. 它的第一個(只有一個)孩子是Paris。 Paris有孩子Louver。最后,我在Louver照片目錄訪問到了目錄集合。

Summer Vacation
    Paris
        Louvre
            Egyptian Antiquities
            Sculptures
            Paintings

 

  顯然,代碼稍微有點點復雜了,我們最開始創建並初始化多個實體類型的實例,通過將這些照片目錄一起增加到目錄louver來將它們添加進對象圖,然后中我們將louver目錄添加到paris目錄,最后我們將paris目錄添加到summer vacation目錄。我們從下到上構建了整個繼承體系。

  一旦調用SaveChange()方法,所有的目錄都將插入到數據庫,我可以查詢表中的數據,看是不是所有的行都被正確地插入了。

  對於獲取部分代碼,我們最開始獲取一個根實體,它是一個沒有父親的目錄,在例中,我們創建了一個summer vacation實體,但並沒有把它設置成任何實體的孩子。這讓它成為整個繼承體系的根節點。

  現在,從根節點開始,我們調用另一個我們編寫的方法:Print(),Print()方法接受一對參數,第一個參數是一個PicturCategory實例對象,第二個參數是一個在繼承體系中表示層級或深度的整型。對於根目錄,summer vacation,它在繼承體系的頂端,我們傳0給print()方法. 方法調用會是這樣 Prin(root,0)。

  在Print()方法中,我們輸出目錄的名稱,並在名稱前根據目錄在繼承體系所處的深度,加上相應數量的前導空格。StringBuilder類的方法Append()接受兩個參數,一個是字符和一個整型,他創建一個StringBuilder實例,並附加整型參數指定數量的字符。在我們的調用中,我們使用空格和目錄的深度(level)作為參數,他返回一個目錄深度數量的空格字符串。我們調用StringBuilder的Sostring()方法,將StringBuilder實例轉換成一個string實例。

  現在到了遞歸部分,我們通過children迭代孩子目錄,為每一個孩子目錄調用Print()方法,並確保Levle遞增。 當遍歷完children,我們就返回。最終的結果如前面的輸出。

  在6-5中,我們會展示另一個方法,存儲過程中使用表表達式,在存儲端能過關系圖迭代,然后返回一個扁平化的結果集。

 

  本篇主題到此結束,希望你有收獲。 轉載請注明出處。謝謝。

 

實體框架交流QQ群:  458326058,歡迎有興趣的朋友加入一起交流

謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM