《Entity Framework 6 Recipes》中文翻譯系列 (31) ------ 第六章 繼承與建模高級應用之自引用關聯

翻譯的初衷以及爲何選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇

6-4  使用TPH建模自引用關係

問題html

  你有一張自引用的表,它表明數據庫上不一樣類型但關聯的對象。你想使用TPH爲此表建模。數據庫

解決方案框架

  假設你有一張如圖6-5所示的表,它描述了關於人的事,人一般會有一個心中英雄,他最能激發本身。咱們用一個指向Person表中的另外一個行的引用來表示心中的英雄。ide

圖6-5  包含不一樣角色的Person表學習

 

  在現實中,每一個人都會有一個角色,有的是消防員,有的是教師,有的已經退休,固然,這裏可能會有更多的角色。每一個人的信息會指出他們的角色。一個消防員駐紮在消防站,一位教師在學校任教。退休的人一般會有一個愛好。ui

  在咱們示例中,可能角色有firefighter(f),teacher(t)或者retired(r)。role列用一個字符來指定人的角色。this

  按下面的步驟建立一個模型:spa

    一、建立一個派生至DbContext的上下文對象Recipe4Context;翻譯

    二、使用代碼清單6-8的代碼,建立一個抽象的POCO實體Person;設計

代碼清單6-8. 建立一個抽象的POCO實體類Person

[Table("Person", Schema = "Chapter6")]
    public abstract class Person
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int PersonId { get; protected set; }
        public string Name { get; set; }

        public virtual Person Hero { get; set; }
        public virtual ICollection<Person> Fans { get; set; } 
    }
   

    三、在上下文對象Recipe4Context中添加一個類型爲DbSe<Person>的屬性;

    四、使用代碼清單6-9中的代碼,添加具體的POCO實體類,Fierfighter,Teacher和Retired;

代碼清單6-9.建立具體的POCO實體類,Fierfighter,Teacher和Retired

 public class Firefighter : Person
    {
        public string FireStation { get; set; }
    }

    public class Teacher : Person
    {
        public string School { get; set; }
    }

    public class Retired : Person
    {
        public string FullTimeHobby { get; set; }
    }

    五、在上下文對象Recipe4Context中重寫OnModelCreating方法,以此配置HeroId外鍵和類型層次結構。如代碼清單6-10所示;

代碼清單6-10.重寫OnModelCreating方法

 1  protected override void OnModelCreating(DbModelBuilder modelBuilder)
 2         {
 3             base.OnModelCreating(modelBuilder);
 4 
 5             modelBuilder.Entity<Person>()
 6                         .HasMany(p => p.Fans)
 7                         .WithOptional(p => p.Hero)
 8                         .Map(m => m.MapKey("HeroId"));
 9 
10             modelBuilder.Entity<Person>()
11                         .Map<Firefighter>(m => m.Requires("Role").HasValue("f"))
12                         .Map<Teacher>(m => m.Requires("Role").HasValue("t"))
13                         .Map<Retired>(m => m.Requires("Role").HasValue("r"));
14         }

原理

  代碼清單6-11演示了從咱們的模型中獲取和插入Person實體,咱們爲每一個派生類型建立了一個實例,並構造了一些英雄關係。咱們有一位教師,它是消防員心中的英雄,一位退休的職工,他是這位教師心中的英雄。當咱們把消防員設爲退休職工的英雄時,咱們便引入了一個循環,此時,實體框架會產生一個運行時異常(DbUpdatexception),由於它沒法肯定合適的順序來將數據插入到數據庫中。在代碼中,咱們採用在設置英雄關係以前調用SaveChanges()方法,來繞開這個問題。一旦數據提交到數據庫,實體框架會把數據庫中產生的鍵值帶回到對象圖中,這樣咱們就不會爲更新關係圖而付出什麼代價。固然,這些更新最終仍要調用SaveChages()方法來保存。

 1  using (var context = new Recipe4Context())
 2             {
 3                 var teacher = new Teacher
 4                 {
 5                     Name = "Susan Smith",
 6                     School = "Custer Baker Middle School"
 7                 };
 8                 var firefighter = new Firefighter
 9                 {
10                     Name = "Joel Clark",
11                     FireStation = "Midtown"
12                 };
13                 var retired = new Retired
14                 {
15                     Name = "Joan Collins",
16                     FullTimeHobby = "Scapbooking"
17                 };
18                 context.People.Add(teacher);
19                 context.People.Add(firefighter);
20                 context.People.Add(retired);
21                 context.SaveChanges();
22                 firefighter.Hero = teacher;
23                 teacher.Hero = retired;
24                 retired.Hero = firefighter;
25                 context.SaveChanges();
26             }
27 
28             using (var context = new Recipe4Context())
29             {
30                 foreach (var person in context.People)
31                 {
32                     if (person.Hero != null)
33                         Console.WriteLine("\n{0}, Hero is: {1}", person.Name,
34                                             person.Hero.Name);
35                     else
36                         Console.WriteLine("{0}", person.Name);
37                     if (person is Firefighter)
38                         Console.WriteLine("Firefighter at station {0}",
39                                            ((Firefighter)person).FireStation);
40                     else if (person is Teacher)
41                         Console.WriteLine("Teacher at {0}", ((Teacher)person).School);
42                     else if (person is Retired)
43                         Console.WriteLine("Retired, hobby is {0}",
44                                            ((Retired)person).FullTimeHobby);
45                     Console.WriteLine("Fans:");
46                     foreach (var fan in person.Fans)
47                     {
48                         Console.WriteLine("\t{0}", fan.Name);
49                     }
50                 }
51             }

代碼清單6-11的輸出以下:

Susan Smith, Hero is: Joan Collins
Teacher at Custer Baker Middle School
Fans:
        Joel Clark

Joel Clark, Hero is: Susan Smith
Firefighter at station Midtown
Fans:
        Joan Collins

Joan Collins, Hero is: Joel Clark
Retired, hobby is Scapbooking
Fans:
        Susan Smith

 

6-5  使用TPH建模自引用關係

問題

  你正在使用一張自引用的表來存儲層級數據。給定一條記錄,獲取出全部與這關係的記錄,這此記錄能夠是層級中任何深度的一部分。

解決方案

  假設你有一張如圖6-6所示的Category表。

圖6-6 自引用的Category表

 

  使用下面步驟,建立咱們的模型:

    一、建立一個派生至DbContext的上下文對象Recipe5Context;

    二、使用代碼清單6-12的代碼,建立一個POCO實體Category;

代碼清單6-12.  建立一個POCO實體類Category

 1  [Table("Category", Schema = "Chapter6")]
 2     public class Category
 3     {
 4         [Key]
 5         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 6         public int CategoryId { get; set; }
 7         public string Name { get; set; }
 8 
 9         public virtual Category ParentCategory { get; set; }
10         public virtual ICollection<Category> SubCategories { get; set; }
11     }

    三、在上下文對象Recipe5Context中添加一個類型爲DbSe<Category>的屬性;

    四、在上下文對象Recipe4Context中重寫OnModelCreating方法,如代碼清單6-13所示,咱們建立了關聯ParentCategory和SubCategories,並配置了外鍵約束。

代碼清單6-13.重寫OnModelCreating方法

1    protected override void OnModelCreating(DbModelBuilder modelBuilder)
2         {
3             base.OnModelCreating(modelBuilder);
4 
5             modelBuilder.Entity<Category>()
6                         .HasOptional(c => c.ParentCategory)
7                         .WithMany(c => c.SubCategories)
8                         .Map(m => m.MapKey("ParentCategoryId"));
9         }

  在咱們的模型中,Category實體有一個導航屬性Subcategories,咱們可使用它獲取到目錄的直接子目錄集合。而後,爲了訪問它們,咱們須要使用方法Load()或者Include()顯式加載它們。Load()方法須要額外的一次數據庫交互,Include()方法只提供一個預先定義的深度肯定的訪問方式。

  咱們須要儘量有效地把整個層次結構所有加載到對象圖中,咱們採用了存儲過程當中的表表達式。

  按下面的步驟將存儲過程添加到模型中:

    五、建立一個名爲GetSubCategories的存儲過程,它使用一個表表達式,經過遞歸爲一個目錄ID返回全部的子目錄。存儲過程如代碼清單6-14所示:

代碼清單6-14. 存儲過程GetSubCategories,爲一個給定的目錄ID返回全部的子目錄

create proc chapter6.GetSubCategories
(@categoryid int)
as
begin
with cats as
(
select c1.*
from chapter6.Category c1
where CategoryId = @categoryid
union all
select c2.*
from cats join chapter6.Category c2 on cats.CategoryId = 
c2.ParentCategoryId
)
select * from cats where CategoryId != @categoryid
end

    六、在上下文對象Recipe5Context中添加一個接受整型類型參數的方法,它返回一個ICollection<Category>。如代碼清單6-15所示。實體模型6中的Code First還不支持在設計器中導入存儲過程。因此,在方法中,咱們使用DbContext的屬性Database中的SqlQuery方法。

代碼清單6-15. 在上下文對象中實現 GetSubCateories方法

1  public ICollection<Category> GetSubCategories(int categoryId)
2         {
3             return this.Database.SqlQuery<Category>("exec Chapter6.GetSubCategories @catId",
4                                                     new SqlParameter("@catId", categoryId)).ToList();
5         }

    咱們使用上下文中定義的GetSubCategoryes方法,實例化包含全部目錄及子目錄的對象圖。 代碼清單6-16演示了使用GetSubCategories()方法。

代碼清單6-16. 使用GetSubCategories()方法獲取整個層次結構

 using (var context = new Recipe5Context())
            {
                var book = new Category { Name = "Books" };
                var fiction = new Category { Name = "Fiction", ParentCategory = book };
                var nonfiction = new Category { Name = "Non-Fiction", ParentCategory = book };
                var novel = new Category { Name = "Novel", ParentCategory = fiction };
                var history = new Category { Name = "History", ParentCategory = nonfiction };
                context.Categories.Add(novel);
                context.Categories.Add(history);
                context.SaveChanges();
            }

            using (var context = new Recipe5Context())
            {
                var root = context.Categories.Where(o => o.Name == "Books").First();
                Console.WriteLine("Parent category is {0}, subcategories are:", root.Name);
                foreach (var sub in context.GetSubCategories(root.CategoryId))
                {
                    Console.WriteLine("\t{0}", sub.Name);
                }
            }

代碼清單6-16輸出以下:

Parent category is Books, subcategories are:
        Fiction
        Non-Fiction
        History
        Novel    

原理

  實體框架支持自引用的關聯,正如咱們在6.2和6.3小節中看到的那樣。在這兩節中,咱們使用Load()方法直接加載實體引用和引用實體集合。然而,咱們得當心,每個Load()都會致使一次數據庫交互才能獲取實體或實體集合。對於一個大的對象圖,這樣會消耗不少數據庫資源。

  在本節中,咱們演示一種稍微不一樣的方法。相比Load()方法實例化每個實體或實體集合這種方法,咱們經過使用一個存儲過程把工做放到數據存儲層中,遞歸枚舉全部的子目錄並返回這個集合。在存儲過程當中,咱們使用一個表表達式來實現遞歸查詢。在咱們的示例中,咱們選擇枚舉全部的子目錄。固然,你能夠修改存儲過程,有選擇地枚舉層次結構中的元素。

  爲了使用這個存儲過程,咱們在上下文中添加了一個 經過DbContext.Database.SqlQuery<T>()調用存儲過程的方法。咱們使用SqlQuery<T>()而不是ExecuteSqlCommand()方法,是由於咱們的存儲過程要返回一個結果集。

 

 

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

謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/VolcanoCloud/

相關文章
相關標籤/搜索