C# .DefaultIfEmpty()基于日期值

C# .DefaultIfEmpty()基于日期值,c#,json,linq,C#,Json,Linq,我有一个实体框架模型Schedule,它映射到一个表dbo.Schedule。Schedule中的两个字段是hours(decimal)和week\u ending(DateTime) 我想将时间表中的数据输出为JSON,如下所示: { "week": [ "2017-08-11", "2017-08-18", "2017-08-25", "2017-09-01"], "hours": [ 40

我有一个实体框架模型
Schedule
,它映射到一个表
dbo.Schedule
Schedule
中的两个字段是
hours
decimal
)和
week\u ending
DateTime

我想将
时间表中的数据输出为JSON,如下所示:

{
   "week": [
         "2017-08-11",
         "2017-08-18",
         "2017-08-25",
         "2017-09-01"],
   "hours": [
         40,  
         40,
          0,
         30]
}
换句话说,我想将结束的
week\u
hours
结果连接到两个数组中,其中结果按
week分组,并在没有该周的记录时为
hours
插入一个
0

我知道
.DefaultIfEmpty()
可以实现后一种功能,但我不知道如何将“empty”定义为“两个查询日期之间的间隔超过7天”(即缺少一周)<代码>周末
值始终为星期五,因此它们总是相隔7天

我不知道从哪里开始。。。我有一个相当基本的LINQ查询,如下所示(省略了不相关的
Where
子句):

它在序列化后生成此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
    }
]

你可以考虑下面的方法

  • 枚举开始/结束日期之间符合条件的计划的集合
  • 确定并添加缺失的日期,然后排序结果
  • 创建一个
    数据
    对象以匹配所需的输出
  • 代码可能如下所示,以所需格式保留数据

    // 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)
    };
    
    //模拟linq连接
    var db=新
    {
    明细表=新列表
    {
    新时间表(){hours=40,week_ending=new DateTime(2017,8,11)},
    新计划(){hours=20,week_ending=new DateTime(2017,8,18)},//模拟多个记录
    新计划(){hours=20,week_ending=new DateTime(2017,8,18)},//模拟多个记录
    //8月25日没有记录
    新时间表(){hours=30,week_ending=new DateTime(2017,9,1)},
    }
    };
    //需要开始/结束日期,以便生成缺少的周数
    var startDate=新日期时间(2017年8月11日);
    var endDate=新日期时间(2017年9月1日);
    //从数据库枚举计划
    var schedules=db.schedules//添加日期逻辑之外的任何其他条件
    其中(m=>m.week\u ending>=开始日期和m.week\u ending m.week\u ending)
    .Select(m=>newschedule(){week_ending=m.Key,hours=m.Sum(s=>s.hours)})
    .AsEnumerable();
    //添加缺少的日期
    var结果=可枚举范围(0,1+结束日期减去(开始日期).Days)
    .Select(m=>startDate.AddDays(m))
    .Where(m=>m.DayOfWeek==DayOfWeek.Friday)//仅限周末
    .Where(m=>schedules.Any(s=>s.week_end==m)==false)//添加缺少的周数
    .Select(m=>newschedule(){week_ending=m,hours=0})
    .工会(附表)
    .OrderBy(m=>m.week\u结束);
    //枚举符合条件的已排序计划
    var数据=新
    {
    周=结果。选择(m=>m.week\U ending),
    小时数=结果。选择(m=>m.hours)
    };
    
    进近大纲:

    • 每周的现有工时总和
    • 生成一系列日期,然后
    • 。。。对于范围内的每个日期,生成一个零小时的空计划
    • 然后根据上面的总小时数对该列表进行修剪
    • 最后,将结果组合起来,给出整个范围的完整列表

    //示例类
    公课时间表
    {
    公共日期时间周{get;set;}
    公共整数小时数{get;set;}
    }
    //样本数据
    var scheduleTable=新列表();
    Add(new Schedule(){week=new DateTime(2017,8,11),hours=20});
    Add(new Schedule(){week=new DateTime(2017,8,11),hours=20});
    Add(new Schedule(){week=new DateTime(2017,8,18),hours=30});
    //将同一周的所有小时相加。
    var summedSchedule=scheduleTable.GroupBy(
    x=>x.week,
    x=>x.hours,
    (key,g)=>newschedule(){week=key,hours=g.Sum()}
    );
    //生成日期范围。您需要定义截止点。在这里
    //10周的日期是通过将7天连续添加到
    //开始日期(从上面找到的第一个日期)
    VarDates=Enumerable.Range(1,10)。选择(x=>scheduleTable.First().week.AddDays(7*x));
    //生成空计划,在该范围内每周分配零小时。
    var zeroSchedules=dates.Select(x=>newschedule(){week=x,hours=0});
    //找出有小时的星期的日期。
    var fullWeeks=summedSchedule.Where(x=>x.hours>0)。选择(x=>x.week);
    //使用上面的列表仅拉出不带小时的明细表对象。
    var emptyWeeks=zeroSchedules.Where(x=>!fullWeeks.Contains(x.week));
    //然后,将上述零小时工作周列表与开始时间合并
    //(汇总)具有小时数的周的列表。
    var combined=新列表();
    组合.AddRange(空周);
    合并。添加范围(汇总计划);
    combined=combined.OrderBy(x=>x.week.ToList();
    
    要解决的问题:您无法查询数据库中不存在的内容,例如缺少周数

    你需要一些时间范围,否则你的结果中会有很多周五。。。我将该范围定义为两个
    DateTime
    s
    fromIncl
    toExcl

    简单的部分是:查询已经每周汇总小时数的数据库,并将结果放入字典

    // 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);
    

    以原始查询为基础,对周进行分组,然后在范围中查找第一周/最后一周并生成周范围,然后将范围左键加入原始数据:

    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) };
    

    所以你想按时间的长短和日历的顺序来分组周,对吗?不,只是按日历的顺序。如果同一周有多个记录,我希望将小时值加在一起。例如,如果我有两个
    时间表
    ,都是以
    周结束=2017-08-11
    ,一个是
    小时=10
    ,另一个是
    小时=30
    ,那么输出将有一个si
    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) })
    
    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) };