使用linq组按日期查询填写缺少的日期

我有一个Linq查询,它基本上计算在特定日期创建的条目数,这是通过按年,月,日进行分组来完成的 。 问题是因为有些日子没有任何条目我需要用0计数的条目来填补缺少的“日历日”。 我的猜测是,这可能是通过联盟或其他东西完成的,或者甚至可能是一些简单的for循环来处理查询后的记录。

这是查询:

from l in context.LoginToken where l.CreatedOn >= start && l.CreatedOn <= finish group l by new{l.CreatedOn.Year, l.CreatedOn.Month, l.CreatedOn.Day} into groups orderby groups.Key.Year , groups.Key.Month , groups.Key.Day select new StatsDateWithCount { Count = groups.Count(), Year = groups.Key.Year, Month = groups.Key.Month, Day = groups.Key.Day })); 

如果我有12/1 – 12/4/2009的数据(简化):

 12/1/2009 20 12/2/2009 15 12/4/2009 16 

我希望通过代码添加12/3/2009 0的条目。

我知道通常这应该在数据库中使用非规范化表来完成,您可以使用数据填充或加入日历表,但我的问题是如何在代码中完成此操作?
可以在Linq完成吗? 应该在Linq完成吗?

我今天就这样做了。 我从数据库中收集了完整的数据,然后生成了一个“样本空”表。 最后,我使用真实数据对空表进行外连接,并使用DefaultIfEmpty()构造来处理知道数据库中缺少行以使用默认值填充它的时间。

这是我的代码:

 int days = 30; // Gather the data we have in the database, which will be incomplete for the graph (ie missing dates/subsystems). var dataQuery = from tr in SourceDataTable where (DateTime.UtcNow - tr.CreatedTime).Days < 30 group tr by new { tr.CreatedTime.Date, tr.Subsystem } into g orderby g.Key.Date ascending, g.Key.SubSystem ascending select new MyResults() { Date = g.Key.Date, SubSystem = g.Key.SubSystem, Count = g.Count() }; // Generate the list of subsystems we want. var subsystems = new[] { SubSystem.Foo, SubSystem.Bar }.AsQueryable(); // Generate the list of Dates we want. var datetimes = new List(); for (int i = 0; i < days; i++) { datetimes.Add(DateTime.UtcNow.AddDays(-i).Date); } // Generate the empty table, which is the shape of the output we want but without counts. var emptyTableQuery = from dt in datetimes from subsys in subsystems select new MyResults() { Date = dt.Date, SubSystem = subsys, Count = 0 }; // Perform an outer join of the empty table with the real data and use the magic DefaultIfEmpty // to handle the "there's no data from the database case". var finalQuery = from e in emptyTableQuery join realData in dataQuery on new { e.Date, e.SubSystem } equals new { realData.Date, realData.SubSystem } into g from realDataJoin in g.DefaultIfEmpty() select new MyResults() { Date = e.Date, SubSystem = e.SubSystem, Count = realDataJoin == null ? 0 : realDataJoin.Count }; return finalQuery.OrderBy(x => x.Date).AsEnumerable(); 

基本上我最终在这里做的是创建一个相同类型的列表,其中包含范围内的所有日期和计数的0值。 然后将我原始查询的结果与此列表结合使用。 主要障碍是创建自定义IEqualityComparer。 有关详细信息, 请单击此处

您可以生成从“开始”开始到“完成”结束的日期列表,然后逐步检查每个日期的计数数量

我创建了一个辅助函数,它被设计为与匿名类型一起使用,并以尽可能通用的方式重用。

假设这是您查询每个日期的订单列表的查询。

 var orders = db.Orders .GroupBy(o => o.OrderDate) .Select(o => new { OrderDate = o.Key, OrderCount = o.Count(), Sales = o.Sum(i => i.SubTotal) } .OrderBy(o => o.OrderDate); 

为了我的工作function,请注意此列表必须按日期排序。 如果我们有一天没有销售,那么列表中就会有一个漏洞。

现在为将使用默认值(匿名类型的实例)填充空白的函数。

  private static IEnumerable FillInEmptyDates(IEnumerable allDates, IEnumerable sourceData, Func dateSelector, Func defaultItemFactory) { // iterate through the source collection var iterator = sourceData.GetEnumerator(); iterator.MoveNext(); // for each date in the desired list foreach (var desiredDate in allDates) { // check if the current item exists and is the 'desired' date if (iterator.Current != null && dateSelector(iterator.Current) == desiredDate) { // if so then return it and move to the next item yield return iterator.Current; iterator.MoveNext(); // if source data is now exhausted then continue if (iterator.Current == null) { continue; } // ensure next item is not a duplicate if (dateSelector(iterator.Current) == desiredDate) { throw new Exception("More than one item found in source collection with date " + desiredDate); } } else { // if the current 'desired' item doesn't exist then // create a dummy item using the provided factory yield return defaultItemFactory(desiredDate); } } } 

用法如下:

 // first you must determine your desired list of dates which must be in order // determine this however you want var desiredDates = ....; // fill in any holes var ordersByDate = FillInEmptyDates(desiredDates, // Source list (with holes) orders, // How do we get a date from an order (order) => order.OrderDate, // How do we create an 'empty' item (date) => new { OrderDate = date, OrderCount = 0, Sales = 0 }); 
  • 必须确保所需日期列表中没有重复项
  • desiredDatessourceData必须按顺序排列
  • 因为如果您使用的是匿名类型,该方法是通用的,那么编译器会自动告诉您“默认”项目与常规项目的“形状”是否相同。
  • 现在我在sourceData包含对重复项的检查,但是在sourceData中没有这样的检查
  • 如果要确保按日期排序列表,则需要添加额外的代码