本文將介紹以下內容:
實體框架背後的原理
實體數據模型
查詢、映射和 n 層開發
本文使用了以下技術:
ADO.NET、LINQ、實體框架
框架概念最初是在 2006 年作為 ADO.NET vNext 引入的,現在它已准備好要在即將發布的 Visual Studio® 2008 SP1 中大顯身手。在歷經多年對類似產品的多次失敗嘗試後,Microsoft 隨 Visual Studio 2008 發布了部分適合於對象關系映射 (ORM) 空間的下列兩種技術:LINQ to SQL 和 ADO.NET Entity Framework。隨著市場逐漸開始采用這些技術,開發人員希望了解我們目前的狀況以及 Microsoft 未來的發展方向。他們還希望了解這些技術開發背後的原理、實體框架不同於市場上其他 ORM 技術的原因以及 Microsoft 對這些技術的投資趨向。
在 Visual Studio 2008 發布伊始,就出現了大量有關 LINQ to SQL 的文章以及介紹應采用哪種技術的文章。在此我將著重介紹實體框架,並深入探討在開發過程中應該如何進行選擇以及為何如此選擇。
Microsoft® 實體數據模型 (EDM) 基於 Peter Chen 博士的實體關系 (ER) 模型,實際上它是 ADO.NET Entity Framework 背後的驅動力。EDM 也是區分實體框架與市場上其他 ORM 型技術的最明顯特征。EDM 構建在 ER 模型之上,它將模型的抽象級別提升到高於邏輯模型的級別,但同時仍保留實體和關系的概念。
為什麼需要建立另外一種數據模型?
那麼為什麼需要建立另外一種模型呢?隨著公司數據處理量的增加,理順數據關系並基於這些數據來開發應用程序變得非常困難。數據庫架構的設計需要考慮存儲問題(如數據完整性、性能和管理),有時候這不是很容易理解。這些架構還經常與應用程序的結構有沖突,使開發和維護工作變得更加復雜。
我們經常會遇到數據結構與所構建的應用程序被分割開的自定義解決方案。遺憾地是,對每個應用程序而言,自定義解決方案的數量、各種各樣的方法以及建模數據所需的步驟都各不相同,導致問題不斷產生。整個行業都希望能有一種方法來針對應用程序級的域模型進行定義和開發,以便能夠與邏輯模型的存儲清晰地分隔開。因此引入了實體框架。
EDM(請參閱圖 1 中描述的示例)允許以組織看待和使用數據的方式(不是數據的存儲方式)來定義域模型。開發 EDM 還有一個主要目標,那就是在 Microsoft 內成為用於開發人員和服務器技術套件的核心數據模型。
圖 1 某個博客數據庫的示例實體數據模型
通過使用一個核心數據模型,應用程序的維護得以簡化。實現此目標後,EDM 即可發揮作用,不但可以為基於 ADO.NET Entity Framework 構建的自定義應用程序定義模型,還可以作為報告和可視化應用程序、Intranet 門戶應用程序或工作流應用程序的輸入內容。
與 ER 模型類似,EDM 使用以下兩個主要概念:實體(事物)以及這些實體之間的關系(或關聯)。在考慮實體和關聯實例(或這些實例集操作的封閉包)的存儲時,還需要用到“集”的概念。因此,更清楚地說,實體存在於 EntitySet 中而關聯存在於 AssociationSet 中。
在 EDM 中定義的最後一個結構概念是 EntityContainer 概念,它可以在之前介紹的實例和關系集周圍定義一個封閉包。這些簡單概念在聯合使用後可允許開發人員定義一個域模型,此模型可被映射回持久性層和在應用程序自身中使用的類。(需要注意的是,EDM 的持久性層不需要是相關的,盡管它現在是相關的。)
EDM 中定義的每個實體類型可包含兩個不同類型的成員,一個是用來定義實體的屬性(類似於數據庫中的列),另一個是導航屬性,它支持實體類型參與的導航關系(通常表示為數據庫中的外鍵)。此外,每個實體類型都必須有一個唯一的標識或關鍵字。圖 2 中的 XML 片斷描述了一個 "Blog Post" 實體類型。
圖 2 BlogPost 實體類型定義
<EntityType Name="BlogPost"> <Key> <PropertyRef Name="BlogPostID" /> </Key> <Property Name="BlogPostID" Type="Int32" Nullable="false" /> <Property Name="BlogEntry" Type="String" Nullable="false" MaxLength="Max" Unicode="true" FixedLength="false" /> <Property Name="BlogDate" Type="DateTime" Nullable="false" /> <Property Name="BlogTitle" Type="String" Nullable="false" MaxLength="500" Unicode="true" FixedLength="false" /> <Property Name="BlogType" Type="Int32" Nullable="false" /> <Property Name="CityVisited" Type="String" MaxLength="200" Unicode="true" FixedLength="false" /> <Property Name="CountryVisited" Type="String" MaxLength="200" Unicode="true" FixedLength="false" /> <Property Name="DateVisited" Type="DateTime" /> <NavigationProperty Name="Blog" Relationship= "MyTravelPostModel.FK_BlogPosts_Blogs" FromRole="BlogPosts" ToRole="Blogs" /> <NavigationProperty Name="Pictures" Relationship= "MyTravelPostModel.FK_Pictures_BlogPosts" FromRole="BlogPosts" ToRole="Pictures" /> <NavigationProperty Name="Comments" Relationship= "MyTravelPostModel.BlogComments" FromRole="BlogPosts" ToRole="Comments" /> </EntityType>
實體類型的屬性可以是基元類型或復雜類型(請參閱圖 3),但在 Entity Framework 1.0 中,它們不能是基元或復雜類型的其他實體類型或集合。實體類型的關鍵字由這些屬性的子集組成。導航屬性允許導航從一個實體到另一實體的關系。
圖 3 解決復雜類型定義
<ComplexType Name="Address"> <Property Name="StreetAddress" Type="String" MaxLength="50" /> <Property Name="Address2" Type="String" MaxLength="50" /> <Property Name="City" Type="String" MaxLength="50" /> <Property Name="Region" Type="String" MaxLength="50" /> <Property Name="PostalCode" Type="String" MaxLength="50" /> <Property Name="Country" Type="String" MaxLength="50" /> </ComplexType>
如之前所介紹的,關系可以作為某個關系參與方的各實體類型的導航屬性。關系本身是 EDM 中的一等公民並且在 EDM 中明確定義為關聯:
<Association Name="FK_Friends_People"> <End Role="People" Type="MyTravelPostModel.Person" Multiplicity="1" /> <End Role="Friends" Type="MyTravelPostModel.Friend" Multiplicity="*" /> <ReferentialConstraint> <Principal Role="People"> <PropertyRef Name="PersonID" /> </Principal> <Dependent Role="Friends"> <PropertyRef Name="PrimaryPersonID" /> </Dependent> </ReferentialConstraint> </Association>
因此簡言之,我們為什麼要首先創建一種新的數據建模技術?盡管在 EDM 之前就存在許多數據建模技術或語言,但沒有一個能滿足 Microsoft 想要實現的主要目標,也沒有一個能使經常使用的實體關系模型變為可執行模型。團隊曾研究了大量現有的數據建模技術,但最後發現它們都專門針對或強調特定的問題領域,因此不得不重新開發 EDM 來創建一種新模型,以滿足這些目標並能夠真正用作開發人員和服務器技術套件的核心數據模型。
為什麼使用 XML 來描述 EDM?
經過認真考慮後,XML 被選作 EDM 的第一個序列表示。通過生成 XML 文件(或資源)或從動態生成的 XML 表示中加載,開發人員和第三方可以利用經過良好定義的 XML 格式來進行此類格式的轉換以及將其加載到實體框架的元數據運行時中。但是,創建 EDM 的其他表示也是完全可能的,而且很可能隨著產品的發展在未來版本中就會出現其他替代表示。
當前的 EDM 語法是使用產品隨附的 XML 架構定義語言 (XSD) 定義的。但是,不應期望大多數人都會采用手動開發 XML 的方式,而應認為人們會使用 Visual Studio 中提供的工具。也就是說,團隊已知曉大家對特定於域的語言 (DSL) 以及對 EDM 模型備用持久性機制(通常針對數據庫而言)的關注,並且正在對這些選項做評估以便在即將發行的版本中進行擴展。
誰需要另一種新的查詢語言?
有關 EDM 開發的最後一個問題是為什麼要創建一種新的查詢語言?為什麼不使用現有的語言?在稍微深入地研究一個 EDM 後,答案就會變得清晰起來。
到目前為止,我已經介紹了為什麼要創建 EDM 以及 EDM 中使用的各種構造,還介紹了這樣一個事實,即該模型是實體關系模型的後代。為了使創建的模型不但能夠清楚地映射到底層數據存儲,而且還可以表現開發人員編程時所依據的應用程序級的域模型,EDM 需要能夠對各種概念(如繼承和多態性)進行建模。由於當前的關系查詢語言並不支持基於繼承、關系導航或多態結果的返回值進行查詢,因此需要開發一種新的查詢語言來滿足此需求。
這就催生了實體 SQL (ESQL),它是一種新的 SQL 語言,其中加入了之前的 SQL 語言並不支持的基於概念的查詢功能。ESQL 擴展現有 SQL 語言的方式與 EDM 擴展數據庫中所使用的關系模型的方式十分類似。此外,ESQL 未綁定到任何特定於後台數據庫的語法,因此可一次性編寫查詢(和/或應用程序),無論針對的是哪個後台數據庫都無影響。下面我將以一個簡單的 ESQL 查詢為例,說明如何從全部博客中檢索至少具有一篇帖子和關聯人員(在我的模型中是指博客所有者)的博客:
select c, c.Person from travelEntitiesGeneral.Blogs as c where c.BlogPosts.Count > 0
實現 EDM
ADO.NET Entity Framework 是由 ADO.NET 演變而來的,是 EDM 的首個具體實現,可在開發關系數據庫時提供較高級別的抽象。在版本 1.0 中,團隊一直側重於構建平台基礎,而不僅僅是一個簡單的 ORM,這將允許開發人員使用非常靈活的映射來處理概念模型或對象模型,並能夠適應與底層存儲之間存在的巨大分別。
這一高度的靈活性以及與底層存儲的巨大分別是允許數據庫和應用程序分別發展的關鍵所在。當數據庫架構發生改變時,應用程序由於采用實體框架而不必進行改動,並且您通常不需要重新編寫應用程序的內容,只是在必要時更新映射文件以適應此變化即可。
為了開始發展 ADO.NET 平台,需要以現有 ADO.NET 2.0 提供程序模型為基礎構建實體框架,並對現有提供程序進行適當更新以支持新的實體框架和 ADO.NET 3.5 功能。我們之所以選擇在現有 ADO.NET 提供程序模型的基礎上來實現是為了確保開發社區對提供程序模型不會感到陌生。
體系結構如圖 4 所示。您會注意到可接受的架構包括概念架構定義語言 (CSDL)、映射架構語言以及存儲架構定義語言 (SSDL)。您還會注意到,實體框架包括了更新後的支持規范命令樹 (CCT) 的 SqlClient 數據提供程序。
圖 4 ADO.NET Entity Framework 體系結構
EntityClient
然後,實體框架在這些 ADO.NET 3.5 提供程序的基礎上引入新的 ADO.NET 提供程序 EntityClient。EntityClient 看上去與之前使用的 ADO.NET 提供程序非常類似,它將提供第一個抽象,可允許開發人員使用標准的 Connection、Command 和 DataReader 對象依照 EDM 執行查詢。它還會將映射域模型所需的客戶端視圖引擎(根據 EDM 定義的)添加到底層關系數據庫架構。必要時,EntityClient 可借助 ESQL 查詢字符串讓開發人員以行和列的形式處理實體,而不必生成類來表示概念架構。
如果看一看圖 5 中的 EntityClient,您會注意到我創建了一個包含 ESQL 查詢字符串的 EntityCommand,該命令隨後會在我的 EDM 中被執行。作為 EntityCommand 的一部分而提供的查詢文本被解析並會創建一個 CCT。
圖 5 使用 ESQL 執行針對 EntityClient 的查詢
using (EntityConnection conn = new EntityConnection("name=travelEntitiesGeneral")) { conn.Open(); EntityCommand cmd = conn.CreateCommand(); cmd.CommandText = @"select c.BlogID from travelEntitiesGeneral.Blogs as c where c.BlogPosts.Count > 0"; EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); while (reader.Read()) { Console.WriteLine("BlogID = {0}", reader["BlogID"]); } conn.Close(); }
在這第一個階段中,命令樹仍針對 EDM 來表示。客戶端視圖引擎借鑒數據庫系統中的具體化視圖理論並將這些理論應用到數據訪問層,它在樹中應用了一個映射轉換,可生成一個在底層邏輯存儲模型方面表示相同操作的樹,並刪除任何非關系概念(如關系、繼承和多態性)。這一新轉換的樹被傳給 ADO.NET 3.5 提供程序服務,而此服務會返回封裝底層存儲的本機 SQL 的 DbCommand 命令,然後此命令被執行,其結果通過堆棧向上回傳。
在定義客戶端視圖引擎中所使用的映射以便在 EDM 和邏輯數據庫架構之間進行轉換時,可采用多種不同的方法。此映射可使用映射規范語言 (MSL) 來指定,這是一種聲明性的 XML 語法,可通過手動編寫 XML 或使用 Visual Studio 中包括的實體映射工具進行創建和編輯(請參閱圖 6)。
圖 6 MSL—EntitySetMapping 示例
<EntitySetMapping Name="BlogPosts"> <EntityTypeMapping TypeName="IsTypeOf(MyTravelPostModel.BlogPost)"> <MappingFragment StoreEntitySet="BlogPosts"> <ScalarProperty Name="BlogPostID" ColumnName="BlogPostID" /> <ScalarProperty Name="BlogEntry" ColumnName="BlogEntry" /> <ScalarProperty Name="BlogDate" ColumnName="BlogDate" /> <ScalarProperty Name="BlogTitle" ColumnName="BlogTitle" /> <ScalarProperty Name="BlogType" ColumnName="BlogType" /> <ScalarProperty Name="CityVisited" ColumnName="CityVisited" /> <ScalarProperty Name="CountryVisited" ColumnName="CountryVisited" /> <ScalarProperty Name="DateVisited" ColumnName="DateVisited" /> </MappingFragment> </EntityTypeMapping> </EntitySetMapping>
編譯時,MSL 允許實體框架生成必要的查詢並更新視圖,這些視圖隨後會在客戶端視圖引擎中被用來完成從查詢(利用 EDM 定義的)到邏輯存儲架構的轉換。
另一種用於表達映射或部分映射的方法是使用 ESQL 查詢。在這種情況下,當開發人員使用 ESQL 來表達查詢視圖時,基礎結構會要求他們在映射規范中同時定義伴隨的 Create、Update 和 Delete 映射。此操作是必需的,因為如果能夠在“查詢視圖”中利用 ESQL 功能,從而可以為沒有單個有效更新視圖的查詢定義視圖,則映射基礎結構將無法為查詢生成對應的更新視圖。
對象服務
在 EntityClient 提供程序的基礎上,實體框架添加了另一組抽象,以便允許針對對象而非 EntityClient 返回的非類型化數據記錄進行開發。這就是通常被認為是 ORM 的層,它可以生成在數據模型中所定義類型的 CLR 實例並允許開發人員使用 LINQ 或 ESQL 查詢這些對象。它也恰好是當初眾多開發人員在市場中尋找可用的 ORM 技術時最能吸引他們眼球的實體框架層。
正如圖 1 所示,對象服務層的高級功能是接受來自應用程序的 ESQL 或 LINQ 查詢,然後將查詢表達式傳遞給下面的 EntityClient 並返回 IEnumerable<T>。但是,經過深入的分析後您會發現,對象服務層的中心是代表應用程序與底層數據存儲之間的交互會話的 ObjectContext。
ObjectContext 是開發人員在查詢、添加和刪除其實體實例以及將新狀態保存回數據庫時用到的主要構造。圖 7 顯示的是實體的創建以及使用 ObjectContext 對實體執行查詢、操作以及 SaveChanges 等動作。此示例使用 ESQL 作為查詢語言。
圖 7 使用 ObjectContext
using (ObjectContext context = new ObjectContext("name=travelEntities")) { //--- create a query for customers ObjectQuery<Person> personQuery = context.CreateQuery<Person>( @"select value c from travelEntitiesGeneral.People as c where c.PersonID == 1"); //--- by enumerating the query will be implicitly executed //--- against the store and you can now work with an //--- IEnumerable<Customer> foreach (Person c in personQuery) { //--- dereference anything you like from Customer Console.WriteLine(c.PersonID + ": " + c.Name); c.Name = "New Name"; } try { context.SaveChanges(); } catch (OptimisticConcurrencyException opt) { // catching this exception allows you to // refresh travelEntities with either store/client wins // project the travelEntities into this failed travelEntities. var failedEntities = from e3 in opt.StateEntries select new { e3.Entity }; // Note: in future you should be able to just pass // the opt.StateEntities // in to refresh. context.Refresh(RefreshMode.ClientWins, failedEntities.ToList()); context.SaveChanges(); } }
如果使用對象服務,則對開發人員而言,跟蹤內存中對象所發生的更改的流程以及將這些更改保存回數據庫的流程都會得到簡化。對象服務使用 ObjectStateManager 不但會跟蹤內存中實例的當前狀態,還會跟蹤每個實例從存儲庫中檢索出來時的初始狀態,從而使實體框架可以在將數據推送回數據庫時應用最優的並發操作。通過對 ObjectContext 調用 SaveChanges 方法,可以輕松地保存所跟蹤的更改並將其推送回數據存儲庫。
到目前為止,我一直在講述有關 ObjectContext 的基本內容並通過一些示例介紹 Object-Context 的基本用法,它通常用於有動態工具或應用程序需要使用 EDM 模型的情形。但是,在使用 Visual Studio 作為開發環境時,開發人員發現強類型化 ObjectContext 還有一個優點,即可以向可能特定於目標 EDM 的表面功能添加屬性和方法。
圖 8 顯示了使用強類型化 Object-Context 構建的查詢。此示例演示了如何使用 LINQ 作為查詢語言。使用強類型化 ObjectContext 可公開每個 EntitySet 的屬性,從而使其更容易被發現;例如,使用 travel-Entities.BlogPosts 而非 travelEntities.CreateQuery<Blog-Post>("travelEntitiesGeneral.BlogPosts")。
圖 8 使用強類型化 ObjectContext 構建的查詢
using (MyTravelPostEntities travelEntities = new MyTravelPostEntities()) { // get the latest blog post, with the comments and the people // I'm querying for all the blog posts that are related to this blog. // I want to include the comments and the people who wrote the // comments. // I also want only the most recent posting. // Note: Since we use the EntityKey that is put on the EntityReference // we can either do a tracking query or use span. BlogPost post = (from bp in travelEntities.BlogPosts .Include("Comments.Person") .Include("Blog") where bp.Blog.BlogID == requestedBlog.BlogID orderby bp.BlogDate descending select bp).First(); return post; }
LINQ to Entities 是作為對象服務上一個非常薄的層出現的,它在編程語言中提供直接的查詢功能(請參閱圖 8)而非使用基於字符串的查詢。在這種情況下,ObjectQuery 類將實現 Iqueryable,允許它接受 LINQ 表達式樹,並采用對象服務在將 ESQL 查詢傳遞到 Entity-Client 提供程序時所使用的同樣方式在實例框架中推動查詢(作為 CCT 查詢表達式)。
使用實體框架進行 N 層開發
盡管本文的主要目標並非是全面介紹 n 層開發,但作為實體框架開發過程中一個比較有趣的環節,對此還是應該有所提及。在版本 1.0 中,實體框架在許多主要方案中都支持 n 層開發。其中包括使用 ADO.NET 數據服務或使用 Windows Communication Foundation (WCF),它們都具有序列化實體以及在 ObjectContext 中附加/分離實體的功能。這些顯然並非是實現 n 層開發的唯一方法;但是,它們是團隊在 V1 中重點關注的解決方案,而且在 V2 和更高的版本中還添加了更多其他方案(如更接近數據集的體驗)。圖 9 展示了我所講述的內容。
圖 9 N 層應用程序中的 ADO.NET 數據服務
static Uri baseService = new
Uri("http://localhost:17338/MyTravelPostService.svc");
MyPeople2Entities context = new MyPeople2Entities(baseService);
// get the comment that is being marked for deletion
// and get the view state blog post.
BlogPost post = (BlogPost)ViewState["BlogPost"];
// move the comment to the deleted comment selection.
Comment deletedComment = post.Comments[e.RowIndex];
// call the DeleteComment service
context.AttachTo("Comments", deletedComment);
context.DeleteObject(deletedComment);
DataServiceResponse r = context.SaveChanges();
// reload page so that F5, refresh doesn't update all this data.
ReloadPage();
ADO.NET 數據服務是具像狀態傳輸 (REST) 體系結構風格的一個具體實現(每種資源都代表系統中的一個名詞 — 即一個可以通過統一資源標識符 (URI) 唯一標識的事物),它允許在任意 IQueryable 實現上進行 n 層應用程序的開發。利用 ADO.NET 數據服務,除了通過線路查詢實例以外,您還可以實現更多功能。ADO.NET 數據服務支持 Create、Read、Update 和 Delete 等各種 HTTP 動詞,並提供客戶端抽象來幫助開發人員實現其解決方案。
n 層方案的第二種方法是使用具有實體框架的 WCF,以便充分利用序列化實體以及在 ObjectContext 中附加/分離實體的能力。圖 10 展示了在此方案中如何附加到 ObjectContext。
圖 10 附加到 ObjectContext
// the creation of the travel MyTravelPostEntities opens the connection // and sets up all the metadata information automatically for you. using (MyTravelPostEntities travelEntities = new MyTravelPostEntities()) { // attach the comment and delete. travelEntities.Attach(deleteComment); // call delete on the object travelEntities.DeleteObject(deleteComment); try { travelEntities.SaveChanges(); } catch (OptimisticConcurrencyException opt) { // catching this exception allows you to // refresh travelEntities with either store/client wins // project the travelEntities into this failed travelEntities. var failedEntities = from e3 in opt.StateEntries select new { e3.Entity }; travelEntities.Refresh(RefreshMode.ClientWins, failedEntities.ToList()); travelEntities.SaveChanges(); } }
默認情況下,無論是從 Visual Studio 中的 EDM 生成的還是使用 edmgen.exe(實體框架隨附的命令行工具)生成的 CLR 類都是 XML 可序列化和二進制可序列化的,並且是導航屬性默認設置為 DataMembers 的數據約定,因而可以創建 ASMX Web 服務並在視圖狀態或 WCF 服務中使用實體實例。
與大多數 ORM 類似,實體框架目前並不支持使用數據操作語言 (DML) 進行創建、更新或刪除等操作。必須將更改應用到內存中的對象,並且在構建要保持的整個圖表時可能需要與數據庫進行多次交互。
可用來避免這一過程的一種方法是使用 ObjectContext 提供的附加功能。您可以利用 Attach 來通知基礎結構,告知它實體已經存在,應該在內存中執行一組操作然後將更改向下推送。如需有關使用實體框架實現 n 層開發的更多信息,請搜索 MSDN 庫,因為很快就會向其中添加更多內容。
只是另一個 ORM?
到目前為止,許多人都只是將實體框架看作是市場中的另一個 ORM,如果只看本產品的第一個版本,那麼有這種想法也不足為奇。但從該角度來看,產品此時所具有的功能已經可以實現 ORM 剛剛開始著手解決的一組核心方案。不過,目前的許多分析也指出,實體框架並不能完全涵蓋市場中其他一些 ORM 所提供的全部功能,這也的確是實情。
Microsoft 在此領域所做的投資旨在擴展傳統 ORM 產品的功能,而實體框架(我將在稍後介紹)正是圍繞 EDM 所展開的更廣泛策略的第一個步驟。正如我在本文開頭所提到的,EDM 創建了一個較高級別的域模型,其適用范圍遠遠超出了實體框架和傳統的 ORM。預計在未來幾個版本的 Microsoft .NET Framework、Visual Studio、SQL Server 以及其他 Microsoft 技術中,EDM 的身影會越來越多地出現在其中。
此預期以及 EDM 發展的整體願景已成為最主要的推動力(正如本文所討論的各種產品決策中所展示的那樣)。各種決策在制定時都應盡可能確保能被各種技術(如 Reporting Services 和 Analysis Services)所采用。這將為客戶帶來極大的好處,因為各種服務都將可以在常見且一致的域模型中提供。
第一個成為現實的願景是 ADO.NET 數據服務將在 Visual Studio 2008 SP1 中與實體框架一起發布。ADO.NET 數據服務為基於 REST 的應用程序提供了值得關注的開發人員體驗,它將成為使用 EDM 作為元數據交換格式構建的首個產品(在實體框架以外)。
為配合此次發布行動,Microsoft 在 MIX 2008 中展示了各種與眾不同的 Windows Live 屬性,它們都使用 ADO.NET 數據服務協議和 EDM 提供其數據。同樣,當我們開始規劃下一版本的 SQL Server 和 Visual Studio 時,團隊將以 EDM 和實體框架為核心,努力提供更好的端對端開發體驗。