閱讀178 返回首頁    go 技術社區[雲棲]


在Entity Framework中使用存儲過程(四):如何為Delete存儲過程參數賦上Current值?

繼續討論EF中使用存儲過程的問題,這回著重討論的是為存儲過程的參數進行賦值的問題。說得更加具體一點,是如何為實體映射的Delete存儲過程參數進行賦值的問題。關於文中涉及的這個問題,我個人覺得是EF一個有待改進的地方,不知道各位看官是否同意?

目錄
一、EF存儲過程參數賦值的版本策略
二、Delete存儲參數就一定是Original值嗎?
三、如果直接修改.edmx模型的XML呢?
四、為Delete存儲過程參數賦上Current值,如何做得到?

和傳統的基於DataSet的ADO.NET類似,EF的核心功能之一就是“狀態追蹤(State Tacking)”。這中間實際上又涉及到兩個方麵:通過狀態決定數據更新的類型(Insert、Update和Delete);以及同時保存不同版本的屬性值(Current值和Original值)。版本策略主要是針對Update操作設計的,一般來講組成Where條件的為Original值,而更新的值為Current值。

正是因為隻有Update操作才需要顯式指定映射的是實體屬性值的版本(Current/Original),所以在進行實體/存儲過程映射的時候,隻有Update存儲過程才可以選擇“是否采用原始值(Use Original Value)”。Insert和Delete存儲過程默認的版本為Current和Original。反映在VS的.edmx模型設計器上就是:。

image

粗略地想想,EF這樣設計也無可厚非:Insert存儲過程用於添加一條全新的記錄,自然應該采用當前值;而Delete存儲過程用於刪除一條現有的記錄,刪除操作的篩選條件自然應該使用原始值。但是,我們忽略掉一點:?如果我進行“邏輯刪除”,實際上進行的是操作。關於邏輯刪除的實現,可以參閱我上一篇文章《邏輯刪除的實現與自增長列值返回》。

如果你看了我提到的這篇文章,你可能會問,即使在文中介紹的關於“邏輯刪除”的場景中,也沒有使用當前值得要求呀。是的,上一篇文章提到的邏輯刪除確實也隻需要傳入實體屬性的原始值作為Delete存儲過程的參數,現在我們就舉一個這樣的例子。

通過是使用T_CONTACT這張簡單不過的表,同樣是采用邏輯刪除。不過現在有這樣的一個要求,對於條存儲在的記錄,我們需要。對於一條被邏輯刪除掉的記錄,這個最後修改者就是刪除掉該條記錄的人。這是一個很常見的需求,為此我們可以直接在T_CONTACT的數據表中添加一個新的字段:,創建該表的DDL定義如下:

   1: CREATE TABLE [T_CONTACT]
   2: (
   3:     [ID]                [INT] IDENTITY(1,1)   PRIMARY KEY,
   4:     [NAME]              [NVARCHAR](50)        NOT NULL,
   5:     [IS_DELETED]        [BIT]                 NOT NULL,
   6:     [LAST_UPDATED_BY]   [NVARCHAR](50)        NOT NULL
   7: )

那麼對於Delete存儲過程,除了指定需要刪除的記錄的主鍵之外,還需要將當前用戶名作為參數作為傳進來。這樣的一個存儲過程具有如下的定義

   1: CREATE PROCEDURE [dbo].[P_CONTACT_D]
   2: (
   3:  @p_id INT,
   4:  @user_name NVARCHAR(50)
   5:  )
   6: AS
   7: BEGIN
   8:     UPDATE    T_CONTACT
   9:     SET       IS_DELETED = 1, 
  10:               LAST_UPDATED_BY = @user_name
  11:     WHERE     ID = @p_id
  12: END 

在實際操作場景下,我們需要先獲取一條現有的Contact記錄,然後將其標記為刪除。然後Delete存儲過程被執行,並且采用預先定義好的實體屬性/參數的映射關係來對存儲過程的參數進行賦值。但是,由於Delete存儲過程默認使用的是實體對象的初始值,。

由於Delete過程隻能接受實體的映射屬性的初始值作為參數,導致我們無法指定一個新的值作為參數。我想有人會有這樣的疑問:VS提供的設計器不能提供你指定Delete存儲過程參數版本的功能,你是否可以直接修改.edmx文件的XML呢?我們不妨來嚐試一下:

在整個XML中,實體的CUD存儲過程映射對應如下一段XML片段,我們可以看到,隻有UpdateFunction中的參數映射節點才有Version屬性(而且這是一個必需的屬性),用於指定參數定義的是Original值還是Current值。

   1: <EntityTypeMapping TypeName="EFExtensionsModel.Contact">
   2:   <ModificationFunctionMapping>
   3:     <InsertFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_I" >
   4:       <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" />
   5:       <ScalarProperty Name="Name" ParameterName="p_name" />
   6:       <ResultBinding Name="ID" ColumnName="ID" />
   7:     </InsertFunction>
   8:     <UpdateFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_U" >
   9:       <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" Version="Current" />
  10:       <ScalarProperty Name="Name" ParameterName="p_name" Version="Current" />
  11:       <ScalarProperty Name="ID" ParameterName="p_id" Version="Original" />
  12:     </UpdateFunction>
  13:     <DeleteFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_D" >
  14:       <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" />
  15:       <ScalarProperty Name="ID" ParameterName="p_id" />
  16:     </DeleteFunction>
  17:   </ModificationFunctionMapping>
  18: </EntityTypeMapping>

那些現在我們將DeleteFunction的user_name參數的映射節點人為地加上屬性設置。

   1: <DeleteFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_D" >
   2:   <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" Version="Current" />
   3:   <ScalarProperty Name="ID" ParameterName="p_id" />
   4: </DeleteFunction>

但是當你進行編譯的時候,會出現如下的錯誤,明確告訴你:“This function mapping can only contain bindings to property versions.”

image

從上麵的介紹我們不難發現,Delete存儲過程不能接受基於當前值得參數映射,並不僅僅是設計器不支持,EF本來就是這樣設計的。在這種情況下要實現我們的要求,隻有一個辦法:,這樣的轉變通過調用ObjectContext的AcceptAllChanges方法可以實現。具體來說,對於需要刪除的實體,現設定LastUpdatedBy屬性,然後調用AcceptAllChanges方法,然後再調用ObjectStateManager的ChangeObjectState方法將狀態設置為Deleted。最終通過調用SaveChanges方法提交更新,具體的代碼如下:

   1: static void Main(string[] args)
   2: {
   3:     using (EFExtensionsEntities context = new EFExtensionsEntities())
   4:     {
   5:         Contact contact = new Contact { Name = "Foo", LastUpdatedBy = "Bar" };
   6:         context.Contacts.AddObject(contact);
   7:         context.SaveChanges();
   8:         Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
   9:  
  10:         contact.LastUpdatedBy = "Baz";
  11:         context.AcceptAllChanges();
  12:         context.ObjectStateManager.ChangeObjectState(contact, EntityState.Deleted);
  13:         context.SaveChanges();
  14:     }
  15: }

執行上麵的程序後,你會在數據庫中發現為刪除對象指定的LastUpdatedBy屬性“Baz”,而不是初始值“Bar”最終反映在數據庫中。

image

雖然通過“曲線救國”我們可以實現為實體映射的Delete存儲過程指定一個“新值”作為某個參數的值,但是這樣的做法總覺得不怎麼優雅。所以,我個人覺得這是EF一個值得改進的地方,。

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


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

最後更新:2017-10-27 12:03:36

  上一篇:go  在Entity Framework中使用存儲過程(三):邏輯刪除的實現與自增長列值返回
  下一篇:go  曾鳴:為什麼要讓「聽得見炮火的士兵」做決定?| 幹貨