DefaultIfEmpty - LINQ to SQL vs In Memory

时间:2011-04-12 15:59:09

标签: linq linq-to-sql

对于单元测试,我们在内存集合中使用它来验证LINQ查询的逻辑。但是,在下面的场景中,我看到LINQ to SQL与In Memory的结果之间存在差异。

对于此示例,我们有三个表Customer,Order,Item。我想要计算客户订购的所有商品。我想向那些没有订购任何物品的顾客展示。在SQL中,这将是外连接。在LINQ to SQL中我写了这个......

var itemCounts = 
   from c in Customer
   from o in Order.Where(oo=>o.CustomerId==c.CustomerId).DefaultIfEmpty()
   from i in Item.Where(ii=>i.OrderId==o.OrderId).DefaultIfEmpty()
   group i by new { i.ItemId, c.CustomerId } into ig
   select new ItemCountResult {
     CustomerId = ig.Key.CustomerId,
     Count = ig.Count()
   };

当我们反对数据库时,这很好用。我们为客户提供订单和订单以及订单。当我们在内存集合中替换单元测试时,我们看到一个对象引用没有设置异常。我把它缩小到“i.OrderId == o.OrderId”这一行,特别是o为空。

根据“DefaultIfEmpty”的工作原理,这实际上是我期望的行为。 DefaultIfEmpty返回可枚举null的单个元素。

那么如何在两种情况下修复此代码?

更新 在我简化问题时,我丢失了一些重要的信息。所以让我重申一下这个问题。

客户有0-n订单。 订单有1-n项。 一个物品有一个订单。

我需要物品清单以及订购该物品的客户数量。如果0个客户订购了该项目,我仍希望它仍然被退回,但计数为0。

问题是Order和Item之间的多对多,这使我无法使用join-into语法。

我目前有这样的事情(希望这次没有错误):

var counts =
  from i in Items
  from oi in OrderItems.Where(z=>z.ItemId==i.ItemId).DefaultIfEmpty()
  from o in Orders.Where(z=>z.OrderId==oi.OrderId).DefaultIfEmpty()
  from c in Customers.Where(z=>z.CustomerId==o.CustomerId).DefaultIfEmpty()
  group c by new { i.ItemId, c.CustomerId } into cg
  select new CountResult {
    CustomerId = cg.Key.CustomerId,
    Count = cg.Count()
  };

1 个答案:

答案 0 :(得分:1)

您的查询是开始的。这样:

from ...
from o in Order.Where(oo=>o.CustomerId==c.CustomerId).DefaultIfEmpty()
from i in Item.Where(ii=>i.OrderId==o.OrderId).DefaultIfEmpty()

...正在尝试使用o才真正在范围内。我很惊讶这一切都很有效。看起来你想要:

from ...
from o in Order.Where(oo => oo.CustomerId == c.CustomerId).DefaultIfEmpty()
from i in Item.Where(ii => ii.OrderId == o.OrderId).DefaultIfEmpty()

但是,如果o没有客户,那么问题仍然存在 - c.CustomerId将为空。 SQL转换可能不会表现出相同的行为,但从IMO开始,坦率地说有点奇怪。

请尝试相反,假设您已建立正确的关系:

from c in Customer
join i in Items on c.CustomerId equals i.Order.OrderId into items
select new { CustomerId = c.CustomerId, Count = items.Count() };

这是另一种选择,回到使用显式连接:

from c in Customer
join oi in (from o in Orders
            join i in Items on o.OrderId equals i.OrderId
            select new { o, i })
on c.CustomerId equals oi.o.CustomerId into ordersForCustomer
select new { CustomerId = c.CustomerId, Count = ordersForCustomer.Count() };