对于单元测试,我们在内存集合中使用它来验证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()
};
答案 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() };