本文章是由機器翻譯。

資料點

兵不血刃拒絕對 Entity Framework 的表訪問

Julie Lerman

在看到實際的 Entity Framework 命令創建之後,資料庫所有者的第一反應往往是:“什麼?我必須提供對表的訪問權?”他們有這種反應是因為 Entity Framework 的核心功能之一便是生成 SELECT、UPDATE、INSERT 和 DELETE 等命令。

在本專欄中,我將帶領資料庫管理員瞭解一下 Entity Framework 如何生成命令,然後介紹一些功能,您可以利用這些功能只允許 Entity Framework 使用視圖與存儲過程,從而限制 Entity Framework 對您資料庫的訪問。而與此同時,您不會對應用程式碼產生任何影響,也不會疏遠與團隊中開發人員之間的關係。

認識預設命令生成

這一命令生成過程是如何實現的?Entity Framework 的要點是實體資料模型 (EDM),一個用於描述應用程式定義域物件的概念模型。Entity Framework 讓開發人員可以針對實體資料模型提出查詢,而不必操心資料庫的具體操作。實體資料模型的實體以及實體之間的關係以 XML 形式定義,而開發人員基於該模型的實體來處理強類型化類。Entity Framework 運行時使用實體資料模型的 XML 以及其他中繼資料(用於描述資料庫架構以及從實體資料模型到資料庫架構的映射關係)來溝通類與資料庫(參見圖 1)。

圖 1 Entity Framework 運行時中繼資料用於生成資料庫命令

在運行時,利用特定于資料庫的 ADO.NET 提供程式,Entity Framework 將針對實體資料模型而創建的查詢轉換為存儲查詢(例如 T-SQL),然後送至資料庫。Entity Framework 將查詢結果轉換為由強類型化實體類所定義的物件,如圖 2 所示。

圖 2 Entity Framework 執行查詢並處理查詢結果

在使用者處理這些物件的時候,Entity Framework 利用標識鍵跟蹤屬性以及物件之間的關係所發生的更改。最後,當代碼調用 Entity Framework SaveChanges 方法,從而在資料庫中永久保存更改時,Entity Framework 運行時會讀取自己採集的所有更改跟蹤資訊。對於每個修改、添加或刪除的實體,Entity Framework 會再次讀取模型,並讓提供程式生成存儲命令,然後在一次可逆事務中對資料庫執行這些命令。

這一段關於 Entity Framework 預設行為的描述往往會讓資料庫所有者發瘋,但在這裡我想要強調“預設”這個詞。Entity Framework 有許多可以改變的預設行為。

Entity Framework 對於資料檢索請求或資料保存請求的處理方式就是這樣一個可以修改的行為。您不必建立依賴于 Entity Framework 的模型便能訪問您的資料表。您可以建立一個只知道資料庫視圖與存儲過程的模型,而不影響使用該模型的應用程式碼。通過結合 Entity Framework 的存儲過程支援與其資料庫視圖支援,您能夠以存儲過程和視圖為基礎實現所有資料庫交互。

將實體映射到資料庫視圖,而非表

建立模型有多種方法。我將重點討論通過對舊式資料庫實施反向工程處理而建立的模型。對於這一過程,Visual Studio 提供了一個嚮導。

在此嚮導中,使用者可以選擇資料庫表、視圖和存儲過程。存儲過程部分還會列出可以放入模型的、使用者定義的標量值函數。

通常,開發人員將會選擇表,並讓嚮導根據這些表創建實體。在之前討論的更改跟蹤與 SaveChanges 過程中,Entity Framework 自動為基於表的實體生成 INSERT、UPDATE 與 DELETE 等命令。

我們先來看看如何強制 Entity Framework 針對視圖而非表來執行查詢。

放入模型的資料庫視圖也會成為實體。Entity Framework 跟蹤這些實體所發生的更改,就像它跟蹤映射到表的實體那樣。使用視圖的時候,關於標識鍵有一點需要注意。資料庫表可能會有一個或更多被標為表主鍵的列。預設情況下,嚮導會根據表的主鍵創建實體的標識鍵。在創建映射到視圖(沒有主鍵)的實體的時候,嚮導會根據表中所有不可為空的值創建一個複合鍵,從而盡力推斷該標識鍵。假設有一個視圖有四個不可為空的列,即ContactID、FirstName、LastName 和 TimeStamp,然後根據這個視圖創建一個實體。

所生成的四個屬性將被標為 EntityKey(設計器利用鍵圖示標示 EntityKey 屬性),這表示該實體有一個由這四個屬性組成的 EntityKey。

我們只需要 ContactID 這個屬性來唯一標識此實體。因此,在模型創建完成之後,您可以利用設計器將其他三個屬性的 EntityKey 屬性更改為 False,從而只將 ContactID 設為指定的 EntityKey。

或者(如有可能),您還可以事先規劃好,讓設計出的資料庫視圖提供正確的、不可為空的列。

確定鍵之後,Entity Framework 可以唯一標識每個實體,因而能對這些實體執行更改跟蹤,然後在調用 SaveChanges 的時候將更改永久保存到資料庫中。

用您自己的存儲過程取代命令生成

對於將更改永久保存到資料庫中的操作,您可以覆蓋預設命令生成,在需要永久保存到資料庫的時候讓 Entity Framework 使用您自己的 Insert、Update 和 Delete 存儲過程。此舉稱為“存儲過程映射” WinUnit 瞶

您在 EDM 嚮導(或之後在更新嚮導)中選定應放入模型的任何存儲過程,都會變成模型 XML 中繼資料中用於描述資料庫架構的那個部分中的一個函數。它不會自動成為概念模型的一個組成部分,在設計圖面中不會有任何表示。

下麵是我的某個資料庫中 Person 表的一個簡單的 Insert 存儲過程。

ALTER procedure [dbo].[InsertPerson]
           @FirstName nchar(50),
           @LastName nchar(50),
           @Title nchar(50)
AS
INSERT INTO [Entity FrameworkWorkshop].[dbo].[Person]
           ([FirstName]
           ,[LastName]
           ,[Title]           )
     VALUES
(@FirstName,@LastName,@Title)
SELECT @@IDENTITY as PersonID

此存儲過程不僅執行資料庫插入操作,隨後它還返回 SQL Server 為新行創建的主鍵值。

如果您在嚮導中選擇此過程,那麼它在模型的資料庫架構中將表示為以下函數:

<Function Name="InsertPerson" Aggregate="false" BuiltIn="false"   
 NiladicFunction="false" IsComposable="false" 
 ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo">
  <Parameter Name="FirstName" Type="nchar" Mode="In" />
  <Parameter Name="LastName" Type="nchar" Mode="In" />
  <Parameter Name="Title" Type="nchar" Mode="In" />
</Function>

然後您可以使用設計器的“映射詳細資訊”視窗將這個 InsertPerson 函數映射到根據 Person 表創建的 Person 實體,如圖 3 所示。

圖 3 將存儲過程映射到實體

請注意,在圖 3 中,PersonID 屬性映射到存儲過程的返回值。這一映射將會導致在資料庫中執行插入操作之後,Entity Framework 便會立即利用資料庫生成的鍵來更新記憶體中的 Person 物件。

映射函數時的一個重要要求是:函數中的每個參數必須映射到實體中的某個屬性。不允許將某個公式或值映射到參數。然而,開發人員有許多機會自訂表示這些實體的 Microsoft .NET Framework 類。

您也可以映射 Update 函數和 Delete 函數。雖然不是必須映射所有三個操作(Insert、Update 和 Delete),但開發人員必須注意文檔中所述有關只映射部分函數的某些規則。

圖 3 中,請注意在屬性的右側還有兩列(因為列寬的原因而縮略顯示):“使用原始值”和“影響的行數”。Entity Framework 支援最優併發,而您可以使用這些屬性對 Update 函數和 Delete 函數提供併發檢查。有關此功能的更多資訊,請參見 MSDN 文檔“演練:將實體映射到存儲過程(實體資料模型工具)”。

在運行時,如有使用者創建了一個新的 Person 類型,並且隨後觸發了 SaveChanges 方法,Entity Framework 則會在中繼資料中看到 Insert 函數映射(基於在圖 3 中定義的映射)。它將發出下列命令以執行存儲過程,而不會動態生成自己的 INSERT 命令:

exec [dbo].[InsertPerson] @FirstName=N'Julie',@LastName=N'Lerman',
@Title=N'Ms.'

彌補隔閡以及避免 Entity Framework 訪問表

Entity Framework 將會生成命令以永久保存基於視圖的各實體所返回的資料,但視圖可能無法更新。 如果視圖無法更新,您可以將 Insert、Update 和 Delete 存儲過程映射到實體,並能完成完整的資料檢索與資料永久保存操作,而不必提供對資料庫表的直接存取權限。

您可以只創建與表相匹配的資料庫視圖,也可以只創建用於更新表列的存儲過程。 您也可以擁有更為複雜的視圖,以及包含用以執行更新操作的高級邏輯的複雜存儲過程。 您甚至可以將您的某些讀取存儲過程替換為視圖,讓開發人員能夠針對視圖創建查詢,而這是存儲過程無法做到的。

關於這一查詢創建功能的示例是,應用程式可以針對 CustomersInPastYear 實體提出一個查詢請求,從而利用客戶的 LastName 屬性進一步篩選視圖:

from c in context.CustomersInPastYears
 where c.LastName.StartsWith("B")
 select c;

這樣,在資料庫中便會執行下列命令:

SELECT
[Extent1].[CustomerID] AS [CustomerID], [Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName], [Extent1].[EmailAddress] AS [EmailAddress], 
[Extent1].[TimeStamp] AS [TimeStamp]
FROM (SELECT 
      [CustomersInPastYear].[CustomerID] AS [CustomerID], 
      [CustomersInPastYear].[FirstName] AS [FirstName], 
      [CustomersInPastYear].[LastName] AS [LastName], 
      [CustomersInPastYear].[EmailAddress] AS [EmailAddress], 
      [CustomersInPastYear].[TimeStamp] AS [TimeStamp]
      FROM [dbo].[CustomersInPastYear] AS [CustomersInPastYear]) AS [Extent1]
WHERE [Extent1].[LastName] LIKE N'B%'

.NET 編譯器會接受針對某個存儲過程(已映射到模型中)而創建的類似查詢。 然而,Entity Framework 會對資料庫執行該存儲過程,將所有結果返回給應用程式,然後將篩選器應用於記憶體中由存儲過程所返回的物件。 這樣便有可能造成資源浪費以及對性能造成不利影響,而不為開發人員所知曉。

图 4 中是一個存儲過程,它利用參與了 Customers­InPastYear 視圖的相同列更新 Customer 表。 這個存儲過程可以充當 CustomersInPastYear 實體的 Update 函數。

圖 4 UpdateCustomerFirstNameLastNameEmail 存儲過程

ALTER PROCEDURE UpdateCustomerFirstNameLastNameEmail
@FirstName nvarchar(50),
@LastName nvarchar(50),
@Email nvarchar(50),
@CustomerId int,
@TimeStamp timestamp

AS

UPDATE Customer
   SET [FirstName] = @FirstName
      ,[LastName] = @LastName
      ,[EmailAddress] = @Email
 WHERE CustomerID=@CustomerId AND TimeStamp=@TimeStamp
 
 SELECT TimeStamp 
 FROM Customer
 WHERE CustomerID=@CustomerId

現在,您可以將此存儲過程映射到實體。图 5 中的映射將初始 TimeStamp 送至存儲過程,然後利用“結果列綁定”捕獲存儲過程所返回的經過更新的 TimeStamp。

圖 5 將存儲過程映射到基於視圖的實體

總而言之,只要模型設計得當,基於視圖的實體有相應的標識鍵,並且函數得到正確映射,就不需要對利用 Entity Framework 實現資料訪問策略的應用程式公開資料庫表。資料庫視圖和存儲過程可以為 EDM 以及 Entity Framework 提供它們要與您的資料庫成功進行交互所需要的所有條件。

Julie Lerman 是 Microsoft MVP、.NET 導師和顧問,住在佛蒙特州的山區。您可以在全球的使用者組和會議中看到她對資料訪問和其他 Microsoft .NET 主題的演示。Lerman 是《Programming Entity Framework》(O’Reilly Media,2009)一書的作者,該書受到廣泛稱讚,她的博客位址是 thedatafarm.com/blog。請關注她的 Twitter:julielerman。ClaimsPrincipalHttpModule:

衷心感謝以下技術專家參與本文的審閱:Noam Ben-AmiSrikanth Mandadi