閱讀360 返回首頁    go 阿裏雲 go 技術社區[雲棲]


在Entity Framework中使用存儲過程(二):具有繼承關係實體的存儲過程如何定義?

在《實現存儲過程的自動映射》中,我通過基於T4的代碼生成實現了CUD存儲過程的自動映射。由於映射的都是基於數據表結構的標準的存儲過程,所以它們適合概念模型和存儲模型結構相同的場景。如果兩種模型存在差異,在進行數據更新操作的時候就會出錯。本篇文章主要介紹當概念模型中具有繼承關係的兩個實體映射到數據庫關聯的兩個表,如何使用存儲過程。

目錄
一、創建具有繼承關係的實體
二、基於繼承關係實體的查詢與更新
三、映射標準的CUD存儲過程
四、修正存儲過程

假設數據庫中有如下兩個關聯的表:T_EMP和T_SALES。T_EMP用於存儲員工信息,主鍵為代表員工ID號的EMP_ID。為了簡單起見,我僅僅定義兩個額外的字段:FIRST_NAME和LAST_NAME。另一個表T_EMP用於存儲銷售人員的信息,它具有一樣的主鍵EMP_ID,額外的兩個字段代表負責的區域(Territory)和提成的比率(Commission Rate)。兩者通過EMP_ID進行關聯。

image

然後我們通過選擇這兩個表創建.edmx模型。由於這兩個表之間具有關聯,.edmx模型得兩個實體之間會默認創建聯係,你首先需要刪除此聯係。由於銷售人員也是公司的員工,它屬於是員工類型的子類。所以你需要建立它們之間的繼承關係。由於具有繼承關係的兩個實體不能有重複的屬性,屬於你需要刪除掉T_SALES的EMP_ID屬性。最後你需要修正實體和屬性的名稱使之更具可讀性。最後的.edmx模型如下圖所示。

image

在引入存儲過程之前,我們先來談談針對於如上一個具有繼承關係實體的.edmx模型,如果進行查詢和更新。使用過EF的讀者應該很清楚,客戶端代碼進行數據的查詢和更新都是通過自動生成的一個繼承自ObjectContext的類來完成的。我們不妨來看看針對上麵創建的.edmx模型,這個類具有怎樣的定義。由於我為該模型的Entity Container起名為HrEntities,隨後最終生成的是如下一個同名的類。

   1: public partial class HrEntities : ObjectContext
   2: {    
   3:     public ObjectSet<Employee> Employees{get}    
   4:     public void AddToEmployees(Employee employee)
   5:     {
   6:         //...
   7:     }
   8: }
   9:  
  10: public partial class Employee : EntityObject
  11: {
  12:      //...
  13: }
  14: public partial class Sales : Employee
  15: {
  16:     //...
  17: }

我們可以看到,雖然在模型中有兩個實體,但是對於HrEntities來說,它僅僅具有一個類型為ObjectSet<Employee>的Employees屬性(沒有ObjectSet<Sales>類型的屬性)和對應的AddToEmployee方法。但是針對這個兩個實體對應的類都是存在的,並且存在繼承關係。理解起來也容易,Sales也是Employee,所以Employees屬性表述的ObjectSet可以同時包括普通的Employee和Sales。

最後我們在一個控製台應用中編寫如下一段代碼。這段代碼中,先刪除掉現有的Employee(包括Sales)記錄,然後分別添加一個Employee對象和Sales對象。最後通過查詢確保它被成功提交到數據庫中。

   1: static void Main(string[] args)
   2: {
   3:     using (HrEntities context = new HrEntities())
   4:     {
   5:         foreach (Employee emp in context.Employees)
   6:         {
   7:             context.Employees.DeleteObject(emp);
   8:         }
   9:         context.SaveChanges();
  10:  
  11:         Employee employee = Employee.CreateEmployee("001", "Jin Nan", "Jiang");
  12:         Employee sales = Sales.CreateSales("002", "Yan Yan", "Xu", "West", 10);
  13:         context.AddToEmployees(employee);
  14:         context.AddToEmployees(sales);
  15:         context.SaveChanges();
  16:  
  17:         foreach (Employee emp in context.Employees)
  18:         {
  19:             Console.WriteLine("Employee ID: {0}; First Name: {1}; Last Name: {2}; Is Sales: {3}",
  20:                 emp.ID, emp.FirstName, emp.LastName, (emp is Sales) ? "Yes" : "No");
  21:         }
  22:     }
  23: }

下麵是我們希望的輸出結果:

   1: Employee ID: 001; First Name: Jin Nan; Last Name: Jiang; Is Sales: No
   2: Employee ID: 002; First Name: Yan Yan; Last Name: Xu; Is Sales: Yes

從上麵的代碼中我們可以看到,當我們通過ObjectContext添加一個Employee對象的時候,它會先根據對象的真實類型,判斷僅僅需要添加Employee對應的數據表記錄,還是需要同時在Employee和Sales對應的兩張數據表中同時添加一條記錄。修改和刪除操作采用的機製也是如此。

到目前為止,我們的程序運行的很好,現在我們分別Employee和Sales實體映射我們創建的標準的數據表,你可以手工是完成,也可以利用在《實現存儲過程的自動映射》提到的代碼生成的方式。在這之前我們不妨先來看看我們標準的存儲過程長什麼模樣。下麵是基於T_EMP數據表的CUD存儲過程。

   1: CREATE PROCEDURE [dbo].[P_EMP_I]
   2:     @p_emp_id        VARCHAR(50),
   3:     @p_first_name    NVARCHAR(50),
   4:     @p_last_name    NVARCHAR(50)
   5: AS
   6: BEGIN
   7:     INSERT T_Emp(EMP_ID, FIRST_NAME, LAST_NAME)
   8:     VALUES(@p_emp_id,@p_first_name,@p_last_name)
   9: END
  10: GO
  11:  
  12: CREATE PROCEDURE [dbo].[P_EMP_U]
  13:     @o_emp_id        VARCHAR(50),
  14:     @p_first_name    NVARCHAR(50),
  15:     @p_last_name    NVARCHAR(50)
  16: AS
  17: BEGIN
  18:     UPDATE T_EMP
  19:     SET FIRST_NAME    = @p_first_name,    LAST_NAME = @p_last_name
  20:     WHERE emp_id    = @o_emp_id
  21: END
  22: GO
  23:  
  24: CREATE PROCEDURE [dbo].[P_EMP_D]
  25:     @o_emp_id VARCHAR(50)
  26: AS
  27: BEGIN
  28:     DELETE T_EMP    
  29:     WHERE EMP_ID = @o_emp_id
  30: END
  31: GO

下麵三個是基於T_SALES數據表的三個存儲過程。

   1: CREATE PROCEDURE [dbo].[P_SALES_I]
   2:     @p_emp_id            VARCHAR(50),
   3:     @p_territory        NVARCHAR(50),
   4:     @p_commission_rate    INT
   5: AS
   6: BEGIN
   7:     INSERT T_SALES(EMP_ID, TERRITORY,COMMISSION_RATE)
   8:     VALUES(@p_emp_id, @p_territory, @p_commission_rate)
   9: END
  10: GO
  11:  
  12: CREATE PROCEDURE [dbo].[P_SALES_U]
  13:     @o_emp_id            VARCHAR(50),
  14:     @p_territory        NVARCHAR(50),
  15:     @p_commission_rate    INT
  16: AS
  17: BEGIN
  18:     UPDATE T_SALES
  19:     SET TERRITORY        = @p_territory,
  20:         COMMISSION_RATE = @p_commission_rate    
  21:     WHERE emp_id        = @o_emp_id
  22: END
  23: GO
  24:  
  25: CREATE PROCEDURE [dbo].[P_SALES_D]
  26:     @o_emp_id    VARCHAR(50)
  27: AS
  28: BEGIN
  29:     DELETE T_SALES    
  30:     where EMP_ID = @o_emp_id
  31: END

完成存儲過程的映射後,再次運行上麵的代碼,會有如下一個UpdateException異常拋出來。追蹤InnerException,你會發現一條有用的異常消息:

image

之所以出現上述的異常在於:當我們添加一個Sale對象的時候,隻有Sales實體對象的Insert存儲過程被執行。而該存儲過程僅僅是為T_SALES數據表中插入數據,但是此時主表T_EMP沒有相應的記錄,違反外鍵約束。在進行數據的修改和刪除時,也有相同的問題。

為了解決這個問題,我們隻需要修改子類對應表的存儲過程,讓它們同時去添加、修改和刪除主記錄。下麵列出了修正後的存儲過程定義。

P_SALES_I

   1: CREATE PROCEDURE [dbo].[P_SALES_I]
   2:     @p_emp_id           VARCHAR(50),
   3:     @p_first_name       NVARCHAR(50),
   4:     @p_last_name        NVARCHAR(50),
   5:     @p_territory        NVARCHAR(50),
   6:     @p_commission_rate  INT
   7: AS
   8: BEGIN
   9:     INSERT  T_Emp(EMP_ID, FIRST_NAME, LAST_NAME)
  10:     VALUES(@p_emp_id,@p_first_name,@p_last_name)
  11:     
  12:     INSERT T_SALES(EMP_ID, TERRITORY,COMMISSION_RATE)
  13:     VALUES(@p_emp_id, @p_territory, @p_commission_rate)
  14: END

P_SALES_U

   1: CREATE PROCEDURE [dbo].[P_SALES_U]
   2:     @o_emp_id           VARCHAR(50),
   3:     @p_first_name       NVARCHAR(50),
   4:     @p_last_name        NVARCHAR(50),
   5:     @p_territory        NVARCHAR(50),
   6:     @p_commission_rate  INT
   7: AS
   8: BEGIN
   9:     UPDATE T_EMP
  10:     SET FIRST_NAME        = @p_first_name,
  11:         LAST_NAME         = @p_last_name
  12:     WHERE EMP_ID           = @o_emp_id
  13:     
  14:     UPDATE T_SALES
  15:     SET TERRITORY        = @p_territory,
  16:         COMMISSION_RATE  = @p_commission_rate    
  17:     WHERE emp_id         = @o_emp_id
  18: END

P_SALES_D

   1: CREATE PROCEDURE [dbo].[P_SALES_D]
   2:     @o_emp_id VARCHAR(50)
   3: AS
   4: BEGIN
   5:  
   6:     DELETE T_SALES    
   7:     WHERE EMP_ID = @o_emp_id
   8:     
   9:     DELETE T_EMP
  10:     WHERE EMP_ID = @o_emp_id
  11: END

然後在EF的模型設計器中對新的參數進行映射即可。

在Entity Framework中使用存儲過程(一):實現存儲過程的自動映射
在Entity Framework中使用存儲過程(二):具有繼承關係實體的存儲過程如何定義?
在Entity Framework中使用存儲過程(三):邏輯刪除的實現與自增長列值返回
在Entity Framework中使用存儲過程(四):如何為Delete存儲過程參數賦上Current值?
在Entity Framework中使用存儲過程(五):如何通過存儲過程維護多對多關係?


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 13:33:38

  上一篇:go  在Entity Framework中使用存儲過程(一):實現存儲過程的自動映射
  下一篇:go  兩個簡單的擴展方法:TrimPrefix和TrimSuffix