Entity Framework 4 in Action读书笔记——第四章:使用LINQ to Entities查询:投影结果
4.2 投影结果
基本需求:用户只想查看订单日期和送货地址
解决方案:为每个订单提取出所有的数据浪费资源,在本例中,仅仅需要一部分数据,你需要的是订单的投影。
LINQ to Entities有一个投影方法:Select。我们看一个例子,查询所有订单的ID,日期和送货地址。
var result = from o in ctx.Orders select new { o.OrderId, o.OrderDate, o.ShippingAddress };
新需求:在一个表格列里查看地址信息。
var result = from o in ctx.Orders select new{ o.OrderId, o.OrderDate, ShippingAddress = string.Format("{0}-{1}-{2}-{3}", o.ShippingAddress.Address, o.ShippingAddress.City, o.ShippingAddress.ZipCode, o.ShippingAddress.Country) };
4.2.1关联投影
单一关联投影
需求:在表格列中显示顾客的信息。
var result = from o in ctx.Orders select new { o.OrderId, o.OrderDate, o.ShippingAddress, o.Customer };
然而检索顾客所有的信息是没必要的,因为所有的用户仅仅需要查看顾客的名字。下面的解决方案将关联的对象合并成一个有所需数据组成的单独对象。
var result = from o in ctx.Orders select new { o.OrderId, o.OrderDate, o.ShippingAddress, o.Customer.Name };
在现实世界中,使用匿名类型可能很痛苦。它们不能暴露在外层,除非你作为Object的实例暴露它们。根据我们的经验,这是不实际的,所以必须找到另一种方法。
一种选择是遍历返回的对象,然后实例化实体,只填写查询的属性。但是这种做法对代码和运行时性能都是一种浪费。
修复这一问题最自然的方法是使用对象初始化器创建一个模型实体的一个实例,只初始化需要的属性。不幸的是,这种做法EF中是不允许的,因为EF只能创建模型实体的完整示例。
我们强烈建议另一种方法,创建一个数据传输对象(DTO),直接在查询时使用投影数据填充,如下所示:
public class OrderDTO { public int Id { get; set; } public DateTime OrderDate { get; set; } public AddressInfo ShippingAddress { get; set; } public string CustomerName { get; set; } }
var result = from o in ctx.Orders select new OrderDTO { Id = o.OrderId, OrderDate = o.OrderDate, ShippingAddress = o.ShippingAddress, CustomerName = o.Customer.Name };
代码非常简单,只需定义一个DTO类,然后填充它来代替创建匿名类型。
集合关联投影
需求:在表格中列出每个订单的详细信息
解决方案:现在不是关联一个实体而是集合。幸运的是,下面的代码片段实现了这个任务。
var result = from o in ctx.Orders select new { o.OrderId, o.ShippingAddress, o.OrderDetails };
当然,用户不需要订单详细信息的所有属性,也就是说必须执行嵌套的投影,检索出详细信息需要的属性。这听起来有点困难,但事实证明相当容易。
var result = from o in ctx.Orders select new { o.OrderId, o.OrderDate, o.ShippingAddress, Details = from d in o.OrderDetails select new { d.OrderDetailId, d.Product.ProductId, d.Quantity } };
看到新版本,用户觉得订单和订单详细信息放在一块使表格很难读懂。现在用户只想查看订单的总价。这意味着返回类型又可以是平面结构了,因为不再有集合属性。现在看一下怎么从集合属性中查询一个单独列。
var result = from o in ctx.Orders select new { o.OrderId, o.OrderDate, o.ShippingAddress, Total = o.OrderDetails.Sum(d => d.Quantity * (d.UnitPrice - d.Discount)) };
正如你所见,集合关联投影并没有太大的挑战性。做一些练习,你就可以轻松的掌握你需要的方法。
4.2.2投影和对象追踪
当使用投影对象时,关于对象的追踪机制必须记住几点:
Object Services不允许创建只填充一部分属性的对象实体。
使用投影时,你不能使用对象初始化器设置模型实体的属性。尽管语法上是正确的,但是在运行时会抛出一个NotSupportedException的异常。出现这个的原因是,对象追踪只追踪那些实现了IEntityWithChangeTracker接口的对象。下面的代码是不合法的:
.Select(o => new Order { OrderId = o.OrderId });Object Services不追踪匿名实体。
因为对象追踪器只追踪实现了IEntityWithChangeTracker的对象,匿名类型被忽略了。