本章篇幅适中,对真实应用中的常见问题提供了切实可行的解决方案。咱们构建的应用,应当具有在部署环境中接受改变的能力,咱们将应用构建得足够灵活,使其几乎没有配置须要硬编码。html
前三节向你提供了应对这些挑战的办法。剩下的小节覆盖了诸如:实体框架的单复数服务、使用edmgen.exe实用工具、使用标识关系以及从ObjectContext中获取对象。sql
问题数据库
你想为你的应用动态构建链接字符串。app
解决方案框架
许多真实应用一开始是在开发人员的电脑里,而后经过一个或多个测试,集成测试以及在过渡环境(staging environments)上的测试,最终才做为一个产品发布。你想依据当前环境来动态配置应用的链接字符串。ide
按代码清单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给它做为参数。最终,它将使用动态建立的链接字符串来链接数据库服务。
问题
你想从数据表中读取为模型定义的CSDL,MSL和SSDL。
解决方案
假设你有一个如图7-1所示的模型。
图7-1. 一个包含Cutomer实体的模型
咱们的模型只有一个实体:Customer。概念层的CSDL),映射层的MSL和存储层的SSDL,它们的定义一般能在你的项目中的.edmx文件中发现。但咱们想从数据库读取它们的定义。为了能从数据库中读取这些定义,请按下面的步骤进行:
一、右键设计器,查看属性。更改代码生成策略为None。咱们将为咱们的Customer类使用POCO;
二、建立如图7-2所示的表,这张表将保存咱们项目模型的定义;
图7-2. 表Definitions,保存SSDL,CSDL和MSL的定义,注意字段的类型为XML
三、右键设计器,查看属性。更改元数据项目处理(Metadate Artifact Processing)为“复制到输出目录”(Copy to Output Directory)。从新编译你的项目。编译过程将在输出目录生成三个文件:Recipe2.ssdl,Recipe2.csdl和Recipe2.msl;
四、将上一步生成文件的内容插入到Definitions表中合适的列,Id列使用值1;
五、使用代码清单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/