【问题标题】:How do I use LINQ to query for items, but also include missing items?如何使用 LINQ 查询项目,但也包括丢失的项目?
【发布时间】:2008-09-29 19:18:50
【问题描述】:

我正在尝试在我们的注册系统中记录每天的注册数量。我在 sql server 中有一个 Attendee 表,它有一个 smalldatetime 字段 A_DT,这是该人注册的日期和时间。

我是从这个开始的:

var dailyCountList =
    (from a in showDC.Attendee
    let justDate = new DateTime(a.A_DT.Year, a.A_DT.Month, a.A_DT.Day)
    group a by justDate into DateGroup
    orderby DateGroup.Key
    select new RegistrationCount
    {
        EventDateTime = DateGroup.Key,
        Count = DateGroup.Count()
    }).ToList();

这很好用,但它不包括没有注册的日期,因为这些日期没有与会者记录。我希望包含每个日期,并且当给定日期没有数据时,计数应该为零。

所以这是我目前的工作解决方案,但我知道这很糟糕。 我在上面的代码中添加了以下内容:

// Create a new list of data ranging from the beginning to the end of the first list, specifying 0 counts for missing data points (days with no registrations)
var allDates = new List<RegistrationCount>();
for (DateTime date = (from dcl in dailyCountList select dcl).First().EventDateTime; date <= (from dcl in dailyCountList select dcl).Last().EventDateTime; date = date.AddDays(1))
{
    DateTime thisDate = date; // lexical closure issue - see: http://www.managed-world.com/2008/06/13/LambdasKnowYourClosures.aspx
    allDates.Add(new RegistrationCount
    {
        EventDateTime = date,
        Count = (from dclInner in dailyCountList
        where dclInner.EventDateTime == thisDate
        select dclInner).DefaultIfEmpty(new RegistrationCount
        {
            EventDateTime = date,
            Count = 0
        }).Single().Count
    });
}

所以我创建了另一个列表,并循环遍历我根据查询中的第一次和最后一次注册生成的日期序列,对于日期序列中的每个项目,我查询我的第一个查询的结果以获取信息关于给定日期,如果没有返回,则提供默认值。所以我最终在这里做了一个子查询,我想避免这种情况。

任何人都可以提出一个优雅的解决方案吗?或者至少有一个不那么尴尬的?

【问题讨论】:

    标签: c# asp.net linq


    【解决方案1】:

    O(n) 有 2 个枚举。在尝试之前将项目拉入内存非常好。不用考虑这些东西,数据库就足够了。

      if (!dailyCountList.Any())
          return;
    
      //make a dictionary to provide O(1) lookups for later
    
      Dictionary<DateTime, RegistrationCount> lookup = dailyCountList.ToDictionary(r => r.EventDateTime);
    
      DateTime minDate = dailyCountList[0].EventDateTime;
      DateTime maxDate = dailyCountList[dailyCountList.Count - 1].EventDateTime;
    
      int DayCount = 1 + (int) (maxDate - minDate).TotalDays;
    
      // I have the days now.
      IEnumerable<DateTime> allDates = Enumerable
        .Range(0, DayCount)
        .Select(x => minDate.AddDays(x));
    
      //project the days into RegistrationCounts, making up the missing ones.
      List<RegistrationCount> result = allDates
          .Select(d => lookup.ContainsKey(d) ? lookup[d] :
              new RegistrationCount(){EventDateTime = d, Count = 0})
          .ToList();
    

    【讨论】:

    • 这行得通,而且似乎是一个非常好的方法。但是,出于某种原因,在 LINQPad 中执行需要更长的时间(0.2 秒,而我的版本需要 0.15 秒)。我将假设这个字典版本总体上更好,并且创建字典和范围只是一点点开销。
    • 我第一次阅读问题时没有注意到,但dailyCountList是由EventDate排序的。这使得查找最小/最大日期变得微不足道(不需要枚举)。
    • 另外,allDates 在使用前不需要列出。编辑以将其保留为查询。
    【解决方案2】:

    那么,在 SP1 之后,左外连接的这种语法是否不再有效?

    通常,您应该能够执行以下操作,但您需要 SQL 数据库中的各种日历表与注册表中的日期键连接(在日期 ID 字段上带有外键),并且然后尝试:

    var query =
        from cal in dataContext.Calendar
        from reg in cal.Registrations.DefaultIfEmpty()
        select new
        {
            cal.DateID,
            reg.Something
        };
    

    【讨论】:

      【解决方案3】:

      问题是如果不执行查询,您就没有日期范围。因此,您可以选择一个日期范围,对您的数据库运行 SELECT MAX 和 SELECT MIN,或者执行您的查询,然后添加缺失的日期。

      var allDailyCountList =
         from d in Range(dc[0].EventDateTime, dc[dc.Count - 1].EventDateTime) 
         // since you already ordered by DateTime, we don't have to search the entire List
         join dc in dailyCountList on
            d equals dc.EventDateTime
         into rcGroup
         from rc in rcGroup.DefaultIfEmpty(
            new RegistrationCount()
            {
               EventDateTime = d,
               Count = 0
            }
         ) // gives us a left join
         select rc;
      
      public static IEnumerable<DateTime> Range(DateTime start, DateTime end) {
         for (DateTime date = start, date <= end; date = date.AddDays(1)) {
            yield return date;
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多