本文章是由機器翻譯。

資料點

玩 EF6 阿爾法

Julie Lerman

 

Julie Lerman閃亮的新玩具再好玩不過了,儘管我可以緊緊追隨 Entity Framework 6 (EF6) 的演進下載每夜構建的版本,但我還是一直等到第一個打包的 Alpha 版本發佈(2012 年 10 月 30 日),以便深入研究並開始試用。

如果您要問,「嗯?哪來的每夜構建的版本?」那麼您可能錯過了一則新聞,那就是在這個 EF5 版本之後,Entity Framework 成為一個開源專案,並且將在 entityframework.codeplex.com 上公開(並在社區中)開發後續版本。我最近撰寫了一片博客帖子,標題是「開源 Entity Framework CodePlex 網站伴你前行」(bit.ly/W9eqZS),我建議大家不要急著去看那些 CodePlex 頁面,先看看我這一篇文章。

這一新版本將在提高 EF 的靈活性和可擴充性方面取得長足進步。在我看來, EF V6 中最為重要的三項功能是:

  1. 支援代碼優先的預存程序和函數
  2. 支援 .NET 4.5 的 Async/Await模式
  3. Microsoft .NET Framework 中當前提供的核心 Entity Framework API

這最後一點,不僅使得枚舉和空間類型支援面向 .NET Framework 4 的應用程式,並且,由於 EF 是開源的,也就意味著所有 EF 現在都能因開源而受益。

雖然這三種功能可能並沒有廣泛吸引開發人員的目光,但有很多其他重要功能將要推出。For example:

  • 自訂「代碼優先」約定早在 EF4.1 發佈之前就已推出,目前在 EF6 中採用了各種不同方法實現。
  • 「代碼優先」遷移支援多種資料庫模式。
  • 您可以在代碼中定義 Entity Framework 配置,而不是在 web.config 或 app.config 檔中進行設置(這樣做會比較複雜)。
  • 由於現在可通過依賴關係解析程式支援可擴充性,基於代碼進行配置變得可行。
  • 您可以自訂「代碼優先」創建 _Migrations­History 表的方式,從而更加輕鬆地適應各種資料庫提供程式。
  • EF Power Tools 將得到增強並添加到 Visual Studio EF 設計器中。一個增強之處就是將提供更好的途徑,方便選擇模型工作流程,包括代碼優先。

獲取 EF6 Alpha

最熱心的開發人員可能熱衷於下載每夜構建版本。如果您更願意使用發佈的套裝程式,則您在安裝 NuGet 套裝程式時可獲得流暢的安裝體驗。使用 NuGet 套裝程式管理器並選擇「包括預發佈版」以獲取 EF6 套裝程式。如果從套裝程式管理器主控台進行安裝,請務必在「install-package」命令結尾處增加「-prerelease」。

請注意,2012 年12 月 10 日,我正在探索的版本(檔版本為 6.0.11025.0,產品版本為 6.0.0-Alpha2-11210)並不包括預存程序或函數支援,或者工具整合。此外,因為這還是一個較早的 Alpha 版本,我估計這些功能中的一些細節在新版本發佈時會有所變化。儘管總體概念將保持不變,但一些語法或其他細節很可能會根據來自社區的回饋有所演進。在此專欄進行一番練習後,我自己也提供了一些回饋意見。

EF6 對 .NET 4.5 Async 的支援

利用 .NET Framework 中的非同步處理功能,可以在等待資料返回時避免阻塞。這種阻塞,尤其是使用未連接的應用程式或遠端資料庫時,常常令開發人員失去耐心。.NET Framework 2.0 中就引入了 Background Worker 進程,但當時它仍然很複雜。ADO.NET 2.0 對於 BeginExecuteQuery 和 EndExecuteQuery 之類的方法起到了一些説明,但是 Entity Framework 從未有過類似的功能。.NET Framework 4.5 中增加的重大功能之一是新的非同步模式,在這種模式下,可以等待定義為「非同步」的方法返回結果,從而大大簡化了非同步處理。

在 EF6 中,增加了大量支援 .NET 4.5 非同步模式的新方法。按照新模式的準則,新方法全都在其名稱後附加了「Async」,如 SaveChangesAsync、FindAsync 和 Execute­SqlCommandAsync。對於 LINQ to Entities,新增了一個名為 System.Data.Entity.IQueryableExtensions 名稱空間,其中包含了很多 LINQ 方法的 Async 版本,包括 ToListAsync、First­Or­DefaultAsync、MaxAsync 和 SumAsync。此外,為了從 DbCoNtext 管理的實體顯式載入資料,現在提供了 LoadAsync。

接下來介紹一下我針對此項功能進行的一個小試驗,在試驗中,我先不使用非同步方法,然後再使用非同步方法。我強烈建議大家仔細研究一下新的 Async 模式。您可以從 bit.ly/U8FzhP 處的「使用 Async 和 Await 進行非同步程式設計(C# 和 Visual Basic)」著手。

我的示例中包含一個 Casino 類,該類中包含對博彩的評級。我創建了一個存儲庫方法,該方法將查找指定的博彩、使用我的 UpdateRating 方法(此方法在此處無關緊要,因此未列出)增加其評級並將更改保存回資料庫:

public void IncrementCasinoRating(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino =  context.Casinos.Find(id);
    UpdateRating(casino);
    context.SaveChanges();
  }
}

在此方法中有兩處可能會阻塞執行緒。 第一處是調用 Find 時,這會導致上下文先搜索記憶體中的緩存查找所請求的博彩,如果找不到則會查詢資料庫。 第二處是要求上下文將修改後的資料保存回資料庫時。

我僅僅搭建了我的 UI 代碼來演示關鍵的行為。 該 UI 包括調用存儲庫 IncrementCasinoRating 的方法,該方法完成時將在主控台上顯示一個通知:

private static void UI_RequestIncrement (SimpleRepository repo)
{
  repo.IncrementCasinoRating(1);
  Console.WriteLine("Synchronous Finish ");
}

在另一個方法中,我通過調用 UI_ Increment­CasinoRating 來觸發此測試,並在此之後顯示另一條通知訊息:

UI_RequestIncrement (repo);
Console.WriteLine(" After sync call");

運行此代碼時,會在主控台輸出中看到以下內容:

Synchronous Finish
After sync call

這是因為在等待 IncrementCasinoRating 中每一個步驟(包括查找 casino、更新評級和保存到資料庫)完成時,所有語句都運行完畢。

現在我將更改一下存儲庫方法,使用新的 FindAsync 和 SaveChangesAsync 方法。 為遵循非同步模式,我還需要通過以下操作使該方法成為非同步方法:

  • 將 async 關鍵字添加到其簽名中
  • 將 Async 追加到方法名稱結尾
  • 返回一個 Task,如果方法返回結果,則令其返回 Task<resulttype>

在該方法內,我將按照規定,使用 await 關鍵字調用新的 Async 方法,即 FindAsync 和 SaveChangesAsync:

public async Task IncrementCasinoRatingAsync(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino=await context.Casinos.FindAsync(id);
    // Rest is delayed until await has received results
    UpdateRating(casino);
    await context.SaveChangesAsync();
    // Method completion is delayed until await has received results
  }
}

因為該方法標有 async,一旦遇到第一個 await,該方法就會將控制返回給調用進程。 但是我需要修改該調用方法。 構建非同步模式的行為時,有一個「瀑布式」的方法,因此不僅該方法要特別調用我的新異步方法,它本身也需要是非同步方法,因為它將被另一個進程調用:

private static async void UI_RequestIncrementAsync(
  SimpleRepository repo)
{
  await repo.IncrementCasinoRatingAsync(1);
  // Rest is delayed until await has received results
  Console.WriteLine(" Asynchronous Finish ");
}

請注意,我使用了 await 來調用存儲庫方法。 這就能讓調用方知道這是一個非同步方法。 一旦運行到 await 的位置,控制就會交還給調用方。 我還修改了啟動代碼:

UI_RequestIncrementAsync(repo);
  Console.WriteLine(" After asynchronous call");

現在,當我運行此段代碼時,UI_RequestIncrementAsync 方法會將控制返回給調用方,且它也會調用存儲庫方法。 That means I’ll immediately get to the next line of startup code, which prints out “After asynchronous call.” When the repository method finishes saving to the database, it returns a Task to the method that called it, UI_RequestIncrementAsync, which then executes the rest of its code, printing out a message to the console:

After asynchronous call
Asynchronous Finish

因此,我的 UI 無需等待 EF 完成其工作,就能自行完成。 如果存儲庫方法返回了結果,則這些結果會在準備就緒時在 Task 中冒出來。

這個小小的試驗説明我實際體驗了新的非同步方法。 不管您是在編寫依賴于非同步處理的用戶端應用程式還是未連接的應用程式,您都能因 EF6 現在如此簡便地支援新異步模式而獲益。

自訂約定

「代碼優先」有一組內置的約定,在構建具有資料庫映射(源自您的類)的模型時驅動其預設行為。 您可以使用 DataAnnotations 或 Fluent API 定義的顯式配置將其覆蓋。 在「代碼優先」的早期測試版中,您也可以定義您自己的約定。例如將所有字串設置為映射到最大長度 50 的資料庫欄位。 不幸的是,該團隊未能在不使用「代碼優先」的情況下將此功能編寫到一個令人滿意的狀態,因此最終發佈版中未採用此功能。 現在此功能又在 EF6 中回歸。

有幾種方法可以定義您自己的約定。

最簡單的方法是使用羽量級約定。 您可以使用這種方法在 DbCoNtext 的 OnModelCreating 重載中流暢地指定約定。 羽量級約定應用於您的類,並限制為在資料庫中具有直接關聯的配置屬性,如長度限制為 MaxLength。 下面是一個沒有特殊配置的類的示例,因此,預設情況下,它的兩個字串欄位要映射到一個 SQL Server 資料庫中的 Nvarchar(max) 資料類型:

public class Hotel
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
  }

我已經在模型中添加了一個羽量級約定,指定 API 應該檢查其處理的任何實體的屬性,並將字串的 MaxLength 設置為 50:

modelBuilder.Properties<string>()
  .Configure(p => p.HasColumnType("nvarchar"));

圖 1 中,您可以看到「代碼優先」確保了「Name」和 「Description」欄位的最大長度為 50。

A Custom Convention Made the Max Length of These nvarchars 50
圖 1 將這些 Nvarchar 的最大長度設置為 50 的一個自訂約定

您也可以通過實施現有介面(如用於處理 DateTime 屬性的約定介面,即 System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive 名稱空間中的 DateTimePropertyConfiguration 類)來定義約定。 圖 2 顯示了一個示例,在該示例中,我強制 DateTime 屬性對應到 SQL Server 的 date 類型而不是預設的 datetime。 請注意,此示例遵從了 Microsoft 指南,如果屬性(在此例中為 ColumnType)已經配置,則我不會應用我的配置。

圖 2 將 DateTime 屬性對應到 SQL Server 的 date 類型

public  class DateTimeColumnTypeConvention :
  IConfigurationConvention<PropertyInfo, DateTimePropertyConfiguration>
  {
    public void Apply(
      PropertyInfo propertyInfo,
      Func<DateTimePropertyConfiguration> configuration)
    {
      // If ColumnType hasn't been configured ...
if (configuration().ColumnType == null)
      {
        configuration().ColumnType = "date";
      }
    }
  }

應用約定時要遵守特定而嚴格的先後順序,這就是您必須在應用新的 ColumnType 之前確保列類型尚未配置的原因。

模型產生器需要知道如何找到這個新的約定。 下面再一次基於 OnModelCreating 重載方法來說明如何執行此操作:

modelBuilder.Conventions.Add(new DateTimeColumnTypeConvention());

另外還有兩種方法可自訂約定。一種方法允許您創建您可像 DataAnnotations 一樣在您的類中輕鬆使用的自訂屬性。另一種方法則更具體:此方法並不依賴模型產生器從您的類收集資訊來構建一個約定,而是允許您直接影響中繼資料。在 MSDN 資料開發人員中心文檔「自訂代碼優先約定」(msdn.microsoft.com/data/jj819164) 中,您可以找到全部 4 種自訂約定的示例。隨著 EF6 的不斷演進,此文檔將包含指向更新版本的連結,或者修改為與最新版本一致。

針對遷移的多模式支援

EF6 對「代碼優先」遷移提供了處理資料庫中多種模式的能力。有關此功能的更多資訊,請查看我在 Alpha 發佈後不久撰寫的一篇詳細的博客帖子:「深入探討如何使用 EF6 Alpha 進行多租戶遷移」(bit.ly/Rrz1MD)。但是,請務必記住,此功能的名稱已經從「多租戶遷移」更改為「每資料庫多上下文」。

基於代碼的配置

您已可以在應用程式佈建檔(app.config 和 web.config)中為 Entity Framework 指定資料庫相關的配置,這樣就無需在應用程式啟動時或在上下文構造器中提供配置。現在,在 EF6 中,可以通過繼承新的 DbConfiguration 類創建一個類,您可以在該類中指定一些詳細資訊,如代碼優先的預設資料庫提供程式,資料庫初始化策略(如 DropCreateDatabaseIfModelChanges)等等。您可以與在您的上下文相同的專案中創建此 DbConfiguration 類,也可以在另一個不同專案中創建,這樣就可允許多個上下文從單個配置類中獲取資訊。基於代碼的配置概述(msdn.microsoft.com/data/jj680699)中提供了各種選項的示例。

核心 EF 現在於 EF6 中提供,並且也是開源的

雖然代碼優先和 DbCoNtext API 一直與 .NET 的發佈週期並無關聯,但 EF 核心已經嵌入到 .NET Framework 中。核心功能是 ObjectCoNtext API、查詢、更改跟蹤、Entity­Client 提供程式以及更多功能。這就是對枚舉和空間資料的支援必須要等到 .NET Framework 4.5 發佈才能提供的原因 - 這些更改已經深深嵌入到核心 API 中。

在 EF6 中,所有這些核心 API 都已經在此開源專案中採用,並將通過 NuGet 套裝程式部署。正如圖 3 所示,EF5 和 EF6 的名稱空間對照顯示可見端倪。正如您所見,EF6 中名稱空間壯大了許多。

EF6 Has Acquired the Namespaces of the EF Core APIs from the .NET Framework
圖 3 EF6 已經採用了 .NET Framework 中 EF 核心 API 的名稱空間

針對 .NET 4 應用程式的枚舉和空間支援

正如我之前所提到的,在 EF6 中併入核心 API 的最大好處之一就是,它為 EF 特定的功能消除了對 .NET 版本的一些依賴,最顯著的就是在 .NET Framework 4.5 中添加到 EF 中的枚舉和空間資料支援。使用 EF 構建的面向 .NET Framework 4 的現有應用程式,在 EF5 中無法獲得這一優勢。現在,這一限制已不再存在,因為 EF6 提供了枚舉和空間支援,並且這些功能不再依賴于 .NET Framework。有關這些功能的現有文檔可以説明您在面向 .NET Framework 4 的應用程式中使用。

説明推動 EF 的演進

現在 Entity Framework 已成為開源專案,開發人員可以參與到其設計和開發中來,助人助己。無論您是在閱讀規範、參與討論和問題的解決、使用最新 NuGet 套裝程式或獲取每夜構建版本並就這些版本進行深入討論時,您都可以分享您的想法、建議和意見。您還可以為網站上列出的目前還無人著手解決的問題貢獻代碼,或者也可以為您自己在 EF 中必須要完善的功能編寫代碼。

Julie Lerman* 是 Microsoft MVP、.NET 導師和顧問,住在佛蒙特州的山區。您可以在全球的使用者組和會議中看到她對資料訪問和其他 Microsoft .NET 主題的演示。她是《Programming Entity Framework》(2010) 以及「代碼優先」版 (2011) 和 DbCoNtext 版 (2012)(均出自 O’Reilly Media)的作者,博客網址為 thedatafarm.com/blog。請關注她的 Twitter:twitter.com/julielerman。*

衷心感謝以下技術專家對本文的審閱:Glenn Condron