Entity Framework 4 in Action读书笔记——第四章:使用LINQ to Entities查询:筛选数据
本章所有的例子都是在第二章OrdeIT结构的基础上完成的。
4.1 筛选数据
基本需求:根据送货城市查询订单。
解决方案:使用LINQ的Where方法添加过滤功能。下面代码查询送货到New York的所有订单。
var orders = from o in ctx.Orders where o.ShippingAddress.City == "New York" select o;
新需求:查询多个送货城市的订单。
下面代码查询送货到New York和Seattle的所有订单。
var orders = from o in ctx.Orders where o.ShippingAddress.City == "New York" || o.ShippingAddress.City == "Seattle" select o;
如果按照上面的方法查询多个送货城市的订单是不合适的,因为提前并不知道要查询多少个城市的订单,更好的解决方案是使用LINQ的Contains方法(EF1.0不支持Contains方法,EF4.0支持Contains方法)。
var cities = new[] { "New York", "Seattle" }; var orders = from o in ctx.Orders where cities.Contains(o.ShippingAddress.City) select o;
4.1.1关联筛选
当涉及多于一个实体的查询,你就必须使用导航属性导航到模型。
单一关联筛选
基本需求:查询顾客的账单地址城市为New York的订单。
var orders = from o in ctx.Orders where o.Customer.BillingAddress.City=="New York" select o;
集合关联筛选
基本需求:查询所有包含又指定产品品牌的订单。
解决方案:必须聚合每个订单的详细信息,返回一个布尔值表示至少一个产品是否是指定的品牌。这种情况下,使用Any方法获得结果。这个方法属于集合家族,它接受一个Lambda表达式说明条件至少符合一次。
var orders = from o in ctx.Orders where o.OrderDetails.Any(d => d.Product.Brand == "MyBrand") select o;
基本需求:查询所有没有打折的订单。
解决方案:LINQ to Entities还有另一个集合方法:All。它允许你指定一个条件,确保在查询集合中的相都符合这个条件。在本例中,条件就是每个详细信息的Discount属性为0。
var orders = from o in ctx.Orders where o.OrderDetails.All(d => d.Discount == 0) select o;
基本需求:查询打折总价超过一定数量(本例中是5美元)的订单。使用Sum执行计算,查询就变得很简单了。
var orders = from o in ctx.Orders where o.OrderDetails.Sum(d => d.Discount * d.Quantity) > 5 select o;
Sum属于聚合方法家族,它对传入的Lambda表达式的返回值求和。
另外LINQ的方法可以写链式的,先看代码。
var orders = from o in ctx.Orders where o.OrderDetails .Select(d => d.Product.ProductId) .Distinct() .Count() > 2 select o;
我们看看这个查询做了什么。第一个方法Select,这是一个投影方法,用于设置查询的输出。在本例中,只需提取ProductId属性,所以该方法返回一个ProductId的整数列表。接下来使用Distinct方法去掉重复的ProductId,最后使用Count方法对不重复的整数列表求和。下面是该过程的流程图。
4.1.2分页结果
基本需求:查询顾客最新下得15个订单。
解决方案:实现这一过滤功能的方法是Take。这个方法接受一个整数,它指定了需要检索的对象个数。
var orders = (from o in ctx.Orders orderby o.OrderDate select o).Take(15);
新需求:尽管筛选实现了,但是用户每次查询订单都要获取这么多记录,降低了网页的性能和可用性。此外,在一个页面上处理成千上百的订单也不友好,用户需要分页显示。
解决方案:LINQ to Entities提供了一个方法,和Take配合使用:Skip。这个方法忽略前n个记录。实现分页,使用Skip跳过前n个记录,使用Take取得跳过后边的n个记录。下面的代码片段,跳过前10个记录,取得后10个记录。
var orders = (from o in ctx.Orders orderby o.OrderId select o).Skip(10).Take(10);
4.1.3检索一个实体
基本需求:用户选择一个订单后,需要一个表单可以查看详细信息,甚至修改它们。在OrderIT模型中,需要给订单的Id才能找到订单。
解决方案:LINQ查询返回IEnumerable<T>,即使只有一个对象也是如此。当你提前知道要获得一个对象,直接返回它而不是处理列表更方便。First和Single可以实现这一功能。
var orders = (from o in ctx.Orders where o.OrderId == 1 select o).First();
var orders = (from o in ctx.Orders where o.OrderId == 1 select o).Single();
上面查询的结果是Order对象。First方法被转换为TOP 1(TOP在SQL SERVER中是有效的,Provider为其他数据库产生恰当的等价表述)的SQL字句。
如果查询一个记录都不返回,First方法就会产生一个InvalidOperationException异常。你可以使用try-catch处理,但是在运行时处理这一异常是一项昂贵的任务。最好的选择是使用FirstOrDefault方法,它跟First有相同的行为,但是如果没有记录返回就有明显的区别了,它返回查询对象的默认值。因为Order是引用类型,如果没有记录返回,就会返回null。
使用Single可以达到同样的目标。First和Single的细微区别后者强制数据库查询只返回一个记录。如果返回两个记录,就抛出异常。如果零个记录返回,除非使用SingleOrDefault否则同样抛出异常,SingleOrDefault和FirstOrDefault有同样的行为。
4.14动态创建查询
因为用户想根据多个参数查询账单,你需要根据用户输入的参数动态创建查询。LINQ to Entities再一次保持了简单,就像普通的SQL。首先,创建ObjectQuery<T>的类实例。然后,遍历过滤器决定应用哪个过滤器。如果必须将过滤器应用到查询,就连接一个新的LINQ方法到ObjectQuery<T>实例,重新分配结果到相同的实例。最后,你会得到如下个一个动态查询。
var date = DateTime.Today; string city = null; var result = ctx.Orders.AsQueryable(); if (date != DateTime.MinValue) { result = result.Where(o => o.OrderDate < date); } if (!string.IsNullOrEmpty(city)) { result = result.Where(o => o.ShippingAddress.City == city); }
由于知道请求数据时查询才执行,你可以连接可能多的你需要的方法。当然,这种技术适用于所有的LINQ to Entities方法,但是可能当应用过滤器时才会使用到它。到目前为止,创建的查询都返回完整的实体。如果使用数据库的属于,它跟写SELECT * FROM 相似。根据我们的竟然,通常不需要一个实体公开所有的数据,而只是一小部分。在下一篇,我们将讨论如何只检索所需的数据。