【问题标题】:DefaultIfEmpty - LINQ to SQL vs In MemoryDefaultIfEmpty - LINQ to SQL 与内存
【发布时间】:2011-04-12 15:59:08
【问题描述】:

对于单元测试,我们使用内存集合来验证 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。

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

【问题讨论】:

    标签: linq linq-to-sql


    【解决方案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()
    

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

    【讨论】:

    • 在简化问题时,我打错了 o 和 oo。我还遗漏了我们之间的关系。 Order to Item 实际上是多对多而不是一对多。对不起。您是正确的,尽管备用加入语法可以解决问题。但我无法让它适用于多对多的情况。我将在上面更新我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-08
    • 2010-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    • 1970-01-01
    相关资源
    最近更新 更多