【问题标题】:.DefaultIfEmpty() based on date values.DefaultIfEmpty() 基于日期值
【发布时间】:2017-08-03 16:54:54
【问题描述】:

我有一个实体框架模型Schedule,它映射到一个表dbo.ScheduleSchedule 中的两个字段是 hours (decimal) 和 week_ending (DateTime)。

我想将Schedule 表中的数据输出到 JSON,如下所示:

{
   "week": [
         "2017-08-11",
         "2017-08-18",
         "2017-08-25",
         "2017-09-01"],
   "hours": [
         40,  
         40,
          0,
         30]
}

换句话说,我想将week_endinghours 结果连接到两个数组中,其中结果是grouped by 周,并在没有记录时为hours 插入一个0 值那一周。

我知道.DefaultIfEmpty() 可以完成类似后者的操作,但我不知道如何将“空”定义为“两个查询日期之间超过 7 天”(即缺少一周) . week_ending 值总是星期五,所以它们总是相隔 7 天。

我不知道从哪里开始...我有一个相当基本的 LINQ 查询,像这样(不相关的 Where 子句省略):

var data =
  db.Schedules
    .OrderBy(s => s.week_ending)
    .Select(s => new
    { 
        week = s.week_ending,
        hours = s.hours
    });

在序列化后生成这个 JSON:

[
    {
        "week": "2017-08-11",
        "hours": 20
    },
    // I would like for this record to be grouped 
    // with the first into one "hours": 40 record
    {
        "week": "2017-08-11",
        "hours": 20
    },
    {
        "week": "2017-08-18",
        "hours": 40
    },
    // there is no "2017-08-25 record in the DB, 
    // but I would like for one to be printed with "hours": 0
    {
        "week": "2017-09-01",
        "hours": 30
    }
]

【问题讨论】:

  • 所以您想按周的时长和日历顺序对周进行分组,对吗?
  • 不,只是按日历顺序。如果同一周有多个记录,我想将小时值加在一起。例如,如果我有两个Schedules 都带有week_ending =2017-08-11,一个是hours = 10,另一个是hours = 30,那么输出将有一个带有week = 2017-08-11hours = 40 的记录。

标签: c# json linq


【解决方案1】:

您可以考虑以下方法

  1. 枚举与开始/结束日期之间的条件匹配的计划聚合
  2. 确定并添加缺失的日期,然后订购结果
  3. 创建一个data 对象以匹配所需的输出

data 保留为所需格式的代码可能如下所示:

// Simulate linq connection
var db = new
{
    Schedules = new List<Schedule>
    {
        new Schedule() { hours = 40, week_ending = new DateTime(2017,8,11) },
        new Schedule() { hours = 20, week_ending = new DateTime(2017,8,18) }, // Simulating multiple records
        new Schedule() { hours = 20, week_ending = new DateTime(2017,8,18) }, // Simulating multiple records
        // No records for 8/25
        new Schedule() { hours = 30, week_ending = new DateTime(2017,9,1) },
    }
};

// Need a start/end date so you can generate missing weeks
var startDate = new DateTime(2017, 8, 11);
var endDate = new DateTime(2017, 9, 1);

// Enumerate schedules from db
var schedules = db.Schedules // Add any other criteria besides date logic
    .Where(m => m.week_ending >= startDate && m.week_ending <= endDate)
    .GroupBy(m => m.week_ending)
    .Select(m => new Schedule() { week_ending = m.Key, hours = m.Sum(s => s.hours) })
    .AsEnumerable();

// Add missing dates
var results = Enumerable.Range(0, 1 + endDate.Subtract(startDate).Days)
    .Select(m => startDate.AddDays(m))
    .Where(m => m.DayOfWeek == DayOfWeek.Friday) // Only end of week
    .Where(m => schedules.Any(s => s.week_ending == m) == false) // Add missing weeks
    .Select(m => new Schedule() { week_ending = m, hours = 0 })
    .Union(schedules)
    .OrderBy(m => m.week_ending);

// Enumerate the ordered schedules matching your criteria
var data = new
{
    week = results.Select(m => m.week_ending),
    hours = results.Select(m => m.hours)
};

【讨论】:

    【解决方案2】:

    方法大纲:

    • 将每周的现有小时数相加。
    • 生成日期范围,然后...
    • ...对于范围内的每个日期,生成一个零小时的空时间表
    • 然后根据上述总小时数修剪此列表。
    • 最后,将结果组合在一起以提供整个范围的完整列表。

    .

    // sample class
    public class Schedule
    {
        public DateTime week {get; set;}
        public int hours {get; set;}
    }
    
    // sample data
    var scheduleTable = new List<Schedule>();
    
    scheduleTable.Add(new Schedule() { week=new DateTime(2017,8,11), hours=20});
    scheduleTable.Add(new Schedule() { week=new DateTime(2017,8,11), hours=20});
    scheduleTable.Add(new Schedule() { week=new DateTime(2017,8,18), hours=30});
    
    // Sum all hours that fall on the same week.
    var summedSchedule = scheduleTable.GroupBy(
        x => x.week, 
        x => x.hours, 
        (key, g) => new Schedule() { week = key, hours = g.Sum() }
    );
    
    // Generate range of dates. You'll need to define the cut off point. Here,
    // 10 weeks of dates are generated by adding 7 days successively to the
    // starting date (the first date found from above)
    var dates = Enumerable.Range(1, 10).Select(x => scheduleTable.First().week.AddDays(7 * x));
    
    // Generate empty schedules, assigning zero hours to every week in the range.
    var zeroSchedules = dates.Select(x => new Schedule() { week = x, hours = 0 });
    
    // Pull out the dates for the weeks that have hours.
    var fullWeeks = summedSchedule.Where(x => x.hours > 0).Select(x => x.week);
    
    // Use the above list to pull out only the Schedule objects without hours.
    var emptyWeeks = zeroSchedules.Where(x => !fullWeeks.Contains(x.week));
    
    // The above list of zero-hour-weeks is then combined with the starting
    // (summed) list of weeks that have hours.
    var combined = new List<Schedule>();
    combined.AddRange(emptyWeeks);
    combined.AddRange(summedSchedule);
    combined = combined.OrderBy(x => x.week).ToList();
    

    【讨论】:

      【解决方案3】:

      要解决的问题:您无法查询数据库中没有的内容,例如缺少几周。

      您需要一些时间范围,否则您的结果中确实有很多星期五...我将该范围定义为两个 DateTimes fromIncltoExcl

      最简单的部分:查询已经按周汇总小时数的数据库并将结果放入字典中。

      // SELECT week = sch.week_ending, hours = SUM(sch.hours)
      // FROM dbo.Schedules sch
      // WHERE sch.week_ending >= fromIncl AND sch.week_ending < toExcl
      // GROUP BY sch.week_ending
      var hoursByWeek = db.Schedules
          .Where(sch => sch.week_ending >= fromIncl && sch.week_ending < toExcl)
          .GroupBy(sch => sch.week_ending, (k, vs) => new { week = k, hours = vs.Sum(sch1 => sch1.hours) })
          .ToDictionary(sch => sch.week, sch => sch.hours);
      

      现在对于缺少的几周:您需要能够枚举它们,因为它们可能不存在于数据库中:

      public static IEnumerable<DateTime> EnumerateFridays(DateTime fromIncl, DateTime toExcl)
      {
          fromIncl = fromIncl.Date; // just to be sure
          switch (fromIncl.DayOfWeek)
          {
              case DayOfWeek.Sunday: fromIncl = fromIncl.AddTicks(5 * TimeSpan.TicksPerDay); break;
              case DayOfWeek.Monday: fromIncl = fromIncl.AddTicks(4 * TimeSpan.TicksPerDay); break;
              case DayOfWeek.Tuesday: fromIncl = fromIncl.AddTicks(3 * TimeSpan.TicksPerDay); break;
              case DayOfWeek.Wednesday: fromIncl = fromIncl.AddTicks(2 * TimeSpan.TicksPerDay); break;
              case DayOfWeek.Thursday: fromIncl = fromIncl.AddTicks(TimeSpan.TicksPerDay); break;
              // case DayOfWeek.Friday: break;
              case DayOfWeek.Saturday: fromIncl = fromIncl.AddTicks(6 * TimeSpan.TicksPerDay); break;
          }
          Debug.Assert(fromIncl.DayOfWeek == DayOfWeek.Friday);
          for (; fromIncl < toExcl; fromIncl = fromIncl.AddTicks(7 * TimeSpan.TicksPerDay))
              yield return fromIncl;
      }
      

      给定另一种方便的扩展方法:

      public static TValue ValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue))
      {
          TValue value;
          return dictionary.TryGetValue(key, out value) ? value : defaultValue;
      }
      

      你的结果表示为

      MyAwesomeClass.EnumerateFridays(fromIncl, toExcl)
      .Select(friday => new { week = friday, hours = hoursByWeek.ValueOrDefault(friday) })
      

      【讨论】:

        【解决方案4】:

        以您的原始查询为基础,对周进行分组,然后在范围内找到第一周/最后一周并生成周范围,然后将该范围左连接到原始数据:

        var groupeddata = from d in data
                  group d by d.week into dg
                  select new { week = dg.Key, hours = dg.Sum(d => d.hours)};
        
        var beginDate = groupeddata.Select(d => d.week).Min();
        var endDate = groupeddata.Select(d => d.week).Max();
        
        var weeks = Enumerable.Range(0, (endDate-beginDate).Days / 7 + 1).Select(n => beginDate.AddDays(7*n)).ToList();
        
        var ans = from w in weeks
                  join s in groupeddata on w equals s.week into sj
                  from s in sj.DefaultIfEmpty()
                  select new { week = w, hours = (s == null ? 0 : s.hours) };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-06-23
          • 2012-12-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多