Share via


本文章是由機器翻譯。

多層式架構 (N-Tier) 應用程式與 Entity Framework

使用 EF4 建置多層式架構 (N-Tier) 應用程式

Daniel Simmons

下載程式碼範例

本文是關於 n 數列中的第三個-層使用 Entity Framework (請參閱 msdn.microsoft.com/magazine/dd882522.aspxmsdn.microsoft.com/magazine/ee321569.aspx ) 特別有關建置自訂的 Web 服務的實體架構 (EF) 和 Windows Communication Foundation (WCF) 進行程式設計。 (在某些情況下 REST 為基礎的服務或其他的方法是適當,但這些文件中,我著重在自訂的 Web 服務)。第一個發行項所描述了數個重要的設計考量和 antipatterns。 在第二個本文我寫了可用於 n 的成功的大約四種模式-層應用程式。 該發行項也包含的程式碼範例,說明如何使用 Entity Framework (EF 3.5 SP1) 的第一版來實作我稱之為簡易實體圖樣。 在這篇文章我查看下來 Entity Framework (EF4) 的第二版和如何使用它們來實作 Self-Tracking 實體和資料傳輸物件 (DTOs) n -層中的某些功能的模式。

雖然簡單實體並非通常 n 慣用的圖樣-層應用程式,它是 [EF 最可行的選項在第一個版本中。  EF4,但是,大幅變更為 n 選項-層程式設計架構。  索引鍵的新功能包括下列:

  1. 新支援的架構方法中斷 ChangeObjectState 及 ChangeRelationshipState,改為新的狀態 (新增或修改,例如) 的實體或關係等作業,可讓您的 ApplyOriginalValues 設定實體 ; 以及新 ObjectMaterialized 事件,引發每當實體建立由架構原始的值。
  2. 對於一般舊 CLR 物件 (POCO) 和在實體上的外部索引鍵值的支援。 這些功能可讓您建立可共用 mid-tier 服務實作與其他層之間的實體類別而不能有相同版本的 Entity Framework (.NET 2.0 或 Silverlight,例如)。 具有外部索引鍵的 POCO 物件也有直接了當的序列化格式,可簡化與平台和 Java 一樣的互通性。 使用外部索引鍵也可以讓多較簡單並行存取模型的關聯性。
  3. 若要自訂程式碼產生的 T4 範本。 這些範本提供的方法來產生類別實作 Self-Tracking 實體或 DTOs 圖樣。

Entity Framework 團隊已經用於這些功能使該模式更更易存取範本中實作 Self-Tracking 實體模式且而 DTOs 仍然需要最多的工作在初始實作期間,此處理程序也更容易與 EF4。 (Self-Tracking 實體範本和一些其他 EF 功能都可以為部分 Web 下載功能社群技術預覽 (CTP) 而非在 Visual Studio 2010/.NET 4] 方塊中。 本文中的範例假設安裝 Visual Studio 2010/.NET 4 和功能 CTP)。這些新功能與評估我描述 (簡易的實體、 變更集、 Self-Tracking 實體及 DTOs) 的四種模式的方法之一是方面的架構善良 (分隔的考量/失去結合性的合約、 有效率 Wire 格式和互通性的強度) 和容易實作和產品上市的時間之間的取捨。 如果您繪製圖形代表此取捨上的四個圖樣,結果會看起來像一個 的 圖 1


圖 1 比較 N -以 EF4 層圖樣

取得或設定特定的情況下右模式取決於許多因素。 在一般 DTOs 提供許多架構上的優點,較高的初始實作的成本。 變更設定幾個良好的架構特性的展示區,但容易實作 (它 ’s 可用的特定技術 —,例如在傳統的 ADO.NET 資料集)。

我建議之間開頭 Self-Tracking 實體並移動到 DTOs,如果這種情況擔保這些考量 pragmatic/靈活的平衡。 通常,可以 Self-Tracking 實體與快速取得啟動且正在執行,並仍達成許多重要的架構目標。 這種方法代表多更好比變更集或其中之一我會建議僅如果您有沒有可行的選項的簡單實體取捨。 DTOs,另一方面,是明確地設定最佳的選擇為您的應用程式變得更大型且更複雜或如果您有 can’t Self-Tracking 實體由不同的工資率變更用戶端與伺服器之間的像符合需求。 這些兩個模式都有您的工具箱中,請因此 let’s 看看每個最重要的工具。

self-tracking 實體

若要使用這個模式使用 Entity Framework,開始建立Entity Data Model,代表您概念性的實體,並對應到資料庫。 您可以從資料庫,您有,然後自訂它,反向工程模型,或者您可以從頭開始建立模型,然後產生資料庫,以符合 (EF4 中的另一個新功能)。 這個模型和對應放置之後,取代預設的程式碼產生範本 Self-Tracking 實體範本實體設計工具介面上按一下滑鼠右鍵,並選擇 [加入程式碼產生項目。

接下來,從已安裝的範本的清單中選擇 Self-Tracking 實體範本。 預設此步驟關閉程式碼產生,並將兩個範本加入至您的專案:一個範本產生 [ObjectContext 及其他範本會產生實體類別。 分隔成兩個範本產生的程式碼,可讓分割成個別的組件程式碼,一個用於實體類別,一個用於您的內容。

這種方法的主要優點是您可以擁有實體類別 Entity Framework 有沒有相依性的組件中。 這種方式、 實體組件 (或至少它會產生程式碼),並且您已那里實作任何商務邏輯可以共用由 mid-tier 和用戶端如果您想。 內容保存在具有相依性之實體及 [EF 的組件。 如果您的服務用戶端執行.NET 4,您就可以從用戶端專案,只是參考實體組件。 如果您的用戶端執行較早版本的.NET 或正在執行 Silverlight,您可能要將連結從用戶端專案加入至產生的檔案,並重新編譯實體來源 (目標設定適當的 CLR) 該專案中。

不論如何結構您的專案,兩個範本一起實作 Self-Tracking 實體模式。 產生的實體類別是其唯一的功能,超越基本儲存裝置的實體屬性是追蹤的變更實體的簡單 POCO 類別 — 的實體整體的狀態變更為重要的屬性,例如並行存取語彙基元和實體之間的關聯性的變更。 這個額外的追蹤資訊是實體的 DataContract 定義的一部份 (所以當您傳送實體至或來自 WCF 服務,追蹤資訊攜帶沿著)。

在服務的用戶端,實體來修訂自動即使該實體不會附加到任何內容。 每個產生的實體具有類似下列的每個屬性的程式碼。 如果您變更屬性值不變的狀態與實體上,比方說狀態會變更為已修改:

[DataMember]
public string ContactName
{
    get { return _contactName; }
    set
    {
            if (!Equals(_contactName, value))
            {
                _contactName = value;
                OnPropertyChanged("ContactName");
            }
    }
}
private string _contactName;

同樣地,如果新的實體會新增到圖表或實體會從圖表刪除,被追蹤該資訊。 因為實體本身上追蹤的每個實體狀態,追蹤機制的行為就如您會預期甚至當您關聯實體擷取來自一個以上的服務呼叫。 如果您在建立新的關聯性是只會追蹤變更 — 實體涉及保持在相同的狀態,如同它們有全部被從擷取單一服務呼叫。

內容樣板會加入新的方法 [ApplyChanges,至產生的內容。 ApplyChanges 內容來附加實體的圖表,並設定以符合在實體上追蹤資訊 ObjectStateManager 中的資訊。 以資訊實體追蹤有關本身以及 ApplyChanges 產生的程式碼處理兩者變更追蹤和並行考量的正確實作 n 最困難的組件的兩種-層方案。

具體範例 的 圖 2 顯示您可以使用來建立 n -層 Self-Tracking 實體的簡單 ServiceContract 訂購送出系統根據北風貿易範例資料庫。

圖 2 的 Self-Tracking 實體圖樣的簡單服務合約

[ServiceContract]
public interface INorthwindSTEService
{
    [OperationContract]
    IEnumerable<Product> GetProducts();

    [OperationContract]
    Customer GetCustomer(string id);

    [OperationContract]
    bool SubmitOrder(Order order);

    [OperationContract]
    bool UpdateProduct(Product product);
}

GetProducts 服務方法用來擷取關於產品目錄用戶端上的參考資料。 這項資訊通常本機快取,而且 isn’t 通常更新用戶端上。 GetCustomer 擷取客戶以及客戶 ’s 訂單清單。  該方法的實作是相當簡單,如下所示:

public Customer GetCustomer(string id)
{
    using (var ctx = new NorthwindEntities())
    {
        return ctx.Customers.Include("Orders")
        .Where(c => c.CustomerID == id)
        .SingleOrDefault();
    }
}

這是基本上是相同的程式碼會為這種方法具有簡單實體模式的實作撰寫。 不同之處在於所傳回的實體是 self-tracking,也就是說使用這些方法的用戶端程式碼也是相當簡單,但是它可以完成很多。

為了說明,let’s 假設訂單送出程序中您想要不只以適當的順序詳細資料行建立的順序同時也以最新的連絡人資訊更新客戶實體的部份。 進一步,您想要刪除任何具有一個 null [訂單日期] (已拒絕的訂單這種方法也許系統會標示) 的訂單。 以簡單的實體的模式加入、 修改和刪除實體單一圖表中的組合會需要多個服務呼叫,每一種類型的作業或非常複雜的自訂合約與服務實作,如果您嘗試實作的東西在第一版的 [EF 想 Self-Tracking 實體。 EF4,與用戶端程式碼可能看起來 的 圖 3

圖 3 的 Self-Tracking 實體圖樣的用戶端程式碼

var svc = new ChannelFactory<INorthwindSTEService>(
    "INorthwindSTEService")
    .CreateChannel();

var products = new List<Product>(svc.GetProducts());
var customer = svc.GetCustomer("ALFKI");

customer.ContactName = "Bill Gates";

foreach (var order in customer.Orders
    .Where(o => o.OrderDate == null).ToList())
{
    customer.Orders.Remove(order);
}

var newOrder = new Order();
newOrder.Order_Details.Add(new Order_Detail()
    {
        ProductID = products.Where(p => p.ProductName == "Chai")
                    .Single().ProductID,
        Quantity = 1
    });
customer.Orders.Add(newOrder);

var success = svc.SubmitOrder(newOrder);

這個程式碼會建立服務、 呼叫前兩個方法,以取得產品清單和客戶的實體,並再對客戶實體圖表使用相同的排序,如果您已建置一個交談直接到資料庫的兩層 Entity Framework 應用程式或已在 mid-tier 上實作服務會撰寫程式碼的變更。 (如果您 aren’t 熟悉此樣式的建立 WCF 服務用戶端,它會自動建立而不需要建立這些實體的 Proxy,因為我們重複使用的實體類別從 Self-Tracking 實體範本為您用戶端 Proxy。 您可能也使用 Visual Studio 中的 [加入服務參考] 命令所產生,如果您想用戶端)。但是這裡,沒有 ObjectContext 相關。 您只需操控實體本身。 最後,用戶端會呼叫發送所做的變更到 mid-tier SubmitOrder 服務方法。

當然在實際的應用程式中圖形的用戶端 ’s 變更可能會有來自某些排序和您的使用者介面會加入例外處理 (尤其重要當您有透過網路進行通訊時) 的服務呼叫周圍但 的 圖 3 在程式碼將說明原則。 注意到另一個重要的項目是可當您建立新的順序在順序詳細實體,將只是 [產品編號] 屬性,而不是產品實體本身。 這是新的外部索引鍵關聯性功能,作用中。 它可減少因為序列化只回到的 mid-tier 產品實體複本產品編號,透過網路傳輸機密資訊的量。

它 ’s Self-Tracking 實體真的 shines SubmitOrder 服務方法的實作中:

public bool SubmitOrder(Order newOrder)
{
    using (var ctx = new NorthwindEntities())
    {
        ctx.Orders.ApplyChanges(newOrder);
        ValidateNewOrderSubmission(ctx, newOrder);
        return ctx.SaveChanges() > 0;
    }
}

對 ApplyChanges 呼叫會執行所有神奇。 它會從實體讀取變更資訊,並將它套用至,使得結果相同好像有實體附加至內容整個時間上執行這些變更的內容。

驗證變更

其他您應該注意到在 SubmitOrder 實作是以 ValidateNewOrderSubmission 呼叫。 這個服務的實作中新增的方法會檢查以確定的我們預期在 SubmitOrder 的呼叫中的變更類型都存在 ObjectStateManager。

這個步驟是真正重要的因為本身,ApplyChanges 推入到內容中找到相關物件的整個圖形中的任何變更。 我們期望用戶端將只加入新的訂單、 更新客戶等等 doesn’t 表示錯誤或甚至惡意的用戶端不會其他項目。 應該要是變更它上使訂單成本較低或比它更昂貴的產品價格吗? 如何執行驗證的詳細資料都比重要的規則您應該永遠儲存到資料庫之前先驗證變更較不重要。 這個規則套用不論 n -層圖樣您使用。

第二個重要的設計原則是您應該開發每個作業的不同,特定服務方法。 這些個別的作業沒有您沒有強式合約,其代表什麼是及 isn’t 允許您兩個層之間,而且正確驗證您所做的變更事變得更為重要。 如果您有單一的 SaveEntities 服務方法而非一個 SubmitOrder 和個別 UpdateProduct 方法 (由修改產品目錄的權限的使用者只能存取,) 您也可以輕鬆地實作套用,儲存該方法的組件但就無法正確確認,因為您必須知道時允許產品更新和當它們不是沒有辦法。

資料傳輸物件

Self-Tracking 實體模式使 n -層簡單,處理程序,如果您建立特定的服務方法,並驗證每一個,它可以是相當音效 architecturally。 甚至因此會限制您可以使用此模式進行的工作。 當您執行到這些限制時,DTOs 會是超出進退兩難的方式。

在共享 mid-tier] 及 [用戶端之間的單一實體實作的 DTOs 建立只適用於透過服務傳送資料的自訂物件,並開發個別的實體實作 mid-tier 和用戶端。 這項變更提供兩個主要優點:它將隔離您的服務合約從 mid-tier 和允許該合約,以保持穩定,即使在各層的變更和它實作可讓您控制哪些資料流向網路上的用戶端上實作問題。 因此,您可以避免傳送不必要的資料 (或用戶端不允許存取的資料) 或調整其形狀,使其更便於用來服務資料。 通常,服務合約所設計與用戶端案例,請記得,這樣可重繪 mid-tier 實體和 DTOs (或許藉由將多個項目結合成一個 DTO 並略過不需要在用戶端屬性) 之間的資料,且同時 [DTOs 可直接在用戶端上使用。

這些優點不過,來自於讓建立和維護的物件和對應的一或兩個多個圖層的價格。 若要將訂單送出範例的延伸,您可以建立剛才的目的送出新的訂單類別。 這個類別會結合客戶實體的順序從新的順序案例中設定的屬性使用的屬性,但類別會保留出屬性從 mid-tier 計算這兩個實體或設定在處理序中其他的階段。 這樣 [DTO 為小型和盡可能有效率。 實作看起來會像這樣:

public class NewOrderDTO
{
    public string CustomerID { get; set; }
    public string ContactName { get; set; }
    public byte[] CustomerVersion { get; set; }
    public List<NewOrderLine> Lines { get; set; }
}

public class NewOrderLine
{
    public int ProductID { get; set; }
    public short Quantity { get; set; }
}

好吧,這真的是兩個類別 — 一個用於順序,一個用於訂單詳細資料行 — 但資料大小會保持越小越。 唯一表面上沒有直接關聯中的資訊程式碼有包含用於客戶實體上的並行存取檢查的資料列版本資訊的 [CustomerVersion] 欄位。 對客戶實體因為實體已經存在資料庫中,您需要此資訊。 [順序] 和 [詳細資料] 線的那些是因此 aren’t 需要它們的版本資訊和訂單編號被送出至該資料庫的新實體 — 由資料庫所中產生,保存所做的變更時。

服務方法接受 (Accept) 此 DTO 使用同一個較低層級實體 Framework API Self-Tracking 實體範本使用來完成其工作,但現在您需要直接呼叫這些 API,而不是讓為您呼叫產生的程式碼。 實作進來兩個部分。 第一次,您建立的客戶、 訂單及訂單圖表根據 [DTO 中資訊的詳細資料實體 (請參閱 的 圖 4)。

圖 4 建立實體的 Graph

var customer = new Customer
    {
        CustomerID = newOrderDTO.CustomerID,
        ContactName = newOrderDTO.ContactName,
        Version = newOrderDTO.CustomerVersion,
    };

var order = new Order
    {
        Customer = customer,
    };

foreach (var line in newOrderDTO.Lines)
{
    order.Order_Details.Add(new Order_Detail
        {
            ProductID = line.ProductID,
            Quantity = line.Quantity,
        });
}

然後您附加至內容的圖形,並設定適當的狀態資訊:

ctx.Customers.Attach(customer);
var customerEntry = ctx.ObjectStateManager.GetObjectStateEntry(customer);
customerEntry.SetModified();
customerEntry.SetModifiedProperty("ContactName");

ctx.ObjectStateManager.ChangeObjectState(order, EntityState.Added);
foreach (var order_detail in order.Order_Details)
{
    ctx.ObjectStateManager.ChangeObjectState(order_detail, 
       EntityState.Added);
}

return ctx.SaveChanges() > 0;

第一行將整個圖表貼附到內容但發生這種情況時每個實體處於不變的狀態,所以第一次您告訴 ObjectStateManager 放入客戶實體,已修改的狀態,但只有一個屬性,[連絡人],標示為已修改。 這點非常重要,因為您實際上 don’t 的所有客戶資訊 — 就只是處於 [DTO 該資訊。 如果您標示為已修改的所有屬性,Entity Framework 會嘗試保存堆 Null 值和零至客戶實體中其他欄位。

接下來您變更狀態的順序和其訂單詳細資料的每個要加入,然後您呼叫 SaveChanges。

嘿,位置 ’s 驗證程式碼? 在這種情況下因為的您的案例有非常特定 DTO,而且您會解譯該物件,如對應到您的實體將資訊從它您執行驗證一邊。 有 ’s 這段程式碼可能會不慎變更產品的價格,因為您從未碰觸到產品實體沒有辦法。 這是另一個好處 DTO] 模式的而只在一個 roundabout 方法。 您仍有進行驗證工作 ; 模式只會強迫一個層級的驗證。 在許多情況下您的程式碼需要包含額外的驗證的值或其他商務規則。

一個其他考量正確處理並行存取例外狀況。 如我先前所述,客戶實體的版本資訊包含在 [DTO,所以您會設定來正確地偵測並行的問題,如果其他人修改相同的客戶。 讓用戶端可以解決此衝突,或它會攔截的例外狀況,並套用某種自動處理衝突的原則更完整的範例可能會到 WCF 錯誤對應這個例外狀況。

如果您想要新增像能夠修改的訂單的另一個作業來擴充範例進一步您只是右資訊為它建立另一個 DTO 特別為該分析藍本。  這個物件會看起來像我們 NewOrderDTO,不過它會有訂單和訂單詳細資料的實體的 [訂單編號] 和 [版本] 屬性,以及您想讓服務呼叫,以更新的每個屬性。 服務方法實作也是類似於前面所示的 SubmitOrderDTO 方法 — 逐步解說 DTO 資料、 建立對應的實體物件,然後狀態管理員中,先將變更儲存至資料庫設定它們的狀態。

如果您實作順序更新方法,同時具有 Self-Tracking 實體和資料傳輸物件您會發現 Self-Tracking 實體實作會重複使用的實體,而且股份幾乎完全相同服務實作程式碼,它和新的訂單送出方法之間 — 唯一的差異會是該驗證程式碼,可能會共用的甚至一些。 DTO 的實作不過,需要個別的資料傳輸物件類別每個兩個服務的方法,而且方法實作遵循類似模式,但有很少,如果有任何可共用的程式碼。

從 trenches 的秘訣

以下是要注意的並了解什麼的一些秘訣。

  • 請確定重複使用 Self-Tracking 實體範本 ’s 產生的實體程式碼,在您用戶端。 如果您使用 Visual Studio 或其他一些工具中加入服務參考所產生事情大多數的情況下,尋找正確的 Proxy 程式碼,但您會發現,實體 don’t 實際追蹤的他們在用戶端上所做的變更。
  • 建立新的 ObjectContext 執行個體,每個服務方法的使用陳述式中,這樣的處置之前方法傳回。 這個步驟是服務的對您的延展性很重要。 它可確保資料庫連接不保留開啟跨服務呼叫,特定的作業所用的暫存狀態是記憶體回收收集時作業是透過。 Entity Framework 自動快取中繼資料和其他應用程式] 網域中所需的資訊,並 ADO.NET 集區資料庫連接,所以每次重新建立內容是一個快速的操作。
  • 使用新的外部索引鍵關聯性功能每當盡可能  它會變更更容易的實體之間的關係。 與獨立關聯 — 唯一 Entity Framework 的第一版中可用的關聯性的類型 — 在獨立的實體上, 執行的並行存取檢查的關聯性上執行並行存取檢查,而且沒有沒有辦法 opt 超出這些關聯性並行存取檢查。 結果會是您的服務必須執行原始值的關聯性,並設定它們在內容變更關聯性之前。 與外部索引鍵關係不過,關聯性是只是在實體的屬性,而且實體通過其並行存取檢查,沒有進一步檢查必要時。 您只可以變更外部索引鍵的值來變更關聯性。
  • 小心 EntityKey 衝突時附加圖形至 ObjectContext。 如果對於執行個體使用 DTOs 與您圖表的部分代表新加入的實體關鍵值尚未設定因為將在資料庫中產生,您應該呼叫 
AddObject 方法第一次新增整個圖表的實體] 設為預期的狀態變更實體不在加入狀態而不呼叫附加方法,然後變更加入該狀態實體的實體。 否則,當您第一次呼叫附加,Entity Framework 認為每個實體應該被放入不變假設實體關鍵值都是最終的狀態。 如果特定類型的多個實體有相同的金鑰值 (0,例如),Entity Framework 會擲回例外狀況。 啟動加入狀態中的實體來您避免這個問題,因為架構不會不預期要有唯一索引鍵的值加入實體。
  • 關閉自動延遲載入 (另一個新的 EF4 功能) 時從服務方法傳回的實體。 如果您 don’t 序列化程式將會觸發延遲載入並嘗試從資料庫將導致超過您打算要傳回的資料擷取其他實體 (如果您的實體徹底連線,您可能會序列化整個資料庫,) 或因為內容會處置的序列化程式會嘗試擷取資料之前,所以您會更有可能收到錯誤訊息。 self-Tracking 實體並沒有根據預設值,開啟的延遲載入,但如果您正在建立 DTOs 方案,這是值得注意的。

然後在結束

Entity Framework.NET 4 發行使得 architecturally 聽 n -多層應用程式的建立更容易。 對於大部分應用程式建議開頭 Self-Tracking 實體範本簡化程序,並可讓最重複使用。 如果您有不同的工資率的服務與用戶端,之間的變更,或需要絕對控制 Wire 格式,您應該向上移動以資料傳送物件實作。 不論您選擇哪一種模式,永遠牢記金鑰 antipatterns 和圖樣表示的原則 — 和永遠不會忘記要驗證您的資料儲存之前。

Daniel Simmons 是架構設計人員,在 Microsoft Entity Framework 團隊中。