翻譯的初衷以及為什么選擇《Entity Framework 6 Recipes》來學習,請看本系列開篇
第七章 使用對象服務
本章篇幅適中,對真實應用中的常見問題提供了切實可行的解決方案。我們構建的應用,應當具備在部署環境中接受改變的能力,我們將應用構建得足夠靈活,使其幾乎沒有配置需要硬編碼。
前三節向你提供了應對這些挑戰的辦法。剩下的小節覆蓋了諸如:實體框架的單復數服務、使用edmgen.exe實用工具、使用標識關系以及從ObjectContext中獲取對象。
7-1 動態構建連接字符串
問題
你想為你的應用動態構建連接字符串。
解決方案
許多真實應用一開始是在開發人員的電腦里,然后通過一個或多個測試,集成測試以及在過渡環境(staging environments)上的測試,最終才作為一個產品發布。你想依據當前環境來動態配置應用的連接字符串。
按代碼清單7-1中的方式,為你的應用動態構建連接字符串。
代碼清單7-1. 動態構建連接字符串
public static class ConnectionStringManager { public static string EFConnection = GetConnection(); private static string GetConnection() { var sqlBuilder = new SqlConnectionStringBuilder(); sqlBuilder.DataSource = ConfigurationManager.AppSettings["SqlDataSource"]; // 填充剩下的 sqlBuilder.InitialCatalog = ConfigurationManager.AppSettings["SqlInitialCatalog"]; sqlBuilder.IntegratedSecurity = true; sqlBuilder.MultipleActiveResultSets = true; var eBuilder = new EntityConnectionStringBuilder(); eBuilder.Provider = "System.Data.SqlClient"; eBuilder.Metadata = "res://*/Recipe1.csdl|res://*/Recipe1.ssdl|res://*/Recipe1.msl"; eBuilder.ProviderConnectionString = sqlBuilder.ToString(); return eBuilder.ToString(); } } public partial class EF6RecipesContainer { public EF6RecipesContainer(string nameOrConnectionString) : base(nameOrConnectionString) { } }
原理
當你添加一個ADO.NET實體數據模型到你的項目中時,實體框架會在項目的.config文件的<ConnectionStirngs>小節中添加一條目。在運行時,給上下文對象的構造函數傳入配置條目的鍵(本書中絕大章節使用的上下文是EF6RecipesContext)。通過給定的鍵,數據庫上下文會去.config文件中是查找連接字符串並使用。
為了根據應用所在的環境動態創建連接字符串,我們創建了ConnectionStringManager類(如代碼清單7-1)。在GetConnection()方法中,我們從配置文件中獲取了特定環境的data source和initial catalog。為了能使用ConnectionStringManager,我們在EF6RecipesContainer部分類中,增加了一個額外的構造函數,它接受一個表示連接字符串或連接字符串名稱的參數。
當實例化EF6RecipesContainer時,我們傳遞ConnectionStringManager.EFContection給它作為參數。最終,它將使用動態創建的連接字符串來連接數據庫服務。
7-1 從數據庫中讀取模型
問題
你想從數據表中讀取為模型定義的CSDL,MSL和SSDL。
解決方案
假設你有一個如圖7-1所示的模型。

圖7-1. 一個包含Cutomer實體的模型
我們的模型只有一個實體:Customer。概念層的CSDL),映射層的MSL和存儲層的SSDL,它們的定義通常能在你的項目中的.edmx文件中發現。但我們想從數據庫讀取它們的定義。為了能從數據庫中讀取這些定義,請按下面的步驟進行:
1、右鍵設計器,查看屬性。更改代碼生成策略為None。我們將為我們的Customer類使用POCO;
2、創建如圖7-2所示的表,這張表將保存我們項目模型的定義;

圖7-2. 表Definitions,保存SSDL,CSDL和MSL的定義,注意字段的類型為XML
3、右鍵設計器,查看屬性。更改元數據項目處理(Metadate Artifact Processing)為“復制到輸出目錄”(Copy to Output Directory)。重新編譯你的項目。編譯過程將在輸出目錄生成三個文件:Recipe2.ssdl,Recipe2.csdl和Recipe2.msl;
4、將上一步生成文件的內容插入到Definitions表中合適的列,Id列使用值1;
5、使用代碼清單7-2,從數據庫表Definitions中讀取元數據,並創建一個應用要使用的MetadateWorkSpace類;
代碼清單7-2.從表Definitions中讀取數據
public static class Recipe2Program { public static void Run() { using (var context = ContextFactory.CreateContext()) { context.Customers.AddObject( new Customer { Name = "Jill Nickels" }); context.Customers.AddObject( new Customer { Name = "Robert Cole" }); context.SaveChanges(); } using (var context = ContextFactory.CreateContext()) { Console.WriteLine("Customers"); Console.WriteLine("---------"); foreach (var customer in context.Customers) { Console.WriteLine("{0}", customer.Name); } } } } public class Customer { public virtual int CustomerId { get; set; } public virtual string Name { get; set; } } public class EFRecipesEntities : ObjectContext { private ObjectSet<Customer> customers; public EFRecipesEntities(EntityConnection cn) : base(cn) { } public ObjectSet<Customer> Customers { get { return customers ?? (customers = CreateObjectSet<Customer>()); } } } public static class ContextFactory { static string connString = @"Data Source=localhost; initial catalog=EFRecipes;Integrated Security=True;"; private static MetadataWorkspace workspace = CreateWorkSpace(); public static EFRecipesEntities CreateContext() { var conn = new EntityConnection(workspace, new SqlConnection(connString)); return new EFRecipesEntities(conn); } private static MetadataWorkspace CreateWorkSpace() { string sql = @"select csdl,msl,ssdl from Chapter7.Definitions"; XmlReader csdlReader = null; XmlReader mslReader = null; XmlReader ssdlReader = null; using (var cn = new SqlConnection(connString)) { using (var cmd = new SqlCommand(sql, cn)) { cn.Open(); var reader = cmd.ExecuteReader(); if (reader.Read()) { csdlReader = reader.GetSqlXml(0).CreateReader(); mslReader = reader.GetSqlXml(1).CreateReader(); ssdlReader = reader.GetSqlXml(2).CreateReader(); } } } var edmCollection = new EdmItemCollection(new XmlReader[] { csdlReader }); var ssdlCollection = new StoreItemCollection(new XmlReader[] { ssdlReader }); var mappingCollection = new StorageMappingItemCollection( edmCollection, ssdlCollection, new XmlReader[] { mslReader }); var localWorkspace = new MetadataWorkspace(); localWorkspace.RegisterItemCollection(edmCollection); localWorkspace.RegisterItemCollection(ssdlCollection); localWorkspace.RegisterItemCollection(mappingCollection); return localWorkspace; } }
代碼清單7-2的輸出如下:
Customers ---------Jill Nickels Robert Cole
原理
代碼清單7-2的第一部分,對於你來說,應該是非常熟悉了。我們使用實體框架創建了一個新的上下文對象,創建了一些實體對象,並調用SaveChages()方法將這些實體對象持久化到數據庫中。為了獲取這些實體,我們枚舉了整個集合,並將它們從控制台輸出。唯一不同的是,我們在創建上下文對象時調用ContextFactory.CreateConext()。 一般情況下,我們只需要使用new 操作符來獲取一個新的EFRecipesEntities上下文對象實例。
我們創建一個ContextFacotry,使用存儲的元數據來創建我們的上下文對象,元數據不是存儲在.edmx文件,而是數據庫中。我們使用CreateContext()方法來實現這個功能。CreateContext()方法創建了一個新的基於兩個參數的EntityConnection:我們在CreateWorkSpace()方法中創建的 workspace,和一個SQL連接字符串。真正的工作發生在CreateWorkSpace()方法如何創建workspace的過程中。
CreateWorkSpace()方法,打開存儲元數據數據庫的連接,我們構建一條SQL語句從Definitions表中讀取一行數據,Definitions表(如圖7-2)保存着,概念層、存儲層和映射層的定義。我們使用Xmlreaders來讀取這些定義 。 有了這些定義數據后,我們就可以創建MetadataWorkspace的實例對象了。 MetadataWorkspace在內存中代表一個模型。一般地,這個workspace是實體框架的默認管道從.edmx文件創建的,而現在我們是從數據庫表Definitions創建。還有別的方法可以創建這個對象,這些方法包含使用嵌入資源和Code First來實現。
代碼清單7-2使用POCO來表示Customer實體。雖然我們在第八章才覆蓋POCO的內容,但是這里使用POCO來簡化代碼。 有了POCO,我們就不用使用實體框架生成的類。相反,我們使用自己創建的,沒有依賴實體框架的類。在代碼清單7-2中,我們使用Customer類來定義Customer實體。同時我們還創建了自己的上下文對象EFRecipesEntities。 當然,我們的上下文對象,依賴於實體框架,因為它繼承至ObjectContext。
實體框架交流QQ群: 458326058,歡迎有興趣的朋友加入一起交流
謝謝大家的持續關注,我的博客地址:http://www.cnblogs.com/VolcanoCloud/
