如何使用LINQ查询项目,还包括缺少的项目?

时间:2008-09-29 19:18:51

标签: c# asp.net linq

我正在尝试绘制我们注册系统中每天的注册数量。我在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
    });
}

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

任何人都能找到优雅的解决方案吗?或者至少有一个不那么尴尬的事情?

3 个答案:

答案 0 :(得分: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();

答案 1 :(得分:0)

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

通常,您应该能够执行以下操作,但是您需要在SQL数据库中添加一个日历表,该表连接到注册表中的日期键(带有日期ID字段的外键),以及然后尝试:

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

答案 2 :(得分:0)

问题是您没有执行查询而没有日期范围。因此,您可以选择日期范围,针对数据库运行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;
   }
}