1.EF查询技术。
2.捕捉生成的SQL。
3.深入理解EF查询引擎。
4.常见的查询陷阱。
一、查询引擎入口点
对象服务层最重要的类是ObjectContext。在你的代码中它是最有用的类,因为它提供了唯一的执行LINQ to Entities查询的入口点。Visual Studio设计器的一个特征就是生成实体类,这些类继承自ObjectContext并且每个实体类(有些例外)有一个集合属性,表示实体集。可以把实体集理解为数据库,实际上它并不存储数据,而是对它查询。后边会看到对实体集的查询如何变成SQL并返回数据,现在暂且把注意力放在实体集上。
表示实体集的属性是ObjectSet<T>类型的。在OrderIT中,这个属性是Orders,类型为ObjectSet<Order>,下面是它的定义。
public partial class OrderITEntities : ObjectContext { private ObjectSet<Order> _orders; public ObjectSet<Order> Orders { get { return _orders ?? (_orders = CreateObjectSet<Order>("Orders")); } } }
ObjectSet<T>继承自ObjectQuery<T>。ObjectQuery<T>是在EF 1.0时用的类,ObjectSet<T>与ObjectQuery<T>保持兼容性的同时又新加了一些方便的方法。ObjectContext不是抽象的。向导生成一个继承自ObjectContext专门的类的唯一原因是自动生成实体集属性加上一些辅助方法。生成的类是完美的,应该经常使用它。
如果有一种情况,你不能使用它,你可以通过传连接字符串名称直接实例化ObjectContext类。然后,就可以使用CreateObjectSet<T>方法,将实体集的名称作为参数创建ObjectSet<T>实例。下面是代码:
using (ObjectContext ctx = new ObjectContext("name=ConnStringName")) { var os = ctx.CreateObjectSet<Order>("Orders"); }
如你所看,手动创建一个ObjectSet<T>实例一点都不难。
当实例化设计器产生的上下文类,不需要传连接字符串给构造器,然而直接对ObjectContext操作就必须传一个连接字符串了。设计器知道连接字符串是什么,当模板生成上下文类使,它创建了调用基类构造器的构造器,连接字符串传给基类构造器。上下文类中得构造器如下面片段所示:
public OrderITEntities() : base("name=ConnStringName") { }
二、设置连接字符串
你已经看到,连接字符串对ObjectContext来说是强制性的。如何传递连接字符串在EF Framework和ADO.NET Framework中有细微的不同,在EF中必须给连接字符串加一个Name=前缀:Name=ConnStringName。跟过去相比,这不是唯一的不同。连接字符串本身很古怪,下面的代码就是OrderIT配置文件中的连接字符串。
<add name="OrderITEntities" connectionString=" metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl; provider=System.Data.SqlClient; provider connection string="data source=.;initial catalog=OrderIT;persist security info=True;user id=sa;password=sa;multipleactiveresultsets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
跟以前相比,结构是相同的,但是数据是以不同的方式组织起来的。name表示连接字符串的名称,它在上下文类中传递给构造器。connectionString跟以前见过的有很大的不同,可能看起来还有点糊涂。但是必须得理解它,因为当修改数据库时必须修改它。它分为三部分:
metadata —指定三个映射文件的位置,以”|”分割。如果文件作为纯文本存储在硬盘中,就指定文件的路径;如果文件作为资源存储在程序集中,就使用如下的规定格式:res://*/filename。
provider —为数据库指定ADO.NET不变的数据提供程序名称,就好像将providerName填入connectionString节中。
provider connection string —表示连接数据库的真实字符串。如果包含双引号,必须使用它的转义格式",避免与XML格式发生冲突。
最后是providerName包含System.Data.EntityClient字符串,它是不变的EF数据提供程序的名称。
设置连接字符串是一项很重要的工作。幸运的是,它是由设计器完成的,只有在更改数据库时才会触碰它。如果在运行时动态构建,可以使用EntityConnectionStringBuilder类。
在代码中创建连接字符串
通常连接字符串放在配置文件中由程序读取。然后,在一些情况下,需要在运行时改变连接字符串。第一种情况字符串的每个属性由一个服务返回,这不是最好的架构,但是有时必须使用它。第二种情况,应用程序需要处理两个不同版本的SQL Server(2005和2008),这意味着有两个不同的SSDL文件,合适的一个应该是由程序运行是根据数据库的版本选择的。有两个解决方案:
1.手动创建字符串构建连接字符串。
2.运行时使用EntityConnectionStringBuilder构建连接字符串。
不用说,第二种解决方案是更好的选择。
EntityConnectionStringBuilder类继承自ADO.NET基础类DbConnectionStringBuilder,它负责从一组参数构建一个连接字符串和解析一个给定的连接字符串成单独参数。下表描述了它的属性。
在描述的第一种情况,可以使用DbConnectionStringBuilder类从一个单独的参数产生一个连接字符串。你可以从Web服务中取得Metadata, Provider和Provider-ConnectionString参数,使用它们设置连接字符串并且传递给上下文类的构造器,如下所示:
var connStringData = proxy.GetConnectionStringData(); var builder = new EntityConnectionStringBuilder(); builder.Provider = connStringData.Provider; builder.Metadata = connStringData.Metadata; builder.ProviderConnectionString = connStringData.ProviderConnectionString; using (var ctx = new OrderITEntities(builder.ConnectionString)) { ... }
重要的是数据里不能包含节的名称。例如provider参数必须包含值System.Data.SqlClient,而不是Provider=System.Data.SqlClient,Provider=会由EntityConnectionStringBuilder类自动进行处理。
第二种情况,在运行时构建连接字符串,只需要改变连接字符串的一部分:SSDL的位置。这种情况下,可以将连接字符串放在配置文件中看,在SSDL位置放一个占位符{0}。一旦知道数据库的版本就可以用合适的SSDL路径替换占位符。这可以用简单的String.Format完成,但是EntityConnectionStringBuilder能够连接字符串并填入相关的属性。
下面的解决方案首先加载连接字符串,然后使用String.Format只修改Metadata属性。
var builder = new EntityConnectionStringBuilder(); builder.ConnectionString = connString; builder.Metadata = String.Format(builder.Metadata, "res://*/Model.ssdl"); using (var ctx = new OrderITEntities(builder.ConnectionString)) { ... }
连接字符串只是第一部分,在第一章提到EF最主要的特征是对对象模型查询而不是对数据库,下一篇我们把主要精力放在对象模型查询上。