如何使用 LINQ 查询项目,但也包括丢失的项目?

Posted

技术标签:

【中文标题】如何使用 LINQ 查询项目,但也包括丢失的项目?【英文标题】:How do I use LINQ to query for items, but also include missing items? 【发布时间】: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
    );

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

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

【问题讨论】:

【参考方案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;
   

【讨论】:

以上是关于如何使用 LINQ 查询项目,但也包括丢失的项目?的主要内容,如果未能解决你的问题,请参考以下文章

linq - 您如何查询一个查询源中不在另一个查询源中的项目?

如何以编程方式将 LINQ 查询转换为正确描述 linq 表达式的可读英文文本?

简单介绍C#集合查询Linq在项目中使用详解

这是在 .netCore 项目中使用 LINQ 查询连接两个表的正确方法吗?

如何将此 SQL 内部联接查询转换为 LINQ 语法?

LINQ to Entities 查询注意事项