問題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-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/