【问题标题】:LINQ group by date - include empty days WITHOUT using joinLINQ 按日期分组 - 包括空天而不使用连接
【发布时间】:2013-06-13 11:38:37
【问题描述】:

使用 C#、NHibernate、NHibernate 到 LINQ。使用 NHibernate to LINQ,我没有 JOIN 功能。我也不能使用 QueryOver。

我有一个 LINQ 查询来计算潜在客户和销售额的数量。此表仅在新的潜在客户或销售完成时创建,因此有时不会插入任何行。执行以下查询有效

var query3 = query2.AsEnumerable()
           .Where(x => x.Created <= toDate.Value && x.Created >= fromDate.Value)
           .GroupBy(x => x.Created.Date)
           .Select(g => new ReferrerChart {
               Date = g.Key.Date,
               LeadsCount = g.Count(x => !x.IsSale),
               SalesCount = g.Count(x => x.IsSale)
           });

但有些日期不在其中(有 0 个潜在客户和销售的日期)。

如何在不使用连接的情况下包含这些日期并让他们设置 LeadsCount = 0 和 SalesCount = 0?

编辑:

有效的最终结果:

var selectedFromDate = fromDate.Value;
var selectedToDate = toDate.Value;

var selectedDates = Enumerable.Range(0, 1 + selectedToDate.Subtract(selectedFromDate).Days)
          .Select(offset => new ReferrerChart { 
                            Date = selectedFromDate.AddDays(offset),
                            LeadsCount = 0,
                            SalesCount = 0
                            })
          .ToList();

var query3 = Find(x => x.Source == "UserRef").AsEnumerable()
             .Where(x => x.Created <= toDate.Value && x.Created >= fromDate.Value)
             .GroupBy(x => x.Created.Date)
             .Select(g => new ReferrerChart {
                 Date = g.Key.Date,
                 LeadsCount = g.Count(x => !x.IsSale),
                 SalesCount = g.Count(x => x.IsSale)
             }).ToList();

var result = query3.Union(
                        selectedDates
                        .Where(e => !query3.Select(x => x.Date).Contains(e.Date)))
                        .OrderBy(x => x.Date);

【问题讨论】:

    标签: c# linq nhibernate join linq-method-syntax


    【解决方案1】:

    您可以使用您在内存中创建的虚拟项目Union() 查询中的数据。例如:

    var query4 = query3.ToList(); // Prevent multiple execution.
    
    var startDate = DateTime.Today.AddDays(-99);
    
    var emptyData = Enumerable.Range(1,100).Select (i => 
        new ReferrerChart
            {
               Date = startDate.AddDays(i),
               LeadsCount = 0,
               SalesCount = 0
            });
    
    var result = query4 .Union(
                 emptyData
                    .Where(e => !query4.Select(x => x.Date).Contains(e.Date)))
                 .OrderBy(x => x.Date);
    

    【讨论】:

    • 绝妙的答案!谢谢格特。
    【解决方案2】:

    我将在您的 LINQ 查询中更改两件事,我将包括空日期

    .Where(x =&gt; x.Created &lt;= toDate.Value &amp;&amp; x.Created &gt;= fromDate.Value)

    变成类似

    .Where(x =&gt; x.Created.Date == null || (x.Created &lt;= toDate.Value &amp;&amp; x.Created &gt;= fromDate.Value))

    我的另一个指针是您的 .GroupBy 需要 x.Created.Date,使用 IsNull 或类似函数将此处的值设置为一个值,无论条目是否为 Null

    例如(x.Created == null ? "" : x.Created.ToString("yyyyMMdd"))

    (我很抱歉,但我目前不在我的开发 PC 上,因此无法明确说明正确的代码)

    【讨论】:

      【解决方案3】:

      通过实现IEnumerable&lt;IGrouping&lt;TKey, TElement&gt;&gt;(从GroupBy 返回的类型)的扩展方法,有一个很好的方法。在我看来,这是最好的方法,原因有很多:

      • 不重新计算正在填充的结果集(.ToList() 也可以做到这一点,但代价是急切评估),
      • 保持GroupBy 的惰性求值(延迟执行),
      • 单行代码友好(可以链接到流利的 LINQ 查询中),
      • 通用,可重复使用,
      • 优雅、易读。

      实施

      public static IEnumerable<IGrouping<TKey, TElement>> Fill<TKey, TElement>(this IEnumerable<IGrouping<TKey, TElement>> groups, IEnumerable<TKey> filling)
      {
          List<TKey> keys = filling.ToList();
      
          foreach (var g in groups)
          {
              if(keys.Contains(g.Key))
                  keys.Remove(g.Key);
      
              yield return g;
          }
      
          foreach(var k in keys)
              yield return new EmptyGrouping<TKey, TElement>(k);
      }
      
      
      
      class EmptyGrouping<TKey, TElement> : List<TElement>, IGrouping<TKey, TElement>
      {
          public TKey Key { get; set; }
      
          public EmptyGrouping(TKey key)
          {
              this.Key = key;
          }
      }
      

      示例用法

      Random rand = new Random();
      
      var results = Enumerable.Repeat(0, 5)                    // Give us five
          .Select(i => rand.Next(100))                         // Random numbers 0 - 99
          .GroupBy(r => r.Dump("Calculating group for:") / 10) // Group by tens (0, 10, 20, 30, 40...)
          .Fill(Enumerable.Range(0, 10))                       // Fill aby missing tens
          .OrderBy(g => g.Key);                                // Sort
      
      @"Anything above = eager evaluations.
      Anything below = lazy evaluations.".Dump();
      
      results.Dump();
      

      样本输出

      (在计算查询时,顶部的五个整数会从查询内部打印出来。如您所见,只有一次计算传递)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-13
        • 2012-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-10
        相关资源
        最近更新 更多