《Entity Framework 6 Recipes》中文翻譯系列 (32) ------ 第六章 繼承與建模高級應用之TPH與TPT (1)

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

6-6  映射派生類中的NULL條件

問題html

  你的表中,有一列容許爲null。你想使用TPH建立一個模型,列值爲null時,表示一個派生類型,不爲null時,表示另外一個派生類型。數據庫

解決方案瀏覽器

  假設你有一張表,描述醫學實驗藥物。這張表包含一列指示該藥是何時批准生產的。藥在批准生產以前都被認爲是實驗性的。一但批准生產,它就被認爲是藥物了。咱們就以圖6-7中Drug表開始咱們這一小節的學習。架構

圖6-7 Drug表中有一列可爲null鑑別列,AcceptedDateapp

 

  按下面的步驟爲Drug表建模:框架

    一、建立一個派生至DbContext的上下文對象Recipe6Context;編輯器

    二、建立POCO實體類Drug、Medicine和Experimental,如代碼清單6-17所示;ide

代碼清單6-17. 建立POCO實體Drug、Medicine和Experimental函數

 1  [Table("Drug", Schema = "Chapter6")]
 2     public abstract class Drug
 3     {
 4         [Key]
 5         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 6         public int DrugId { get; set; }
 7         public string Name { get; set; }
 8     }
 9 
10     public class Experimental : Drug
11     {
12         public string PrincipalResearcher { get; set; }
13 
14         public void PromoteToMedicine(DateTime acceptedDate, decimal targetPrice, 
15                                   string marketingName)
16         {
17             var drug = new Medicine { DrugId = this.DrugId };
18             using (var context = new Recipe6Context())
19             {
20                 context.Drugs.Attach(drug);
21                 drug.AcceptedDate = acceptedDate;
22                 drug.TargetPrice = targetPrice;
23                 drug.Name = marketingName;
24                 context.SaveChanges();
25             }
26         }
27 
28     }
29 
30     public class Medicine : Drug
31     {
32         public decimal? TargetPrice { get; set; }
33         public DateTime AcceptedDate { get; set; }
34     }

    三、在上下文對象Recipe6Context中添加一個類型爲DbSe<Drug>的屬性;學習

    四、在上下文對象Recipe6Context中重寫OnModelCreating方法,讓它爲臨牀用藥(Medicine)和試驗性藥物(Experimental)類型配置TPH映射。如代碼清單6-18所示;

 

代碼清單6-18.重寫OnModelCreating方法,使之配置TPH映射

1 protected override void OnModelCreating(DbModelBuilder modelBuilder)
2         {
3             base.OnModelCreating(modelBuilder);
4             modelBuilder.Entity<Experimental>()
5                         .Map(m => m.Requires("AcceptedDate").HasValue((DateTime?)null));
6             modelBuilder.Entity<Medicine>()
7                         .Map(m => m.Requires(d => d.AcceptedDate).HasValue());
8         }

原理

  在示例中,咱們使用null和非null做爲條件分別映射,不包含AcceptedDate的試驗性藥物(Experimental)和包含AcceptedDate的臨牀用藥(Medicine)。和多數繼承的例子同樣,咱們建立一個抽象的基類實體,之因此爲抽象,是由於在咱們的模型中,不會使用一個未分類的藥物。

  有趣的是,在Medicine實體中,咱們將鑑別列映射到一個標題屬性上。在絕大多數狀況下,將鑑別列映射到一個標量屬性是被禁止的。然而,在這個示例中,咱們使用null和非null做爲條件,不只如此,AcceptedDate還設爲不可空,這些對屬性值的約束使得該屬性能夠被映射。

  在代碼清單6-19中,咱們插入了兩個試驗性藥物,並查詢出插入的數據。咱們使用AcceptedDate屬性爲咱們提供的機會,能夠將一個對象從一個派生類型改變爲另外一個派生類型。在咱們的示例中,咱們建立了兩個試驗性藥物,隨後將他們中的一個提高爲臨牀用藥。

代碼清單6-19.插入並獲取咱們的派生類型實例

 using (var context = new Recipe6Context())
            { 
                var exDrug1 = new Experimental { Name = "Nanoxol", 
                                       PrincipalResearcher = "Dr. Susan James" };
                var exDrug2 = new Experimental { Name = "Percosol", 
                                       PrincipalResearcher = "Dr. Bill Minor" };
                context.Drugs.Add(exDrug1);
                context.Drugs.Add(exDrug2);
                context.SaveChanges();

                // Nanoxol剛獲生產批准
                exDrug1.PromoteToMedicine(DateTime.Now, 19.99M, "Treatall");
                context.Entry(exDrug1).State = EntityState.Detached; //不在使用此實例
            }

            using (var context = new Recipe6Context())
            {
                Console.WriteLine("Experimental Drugs");
                foreach (var d in context.Drugs.OfType<Experimental>())
                {
                    Console.WriteLine("\t{0} ({1})", d.Name, d.PrincipalResearcher);
                }

                Console.WriteLine("Medicines");
                foreach (var d in context.Drugs.OfType<Medicine>())
                {
                    Console.WriteLine("\t{0} Retails for {1}", d.Name,
                                       d.TargetPrice.Value.ToString("C"));
                }
            }

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

Experimental Drugs
        Percosol (Dr. Bill Minor)
Medicines
        Treatall Retails for $19.99    

  咱們使用PromoteToMedicine()方法將一個試驗性藥物(Experimental)提高爲臨牀用藥(Medicine)。在這個方法的實現中,咱們建立了一個Medicine實例,將它附加到一個新的上下文中,並使用適當的值初始化它。一旦這個新的實例被初始化和附加,咱們就調用SaveChanges()方法將它保存到數據庫中,由於這個實例有一個和試驗性藥物相同的鍵(DrugId),實體框架會產生一個update(更新)語句,而不是insert(插入)語句

  咱們在POCO類Experimental中實現了這個方法。這讓咱們能夠無縫地在這樣的類中添加一個方法,經過這種方式,提供了一種更簡潔的實現 。話雖這麼說,但咱們感興趣的是,建立一個透明持久化(persistence-ignorant)的POCO實體,能被用在多個上下文對象中,這讓咱們能夠在helper類中實現一個稍微不一樣的版本。

 

6-7 使用一個非主鍵列建模TPT繼承映射

問題

  在一個存在的架構中,你有一張或多張表,與一張共享表有一對一的關係,共享表中使用的鍵在表中不是主鍵,你想使用TPT對此建模。

解決方案

  假設你的數據庫中包含的數據庫關係圖如圖6-8所示。

圖6-8 一個包含staff,Principal和Instructor表的關係對象圖

  在圖6-8中,咱們一張職工(Staff)表,它包含員工的姓名(Name),兩張包含校長(Principal)、教員(Instructor)信息的表。這裏須要引發注意的是,表Principal和Instructor中的主鍵不是Staff表的外鍵。在這種類型的關係結構是不能直接使用TPT繼承映射的。對於TPT,關聯表的主鍵必須是主表(基表)的外鍵。同時,還要注意的是,關係是一對一。這是由於咱們對Principal和Instructor表中StaffId列建立了惟一索引的約束。

  按下面的步驟,爲圖6-8中的表及其關係建模:

    一、在你的項目中添加一個ADO.NET Entity Data Model(ADO.NET實體數據模型),並導入表Staff,Principal,和Instructor;

    二、刪除實體Principal與Staff之間的關聯,實體Instructor與Staff之間的關聯;

    三、右鍵Staff實體,選擇Add(增長) ➤Inheritance(繼承)。選擇Staff做爲基類,Principal做爲派生類。重複前面的操做,選擇Staff做爲基類,Instructor做爲派生類型。

    四、從實體Instructor和Principal中刪除屬性StaffId;

    五、右鍵實體Staff,選擇Properties(屬性)。設置Abstract(抽象)屬性爲True。這會讓實體Staff成爲一個抽象類;

    六、由於StaffId在表Principal和Instructor中不是主鍵,因此咱們不能使用默認表映射來映射實體Principal和Instructor,或者Staff。依次選擇每一個表,並在映射詳細信息窗口刪除表映射。

    七、使用代碼清單6-20中的代碼建立存儲過程,咱們會將這些存儲過程映射到實體Principal和Instrucotr中的插入、更新和刪除操做;

代碼清單6-20. 實體Instructor和Principal插入、更新和刪除動做的存儲過程

 1 create procedure [chapter6].[InsertInstructor]
 2 (@Name varchar(50), @Salary decimal)
 3 as
 4 begin
 5 declare @staffid int
 6 insert into Chapter6.Staff(Name) values (@Name)
 7 set @staffid = SCOPE_IDENTITY()
 8 insert into Chapter6.Instructor(Salary,StaffId) values (@Salary,@staffid)
 9 select @staffid as StaffId,SCOPE_IDENTITY() as InstructorId
10 end
11 go
12 create procedure [chapter6].[UpdateInstructor]
13 (@Name varchar(50), @Salary decimal, @StaffId int, @InstructorId int)
14 as
15 begin
16 update Chapter6.Staff set Name = @Name where StaffId = @StaffId
17 update Chapter6.Instructor set Salary = @Salary where InstructorId = @InstructorId
18 end
19 go
20 create procedure [chapter6].[DeleteInstructor]
21 (@StaffId int)
22 as
23 begin
24 delete Chapter6.Staff where StaffId = @StaffId
25 delete Chapter6.Instructor where StaffId = @StaffId
26 end
27 go
28 create procedure [Chapter6].[InsertPrincipal]
29 (@Name varchar(50),@Salary decimal,@Bonus decimal)
30 as
31 begin
32 declare @staffid int
33 insert into Chapter6.Staff(Name) values (@Name)
34 set @staffid = SCOPE_IDENTITY()
35 insert into Chapter6.Principal(Salary,Bonus,StaffId) values 
36 (@Salary,@Bonus,@staffid)
37 select @staffid as StaffId, SCOPE_IDENTITY() as PrincipalId
38 end
39 go
40 create procedure [Chapter6].[UpdatePrincipal]
41 (@Name varchar(50),@Salary decimal, @Bonus decimal, @StaffId int, @PrincipalId int)
42 as
43 
44 begin
45 update Chapter6.Staff set Name = @Name where StaffId = @StaffId
46 update Chapter6.Principal set Salary = @Salary, Bonus = @Bonus where 
47 PrincipalId = @PrincipalId
48 end
49 go
50 create procedure [Chapter6].[DeletePrincipal]
51 (@StaffId int)
52 as
53 begin
54 delete Chapter6.Staff where StaffId = @StaffId
55 delete Chapter6.Principal where StaffId = @StaffId
56 end

    八、右鍵設計器窗口,選擇Update Model from Database(從數據庫中更新模型)。添加第7步建立的存儲過程;

    九、選擇實體Principal,並查看Mapping Details window(映射詳細信息窗口)。單擊Map Entity to Function(映射實體到函數)按鈕。這個按鈕是在映射詳細信息窗口左邊最下邊的一個按鈕。映射Insert、Update和Delete動做到存儲過程。確保映射插入動做的result columns(結果列)StaffId和PrincipalId(如圖6-9)。

圖6-9 爲實體Principal映射Insert,Update和Delete動做

 

    十、在上實體Instructor重複第9步。確保映射插入動做的result columns(結果列)StaffId和PrincipalId。

  在解決方案瀏覽器中右鍵.edmx文件,選擇Open With(打開方式) ➤XML Editor(XML文本編輯器)。這將關閉設計器窗口並在XML編輯器中打開.edmx文件。滾動到映射怪中的標籤<EntityContainerMapping>。將代碼清單6-21中的查詢視圖(QueryView)插入到標籤<EntitySetMapping>中。

 1             <EntitySetMapping Name="Staffs">
 2                 <QueryView>
 3                     select value
 4                     case
 5                     when (i.StaffId is not null) then
 6                     Apress.EF6Recipes.BeyondModelingBasics.Recipe7.Instructor(s.StaffId,s.Name,i.InstructorId,i.Salary)
 7                     when (p.StaffId is not null) then
 8                     Apress.EF6Recipes.BeyondModelingBasics.Recipe7.Principal(s.StaffId,s.Name,p.PrincipalId,p.Salary,p.Bonus)
 9                     END
10                     from ApressEF6RecipesBeyondModelingBasicsRecipe7StoreContainer.Staff as s
11                     left join ApressEF6RecipesBeyondModelingBasicsRecipe7StoreContainer.Instructor as i
12                     on s.StaffId = i.StaffId
13                     left join ApressEF6RecipesBeyondModelingBasicsRecipe7StoreContainer.Principal as p
14                     on s.StaffId = p.StaffId
15                 </QueryView>
16             </EntitySetMapping>

原理

  使用TPT繼承映射,實體框架要求基類表中的外鍵是派生類中的主鍵。在咱們的示例中,每一個派生類表有本身獨立的主鍵。

  爲了建立TPT繼承映射模型,在概念層,實體Principal和Instructor繼承自實體Staff。接下來,咱們刪除導入表時建立的映射。而後咱們使用一個QueryView表達式來建立一個新的映射。使用QueryView將Insert、Update和Delete動做放入咱們的代碼中。爲了處理這些動做,咱們在數據庫中建立了額外的存儲過程。

  咱們使用QueryView將映射派生類中的標量屬性到數據庫表中。QueryView中的關鍵部分是case語句。這裏有兩種狀況,咱們有一個Principal和一個Instructor。若是Instructor的StaffId非null時,咱們就獲得一個Instructor實例;若是Principal的StaffId爲null時,咱們就獲得一個Principal實例。表達式剩下的部分是,引入派生類表中的行。

  代碼清單6-22插入一位校長和一位教員到數據庫表中。

代碼清單6-22. 從模型中插入和獲取

 1 using (var context = new Recipe7Context())
 2             {
 3                 var principal = new Principal
 4                 {
 5                     Name = "Robbie Smith",
 6                     Bonus = 3500M,
 7                     Salary = 48000M
 8                 };
 9                 var instructor = new Instructor
10                 {
11                     Name = "Joan Carlson",
12                     Salary = 39000M
13                 };
14                 context.Staffs.Add(principal);
15                 context.Staffs.Add(instructor);
16                 context.SaveChanges();
17             }
18 
19             using (var context = new Recipe7Context())
20             {
21                 Console.WriteLine("Principals");
22                 Console.WriteLine("==========");
23                 foreach (var p in context.Staffs.OfType<Principal>())
24                 {
25                     Console.WriteLine("\t{0}, Salary: {1:C}, Bonus: {2:C}",
26                                        p.Name, p.Salary,
27                                        p.Bonus);
28                 }
29                 Console.WriteLine("Instructors");
30                 Console.WriteLine("===========");
31                 foreach (var i in context.Staffs.OfType<Instructor>())
32                 {
33                     Console.WriteLine("\t{0}, Salary: {1:C}", i.Name, i.Salary);
34                 }
35             }

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

Principals
==========
        Robbie Smith, Salary: $48,000.00, Bonus: $3,500.00
Instructors
===========
        Joan Carlson, Salary: $39,000.00

 


 

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

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

相關文章
相關標籤/搜索