2-6 拆分實體到多表
問題
你有兩張或是更多的表,他們共享一樣的主鍵,你想將他們映射到一個單獨的實體。
解決方案
讓我們用圖2-15所示的兩張表來演示這種情況。
圖 2-15,兩張表,Prodeuct 和ProductWebInfo,擁有共同的主鍵
按下面的步驟為這兩張表建模一個單獨實體:
1、在你的項目中,創建一個繼承至DbContext的上下文對象EF6RecipesContext;
2、使用代碼清單2-8創建一個POCO實體Product;
代碼清單2-8:創建一個POCO實體Product
1 public class Product { 2 [Key] 3 [DatabaseGenerated(DatabaseGeneratedOption.None)] 4 public int SKU { get; set; } 5 public string Description { get; set; } 6 public decimal Price { get; set; } 7 public string ImageURL { get; set; } 8 }
3、在EF6RecipesContext中添加類型為DbSet<Product>的屬性Products;
4、使用代碼清單2-9在EF6RecipesContext中重寫OnModelCreating()方法;
代碼清單2-9 重寫OnModelCreating()方法
1 public class EF6RecipesContext : DbContext { 2 public DbSet<Product> Products { get; set; } 3 public EF6RecipesContext() 4 : base("name=EF6CodeFirstRecipesContext") { 5 } 6 protected override void OnModelCreating(DbModelBuilder modelBuilder) { 7 base.OnModelCreating(modelBuilder); 8 modelBuilder.Entity<Product>() 9 .Map(m => { 10 m.Properties(p => new { p.SKU, p.Description, p.Price }); 11 m.ToTable("Product", "Chapter2"); 12 }) 13 .Map(m => { 14 m.Properties(p => new { p.SKU, p.ImageURL }); 15 m.ToTable("ProductWebInfo", "Chapter2"); 16 }); 17 } 18 }
原理
這種情況常見於遺留系統中,一個表中的每一行都包含額外的,本該屬於另一張表的信息。隨着數據庫變化,這樣情況經常發生。沒有人願意去打破現有的代碼,而是通過在一個關鍵的表中添加一些列來解決問題。處理這種情況的答案是,建一張新表來“移植”這對額外的列。
通合並兩張或多張並到一個單獨的實體,通常也被叫作分拆一個實體到兩張或多張數據庫表,我可以把每個組成部分當成一個邏輯實體。這過程叫做垂直分拆。
垂直分拆的缺點在,我們獲取實體類型實例時,分拆的表需要一個額外的join(連接)來構建實體類型。這個額外的join如清單2-10所示:
清單2-10 垂直分拆需要額外的Join連接
1 SELECT 2 [Extent1].[SKU] AS [SKU], 3 [Extent2].[Description] AS [Description], 4 [Extent2].[Price] AS [Price], 5 [Extent1].[ImageURL] AS [ImageURL] 6 FROM [dbo].[ProductWebInfo] AS [Extent1] 7 INNER JOIN [dbo].[Product] AS [Extent2] ON [Extent1].[SKU] = [Extent2].[SKU]
插入和獲取Product實體沒有特別的要求。代碼清單2-11演示了操作被垂直分拆的Product實體類型
1 using (var context = new EF6RecipesContext()) { 2 var product = new Product { 3 SKU = 147, 4 Description = "Expandable Hydration Pack", 5 Price = 19.97M, 6 ImageURL = "/pack147.jpg" 7 }; 8 context.Products.Add(product); 9 product = new Product { 10 SKU = 178, 11 Description = "Rugged Ranger Duffel Bag", 12 Price = 39.97M, 13 ImageURL = "/pack178.jpg" 14 }; 15 context.Products.Add(product); 16 product = new Product { 17 SKU = 186, 18 Description = "Range Field Pack", 19 Price = 98.97M, 20 ImageURL = "/noimage.jp" 21 }; 22 context.Products.Add(product); 23 product = new Product { 24 SKU = 202, 25 Description = "Small Deployment Back Pack", 26 Price = 29.97M, 27 ImageURL = "/pack202.jpg" 28 }; 29 context.Products.Add(product); 30 context.SaveChanges(); 31 } 32 using (var context = new EF6RecipesContext()) { 33 foreach (var p in context.Products) { 34 Console.WriteLine("{0} {1} {2} {3}", p.SKU, p.Description, 35 p.Price.ToString("C"), p.ImageURL); 36 } 37 }
代碼清單2-11的輸出如下:
147 Expandable Hydration Pack $19.97 /pack147.jpg
178 Rugged Ranger Duffel Bag $39.97 /pack178.jpg
186 Range Field Pack $98.97 /noimage.jpg
202 Small Deployment Back Pack $29.97 /pack202.jpg
2-7 分拆一張表到多個實體
問題
你有這樣的一張數據庫表,里面包含經常使用的字符,一些不常用的大字段。為了性能,需要避免每個查詢都去加載這些字段。你需要將這張表分拆成兩個或是更多的實體。
解決方案
我們假設你有一張如圖2-16的表,它存儲照片的信息,以及照片的縮略圖和全分辨率圖。
圖2-16 Photograph表,有一個二進制的大對象字段,保存圖像數據
按下面的步驟創建一個包含成本合理且經常使用列的實體,同時創建一個包含成本高且極少使用的高分辨位列的實體:
1、在你的項目中創建一個繼承自DbContext的上下文對象EF6RecipesContext;
2、使用代碼清單2-12創建一個POCO實體Photograph;
代理清單2-12 創建一個POCO實體Photograph
1 public class Photograph { 2 [Key] 3 [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 4 public int PhotoId { get; set; } 5 public string Title { get; set; } 6 public byte[] ThumbnailBits { get; set; } 7 [ForeignKey("PhotoId")] 8 public virtual PhotographFullImage PhotographFullImage { get; set; } 9 }
3、使用代碼清單2-13創建一個POCO實體PhotographFullImage;
代理清單2-13 創建一個POCO實體PhotographFullImage
1 public class PhotographFullImage { 2 [Key] 3 public int PhotoId { get; set; } 4 public byte[] HighResolutionBits { get; set; } 5 [ForeignKey("PhotoId")] 6 public virtual Photograph Photograph { get; set; } 7 }
4、在上下文對象EF6RecipesContext中添加DbSet<Photograph>屬性;
5、在上下文對象EF6RecipesContext中添加另一個DbSet<PhotographFullImage>屬性;
6、使用代碼清單2-14重寫上下文對象中的OnModelCreating()方法;
代碼清單2-14 重寫上下文對象中的OnModelCreating()方法
1 protected override void OnModelCreating(DbModelBuilder modelBuilder) { 2 base.OnModelCreating(modelBuilder); 3 modelBuilder.Entity<Photograph>() 4 .HasRequired(p => p.PhotographFullImage) 5 .WithRequiredPrincipal(p => p.Photograph); 6 modelBuilder.Entity<Photograph>().ToTable("Photograph", "Chapter2"); 7 modelBuilder.Entity<PhotographFullImage>().ToTable("Photograph", "Chapter2"); 8 }
原理
實體框架不直接支持延遲加載某個單一的實體屬性。為了得到延遲加載成本昂貴屬性的好處,利用實體框架延遲加載關聯實體的特性,我們創建一個新的,包含成本昂貴的保存完整圖像列的實體PhotographFullImage,一個Photograph實體和PhotographFullImange實體之單的關聯。並且我們在概念層添加一個跟數據庫引用約束相似的約束,告訴實體框架一個PhotographFullImage不能離開Photograph而獨立存在。
由於引用約束的存在,在模型中,我們有兩件需要注意的事:一個是,當我們新建一個PhotographFullImage實體的實例或者調用SaveChages()方法之前,Photogrpah的實例必須存在上下文中。第二個是,如果我刪除一個photograph,與之關聯的photographFullImage也會被刪除,這有點像是數據庫中引用約束的級聯刪除。
代碼清單2-15 演示從模型中插入和獲取數據。
代碼清單2-15 插入和延遲加載成本昂貴的字段
1 byte[] thumbBits = new byte[100]; 2 byte[] fullBits = new byte[2000]; 3 using (var context = new EF6RecipesContext()) { 4 var photo = new Photograph { 5 Title = "My Dog", 6 ThumbnailBits = thumbBits 7 }; 8 var fullImage = new PhotographFullImage { HighResolutionBits = fullBits }; 9 photo.PhotographFullImage = fullImage; 10 context.Photographs.Add(photo); 11 context.SaveChanges(); 12 } 13 using (var context = new EF6RecipesContext()) { 14 foreach (var photo in context.Photographs) { 15 Console.WriteLine("Photo: {0}, ThumbnailSize {1} bytes", 16 photo.Title, photo.ThumbnailBits.Length); 17 18 //顯式加載存儲完整圖像的字段 19 context.Entry(photo).Reference(p => p.PhotographFullImage).Load(); 20 Console.WriteLine("Full Image Size: {0} bytes", 21 photo.PhotographFullImage.HighResolutionBits.Length); 22 } 23 }
代碼清單2-15的輸出如下:
Photo: My Dog, Thumbnail Size: 100 bytes
Full Image Size: 2000 bytes
代碼清單2-15創建並初始化實體Photograph和PhotographFullmage的實例對象,並將他們添加到上下文對象中,然后調用方法SaveChanges()保存。
在查詢中,我們獲取數據庫中每一個photograph,打印它們的信息,並顯示加載與之關系的實體PhotographFullImage。注意,我們沒有關閉上下文中默認的延遲加載選項,這正是我們需要的。我們可以選擇不去加載PhotographFullImage的實例,如果獲取成百上千張的照片,這將為我們節約大量的時間和帶寬。
本篇到此結束,如果你在堅持看本系列話,請點推薦以示支持。謝謝~
實體框架交流QQ群: 458326058,歡迎有興趣的朋友加入一起交流
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/