DefaultIfEmpty - LINQ to SQL 与内存中

发布于 2024-11-01 00:05:45 字数 1462 浏览 0 评论 0原文

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

对于此示例,我们有三个表 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 个项目。 一个项目有 1-n 个顺序。

我需要商品列表以及订购该商品的客户数量。如果 0 个客户订购了该商品,我希望仍将其退回,但计数为 0。

问题是订单和商品之间的多对多,这阻止我使用 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()
  };

For unit testing we use in memory collections to verify the logic of our LINQ queries. But, in the below scenario I'm seeing differences between the results from LINQ to SQL vs In Memory.

For this example we have three tables Customer, Order, Item. I would like the count of all items ordered by a customer. I would like to show customers who have not ordered any items as well. In SQL this would be an outer join. In LINQ to SQL I wrote this...

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()
   };

This works fine when we are going against the database. We get customers with and without orders along with counts. When we substitute in memory collections for unit testing we see an object reference not set exception. I've narrowed it down to the line "i.OrderId==o.OrderId" specifically o is null.

Based on how "DefaultIfEmpty" works, this is actually the behavior I would expect. DefaultIfEmpty returns a single element enumerable of null.

So how do I fix this code to work in both scenarios?

UPDATE:
While I was simplifying the problem I lost some important pieces of information. So let me restate the problem.

A Customer has 0-n Orders.
A Order has 1-n Items.
A Item has 1-n Order.

I need the list of Items along with the number of customers that ordered that item. If 0 customers ordered the item I want it to still be returned but with a count of 0.

The problem is the many-to-many between Order and Item which prevents me from using the join-into syntax.

I currently have something like this (hopefully without mistypes this time):

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()
  };

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

貪欢 2024-11-08 00:05:45

您的查询是从一开始就被收集的。这:

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()

但是,仍然存在相同的问题 - 如果 c.CustomerId 没有客户,则 o 将为 null。 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() };

Your query is pooched to start with. This:

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

... is trying to use o before it's really in scope. I'm surprised that works at all. It looks like you want:

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

However, that still has the same problem - o will be null if there are no customers for c.CustomerId. The SQL translation may well not exhibit the same behaviour, but it's frankly a little odd to start with IMO.

Try this instead, assuming you have the right relationship set up:

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() };

Here's another alternative, back to using explicit joins:

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() };
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文