[Programming Entity Framework] 第3章 查询实体数据模型(EDM)(二)

Programming Entity Framework 第二版翻译索引

使用对象服务和Entity SQL查询

能代替LINQ to Entities的另一种可以创建的查询方法是直接使用EF的对象服务(Ojbect Services),它在System.Data.Objects命名空间下。你可以直接创建ObjectQuery,用它结合EF类似T-SQL的查询语言构造查询表达式,这种查询语言就是Entity SQL。

为了明白它是如何工作的,根据下列步骤修改你的示例:

  1. 使用下面示例3-3中的代码替换包含LINQ to Entities查询的代码行,或注释掉之前的代码。

示例 3-3. Querying with Entity SQL

var queryString = "SELECT VALUE c " +

"FROM SampleEntities.Contacts AS c" +

"WHERE c.FirstName='Rebort'";

ObjectQuery<Contact> contacts = context.CreateQuery<Contact>(queryString);

 

  1. 再次运行程序,结果与前面的是一样的。

在代码的第一行,创建了Entity SQL 表达式。在第二行,创建了ObjectQuery,传递给它查询会用到的表达式。示例中已存在的代码然后执行查询并返回结果。如果你这样组织过SQL查询,示例3-3中使用的Entity SQL语法它非常类似,但并不完全一样。

在设计时这个查询返回的类型是ObjectQuery<Contact>,它实现了Iqueryable接口。但是你在本书后面会学到,是可以将LINQ to Entities的Iqueryable转换成ObjectQuery,然后访问这些属性和方法的。这意味着即使你选择使用LINQ to Entities,你仍然可以从这些属性和方法中得到好处。

为什么使用另一种查询?

为什么需要除了LINQ to Entities之外其它的方式来查询EDM呢?微软并没有计划使用这两种办法来困扰你。事实上,Entity SQL在LINQ存在之前就已经被创建了,但是现在它们各自有自己的用途。LINQ很明显更容易使用,因为它是你可以在.NET中使用的查询语言,不光是在EF中。它强大的拼写也使它更容易组织。然而,LINQ并不能在每个场景下使用。它是C#和VB的一部分,但没有成为.NET其它语言的一部分。此外,在后面你将学习到当你不需要具体对象时在DataReaders中查询流式结果。这个只有Entity SQL表达式能做到。正如你将在第5章及后面的章节中看到的,在某些场景下可以创建Entity SQL字符串更有优势。因此,尽管你使用LINQ to Entities完成大部分查询,当你遇到这些少见的案例时,你将有所准备。

实体SQL(Entity SQL)

实体SQL(ESQL)的确是第一个用来查询实体的语法。LINQ由VB和C#语言小组作为语言扩展开发出来,最终很明显LINQ是EF极好的附加,这也是LINQ to Entities的由来。

实体SQL起源于SQL,因为从众所周知的东西开始是有意义的。然而,因为实体不同于关系数据,实体SQL脱离了SQL为查询EDM提供了必要的功能。

实体SQL与T-SQL哪里不同?

EF文档有一个话题叫"实体SQL与Transact-SQL哪里不同"。它提供了一个差别列表,还有每个差异的解释说明。例如,实体SQL支持EDM中的继承和关系,而T-SQL你必须使用joins来实现关系。关系数据库甚至没有继承的概念;因此,T-SQL也不支持继承。

仔细看一下前面写到实体SQL查询字符串,你会注意到,像LINQ to Entities一样,它定义了用于查询的变量c,在LINQ中它被称为控制变量,但是在实体SQL中它只是个变量。

图3-6分析了除了WHERE子句之外的查询字符串。变量使用AS关键字定义,只在SELECT子句引用到。VALUE关键字指定你想要返回的单项的集合;在这里它是Contact实体。

如果你要选择单个类型VALUE子句是必须的,单个类型可以是实体,某个属性,基础实体集合,还有你想要返回强类型的对象。这在下面的代码片段中展示:

SELECT VALUE c FROM SampleEntities.Contacts …

SELECT VALUE c.FirstName FROM SampleEneities.Contacts …

SELECT VALUE c.Addresses FROM SampleEneities.Contacts …

如果你想选择多个项,你不能使用VALUE,像这样:

SELECT c, c.Addresses FROM SampleEntities.Contacts ....

SELECT c.LastName, c.Title FROM SampleEntities.Contacts ...

如果你忘记使用VALUE,强类型的对象将会被谢谢一个包装器,这个我们会马上讨论到。你将需要显示的将结果转换成想要的类型,否则在运行时你将遇到InvalidOperationException异常。

如果你在多项中包括了VALUE,会抛出EntitySqlException异常,告诉你下面的提示:

"SELECT VALUE can have only one expression in the projection list."

选择值在规则列表中只能有一个表达式。

它甚至会告诉你问题所在的行号和列号。但是不幸地是,因为EntitySQL字符串直到运行时才被编译,你只能到那时才能知道这个问题。

注:第22章会深入研究EF的异常。

没有VALUE子句,结果会被装进表格式的行中,你必须深入到行和列获取到数据。与LINQ查询类似,你从集合中选择。在这个查询中,集合是实体集Contacts,但是在实体SQL中指定EntityContainer也是有必要的。再次,c是我在查询中使用的任意的变量名称,代表着Contacts实体集合中的contact项。

实体SQL中的WHERE子句使用了类SQL的语法,如下:

WHERE c.FirstName='Rebort'

实体SQL的标准函数

实体SQL语言非常强健,并提供了很多函数。虽然它不可能覆盖语言支持的所有的运算符和功能。你将看到本书用了他们中的很多,在MSDN库的实体SQL文档中你可以找到全部的列表。

实体SQL支持非常大的标准函数集合,这些函数也是所有的数据提供程序被要求支持的。它也允许数据提供程序包含它们自己特殊的函数。微软自已编写的.NET框架的SQL Server提供程序提供了大约75个特殊的函数,在目标数据库是SQL Server时你可以在实体SQL查询中使用它。它们中的有些与标准函数有所重叠。提供程序此外还提供了特定提供程序原生的类型和它们的方面,还有用于映射EDM和SQL Server的内在的逻辑。其它为EDM编写的提供程序将会有他们支持的自己的附加函数和特性的列表。

注:记住使用EF查询最大的一个好处是它是不依托数据库的。因此,在实体SQL查询中使用提供程序特有的元素前你应该考虑这一点。

注:如果你熟悉T-SQL,你将非常高兴有一个标准函数是Trim(),它意味着你不用傻傻地到处使用LTRIM(RTRIM())了。

参数化的对象查询(ObjectQuery)

对象查询(ObjectQuery)允许你创建参数化的查询。与其它查询语言类似,你在字符中使用@占位符,然后在参数中定义它的值。

要使用参数化的查询,你可以给为对象查询增加使用ObjectContext中的CreateQuery方法创建的参数,或者添加到你显式实例化的对象。如示例3-4。当你在实例化对象查询时你也需要将ObjectContext作为参数传递。

然后你在执行前为对象查询增加参数。为了明白这是如何工作的,你可以重写之前的查询,允许动态改变查询。见示例3-4。

Example 3-4. Adding an ObjectParameter to an ObjectQuery

var qStr = "SELECT VALUE c FROM SampleEntities.Contacts AS c " +

"WHERE c.firstname=@firstName";

ObjectQuery<Contact> contacts = new ObjectQuery<Contact>(qStr, context);

contacts.Parameters.Add(new ObjectParameter("firstName", "Robert"));

注:注意示例中的很多命名空间没有在类中提到。确保在你的代码文件顶部加入了正确的命名空间, 例如,对于ObjectQuery类,你需要System.Data.Objects命名空间。

虽然这看起来很诱人,但是你不能在查询字符中使用参数替换属性名称。换句话说,如果你尝试创建实体SQL字符串SELECT @myproperty FROM SampleEntities.Contacts AS c,然后你合建参数将@myproperty赋值为c.LastName,ESQL看起来是这样的:

"SELECT 'c.LastName' FROM SampleEntities.Contacts AS c"

这是不合法的ESQL,将会报错。你需要使用字符串拼接构建ESQL:

"SELECT " + _propName + " FROM SampleEntities.Contacts AS c"

注:因为安全考虑,你应该对于属性名称的来源非常小心。你应该不要在用户输入中拼接它。想想要是某人执行了诸如"Select Login, Password from Contacts"这样的查询吧。

使用方法查询

到目前为上,你见过的LINQ to Entities和对象服务查询是作为标准查询表达式书写的。LINQ to Entitiest和对象服务都提供了以方法写查询的方式,而不是以运行符和函数或以字符串(在实体SQL中)的形式。

两个查询语言都有你能使用的方法语法,但是它们的存在原因不同。C#和VB在查询方法的顶层实现了LINQ。你的LINQ表达式都被转化成了这些查询方法,但是如果你愿意,可以直接使用它们。

EF是直接处理实体SQL的;然而,组织实体SQL表达式的基于方法的语法是可用的。

使用LINQ方法查询

虽然VB和C#理解LINQ语法,但是CLR不理解。当编译器编译LINQ查询发生的第一件事是它将查询转化成一组访问被查询集合的方法。所有标准的查询运算符(WHERE, SELECT,JOIN等)都与.NET中的方法关联。

你可以使用这些方法语法直接编写查询,如果你更喜欢的话。很多开发人员确实更喜欢这样,尽管其它人喜欢使用查询表达式语法。MSDN文档说,"通常来说,我们推荐查询语法,因为它通常更为简单和可读;然而,方法语法和查询语法并没有本质的不同"。因此,使用哪一种只是类型和个人选择的问题。

注:MSDN提供了LINQ方法的列表,及它们是否被LINQ to Entities支持。它的话题是"支持和不被支持的LINQ方法(LINQ to Entities),它的地址是http://msdn.microsoft.com/en-us/library/bb738550.aspx

编写基于方法的查询,你先要知道在.NET 3.5引用的特性,叫做lambdas表达式。Lambda表达式有着特殊语法的内联方法。如果你是LINQ和lambda表达的新手,从示使用过匿名委托,在看过一些示例后你会弄明白。

让我们使用WHERE子句探索使用方法而不是运算符。标准的WHERE子句是这样的,Where LastName="Hesse"。Where()方法要求LastName='Hesse'作为参数。你将在C#和VB中书写lambda表达式。

让lambda表达式占居你的头脑

毫无疑问一开始lambda表达式是有点令人困惑;但是一旦你明白它们,它们将得到完美的理解,并帮助你编写非常高效和代码。无可否认,我的VB背景使我对lambda理解甚少,如果我一直是使用C++或经常使用C#就好了。一些非常棒的文章能帮助你学习更多lambda表达式的内容。对于C#开发人员,Anson Horton有一篇非常优秀的MSN杂志文章"LINQ的革命及它对C#设计的影响" (http://msdn.microsoft.com/en-us/magazine/cc163400.aspx),它对lambda作了很好的解释。对于VB开发人员,Timonthy Ng有一篇非常棒的MSDN杂志文章"基本的本能:Lambda表达式" (http://msdn.microsoft.com/en-us/magazine/cc163362.aspx),解析了lambda。

在这我们将看一下你在之前示例中使用过的查询,现在使用基于方法的查询编写它。在VB中,表达式以Function开始,表示你正在控制变量上执行函数;然后它呈述了条件。控制变量,这个示例中是c,又在忙碌了。

Dim contacts = context.Contacts _

.Where(Function(c) c.FirstName="Robert")

C#的使用基于方法的LINQ to Entities查询语法看起来非常不同:

var contacts = context.Contacts

.Where(c => c.FirstName=="Robert");

C#的Lambda表达式由指定控制变量开始,然后跟着是 =>(lambda),然后是表达式,[控制变量].FirstName="Robert"。

注:在Where子句中,返回布尔类型的表达式称为谓语(predicate)。是查询将返回使用表达式值为True的所有contacts。

试试这个:

  1. 使用一个方法查询替换现有的查询。你将看到在书写lambda时智能感知非常有帮助。
  2. 按下F5运行应用程序。结果同前面一样。

链接方法

你可以联合LINQ查询方法构建更实用的表达式。这被称为链接。为了试一下这个,为之前查询添加一个OrderBy方法。注意对于OrderBy的lambda表达不用像Where方法那样需要条件判断值是否为真或假。它只需返回属性一个属性,参见示例3-5.

示例 3-5. Chaining LINQ methods

var contacts = context.Contacts

.Where((c) => c.FirstName == "Robert")

.OrderBy((foo) => foo.LastName);

注:当方法签名要求谓词时,像Where方法,它要求表达式返回一个布尔值。否则,lambda表达式只需要成为一个函数,像OrderBy方法。你会看到在VB中,所有方法的签名都称之为函数。C#方法则在方法中特别提到要求返回布尔值的表达式的谓词。你可以在MSDN文档的话题"支持与不被支持的方法(LINQ to Entities)"中查看LINQ to Entities各种方法的签名。

虽然在混合方法中,你可以容易地使用相同名称的变量,该变量不代表相同的实例。在前面的LINQ查询中,我给了变量不同的名称,以突出编译器如何评估查询。

LINQ一次只能评估查询中的一个方法。首先它评估context.Contacts。然后它将Where方法应用到结果中,最后它把OrderBy方法应用到Where方法的结果中。在Where方法中的c指的是context.Contacts 返回的项。OrderBy方法中的变量指的是context.Contacts.Where(…)返回的IQueryable。

一次评估一个方法不是说一次执行一个查询。LINQ to Entities将每次评估一个方法,然后创建基于整个方法的SQL查询,除非你使用方法表明它必须在客户端执行。它不会将每个方法分开执行。

这儿有一个从之前的查询中生成的T-SQL:

SELECT

[Extent1].[ContactID] AS [ContactID],

[Extent1].[FirstName] AS [FirstName],

[Extent1].[LastName] AS [LastName],

[Extent1].[Title] AS [Title],

[Extent1].[AddDate] AS [AddDate],

[Extent1].[ModifiedDate] AS [ModifiedDate]

FROM [dbo].[Contact] AS [Extent1]

WHERE N'Robert' = [Extent1].[FirstName]

ORDER BY [Extent1].[LastName] ASC

 

使用查询构造方法和实体SQL查询

在实体SQL中也可以使用方法语法,虽然只有有限的几个方法:13个,事实上,还包括Where和Select。这些方法被称为查询构造方法。查询构建方法像他们的名称建议的那样:使用正确的实体SQL表达式创建对象查询(ObjectQuery)。

虽然查询构造器可能看起来像某些LINQ方法,他们绝对不同。当你使用基于参数表达式(它将包括或者LINQ查询的lambda表达式,或者实体SQL表达式)的查询构造方法时, 编译器可以辨别。

注:在学习不同的查询方法时,因为到目前为止只探索了WHERE和SELECT,我们暂时不列出方法和运算符,在后面的章节中我们再列举更多的查询方法。

示例3-6展示了最近的查询,它使用了实体SQL作为方法参数。

示例 3-6. Entity SQL query builder method

var contacts = context.Contacts

.Where("it.FirstName = 'Robert'")

.OrderBy("it.LastName");

考虑这些表达式时最常见的问题是"它来自于哪里?"。它是控制变量的默认别名。因为你必须与其它查询一起,就没有机会定义控制变量,尽管你是可以为内嵌的查询定义变量,如我们在示例3-8中看到的。

在调试时,你可以检查contacts对象查询的CommandText属性,看看查询构造器确实为你创建了如示例3-7中的实体SQL。它可能比你自己写过的查询略显复杂。这是查询构造器必须流利的结果。此外,它没有在表达式中指定EntityContainer的名称,它是你在构造实体SQL中无法避免的。

示例 3-7. The Entity SQL built by the query builder methods

SELECT VALUE it

FROM (SELECT VALUE it

FROM ([Contacts]) AS it

WHERE it.FirstName = 'Robert')

AS it

ORDER BY it.LastName

使用实体SQl和有着lambda表达式的LINQ方法创建查询构造方法之间有一个非常有趣的差别,那就是实体表达式移除了VB和C#之间语法差异的担心。

不管你使用的是LINQ谓词,还是实体SQL谓词,在编译期EF通过查看谓词能决定选择哪个查询编译路径。

指定控制变量

如你在示例3-8中看到的,你还可以联合查询构造方法。EF控制变量对所有新的OjbectQuery实例默认都是一样的。然而,当你拥有了ObjectQuery实例时,你可以改变控制变量的名称,通过设计名称的属性。在那儿你可以持续组合如示例3-8所示的查询。

示例 3-8. Naming a control variable

var contactsQuery = context.Contacts;

contactsQuery.Name = "con";

var contacts = contactsQuery.Where("con.FirstName = 'Robert'")

.OrderBy("con.lastname");

前面的示例演示了另一个特性,叫做可组合的查询。定义了一个查询,然后另一个查询基于此编写。第一个查询不会单独执行。它会被编译到第二个查询contacts中。当contacts查询最终被执行时,组合查询被EF编译,然后发往数据库。

LINQ to Entities查询也是可组合的。

posted @ 2012-10-25 11:45  鱼十七  阅读(1231)  评论(1编辑  收藏  举报