有很多原因决定你会手动写查询。或许由EF生成的SQL太慢,又或许执行起来浪费了太多资源。另一种情况可能是当你动态生成一个如此复杂的查询,创建SQL代码比使用LINQ to Entities简单。
在这种情况下,你自己可以使用ObjectContext类的ExecuteStoreQuery<T>创建一个SQL命令。它允许你发出任意的查询并将查询结果映射到类。它的使用如下:
var details = ctx.ExecuteStoreQuery<OrderDetail>("Select * from OrderDetail");
就是这么简单。查询被执行,列自动映射到实体。关于映射阶段有一个小小的提示:它绕过了EDM,而是使用另外一种基于属性-列名匹配的机制。这一行为如图:
这个映射解决方案的有一定的局限性:
1.如果一个属性跟相应的列名称不同,映射就不会执行并抛出异常。你可以很容易的通过在查询中使用AS SQL子句重命名列解决这个问题。
2.不能映射到有复杂属性的实体,因为没有办法将复杂属性内的名称和列进行匹配。
当然,你可以映射返回的数据到任何类型的类,不仅仅是那些定义在EDM中的。假设,你创建了一个类OrderDetailProjection,有Quantity,UnitPrice和Discount属性。你可以写一个查询,从OrderDetail表中取出Quantity, UnitPrice和 Discount列,并将结果映射到OrderDetailProjection。
public class OrderDetailProjection { public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal Discount { get; set; } }
var details = ctx.ExecuteStoreQuery<OrderDetailProjection> ("Select quantity, unitprice, discount from OrderDetail");
注意:
通过ExecuteStoreQuery<T>,你也可以启动存储过程。但是我们不建议这样做,因为EF本身就支持存储过程并且提供了比ExecuteStoreQuery<T>更多的选项。
如果查询带有参数,你可以使用ExecuteStoreQuery<T>的重载方法,它接受参数列表。这可能会非常棘手,所以让我们看一看。
当初EF团队设计如何传递参数时,有很多的选择。通过选择,现在有两种操作参数的方式。
1.使用带有编号列表,跟String.Format方法一样。
2.使用ADO.NET语法
下面,我们详细看一下这两种方式。
这是最简单的办法。在SQL查询中,将数字放在花括号中代表参数。然后,你在方法的第二个参数中传递参数。参数既可以是简单的数值也可以是DbParameter类的实例。这里有一些建议,要牢记:
1.如果你使用DbParameter实例,你必须使用与数据库Provider相应的具体类型。例如,如果对SQL Server就必须使用SqlParameter,对OLE DB就必须使用OleDbParameter。如果你使用其他的实例,在运行时会得到InvalidCastException。
2.如果你有多个参数,你不能混用DbParameter实例和纯值。你必须选择其一。否则,在运行时会得到InvalidOperationException。
3.如果你使用简单的数值作为参数,它们传过来的顺序必须和它们在查询中的顺序一样。
使用简单数值传递参数
var names = ctx.ExecuteStoreQuery<string> ("SELECT name FROM company WHERE shippingcity = {0} and billingcity = {1}", "New York", "Seattle");
使用SqlParameter传递参数
var p0 = new SqlParameter("p0", DbType.String) { Value = "New York" }; var p1 = new SqlParameter("p1", DbType.String) { Value = "Seattle" }; var names = ctx.ExecuteStoreQuery<string>("SELECT name FROM company WHERE shippingcity = {0} and billingcity = {1}", p0, p1);
正如你所见,这里没有什么特别困难的。只要注意我们前面提到的陷阱即可。
注意:
即使此语法可能会导致你认为可以通过SQL注入攻击的影响,但绝对不是这种情况。参数往往作为安全的方式传递给数据库。
现在,让我们继续谈谈使用传统的参数
通过经典的ADO.NET写查询时,你通常写这样的东西表达参数:
SELECT * FROM table WHERE id = @id
这种语法对SQL Server provider是有效的。如果你使用OLE DB provider,你必须问号(?)代替@paramname。
这种方法使用ExecuteStoreQuery<T>方法仍然完全有效。不是将数字放在花括号中,而是放置参数。其他都一样。参数的值仍然可以作为简单数值或参数传递过来。
使用简单数值传递参数
var names = ctx.ExecuteStoreQuery<string> ("SELECT name FROM company WHERE shippingcity = @p0 and billingcity = @p1", "New York", "Seattle");
使用SqlParameter传递参数
var p0 = new SqlParameter("p0", DbType.String) { Value = "New York" }; var p1 = new SqlParameter("p1", DbType.String) { Value = "Seattle" }; var names = ctx.ExecuteStoreQuery<string> ("SELECT name FROM company WHERE shippingcity = @p0 and billingcity = @p1", p0, p1);如果将这段代码和前面的代码比较,你会发现只是SQL代码中参数声明发生了改变。剩下的仍然是相同的,也就是说,没有更多的东西需要学习。
到目前为止,我们已经介绍了如何编写功能强大的查询。现在,我们换个角度思考:你有多少相关的实体需要查询?是直接检索出所有相关的实体,还是根据代码需要。这显然是一个获取(fetching)问题,它几乎独立与你写的任何查询。