使用 Linq 在 C# 中进行日期范围插值
本文关键字:日期 范围 插值 Linq 使用 | 更新日期: 2023-09-27 18:30:33
我有一个事件类,其中包含事件的开始和结束日期以及一周中可能发生的特定日期:
public class Event
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public DayOfWeek[] DayOfWeekList { get; set; }
public string Title { get; set; }
}
我可以有一次发生的事件:
事件 1:开始日期 = 9/15/2013,结束日期 = 9/15/2013,DOW = 星期日
我可以举办多天的活动:
事件 2:开始日期 = 9/15/2013,结束日期 = 9/16/2013,DOW = 星期日、星期一
我可以在某个日期范围内发生事件,但仅限于一周中特定的、可能不连续的日子,例如,在 9 月份,但仅在周二和周四
:事件 3:开始日期 = 9/1/2013,结束日期 = 9/30/2013,DOW = 星期二、星期四
给定 9/1 - 9/16 的日期范围,我想在网格中将事件显示为一个事件日记录,如下所示:
活动 3: 9/3/2013 星期二活动 3: 9/5/2013 星期四活动 3: 9/10/2013 星期二活动 3: 9/12/2013 星期四活动 1: 9/15/2013 星期日活动 2: 9/15/2013 星期日活动 2: 9/16/2013 星期一
我只会在数据库中存储一条事件记录来表示事件天数,而不是每天存储一条记录。因此,对于上面的日期范围,我将从数据库中检索 3 个事件记录,并需要使用事件的星期几根据计划生成此日期范围的事件记录序列。
我将使用 Linq to SQL 检索 3 个事件记录,但随后需要扩展每个事件的计划以创建新的事件记录,每天一个。这似乎效率不高,尤其是在网站被广泛使用的情况下。
有没有办法使用 Linq 完成此操作?
另一种方法是在数据库中为事件发生的每一天提供不同的子记录,但如果有人将时间表从每个星期一更改为每个星期二,这将是一个痛苦的维护。此外,我们可以有没有定义结束日期的事件。
这是一个蛮力解决方案,但这样的事情应该有效:
var startDate = new DateTime(2013, 9, 1);
var endDate = new DateTime(2013, 9, 16);
var totalDays = (int)(endDate - startDate).TotalDays + 1;
var results =
from i in Enumerable.Range(0, totalDays)
let d = startDate.AddDays(i)
from e in eventList
where e.StartDate <= d &&
e.EndDate >= d &&
e.DayOfWeekList.Contains(d.DayOfWeek)
select new { Event = e, Date = d };
当我在内存中测试它时,这会生成正确的结果。遗憾的是,这可能无法很好地转换为纯 Linq 到 SQL 代码。我认为您最好的选择是首先具体化事件的子集,然后使用修改后的查询在内存中生成最终结果集:
var eventList =
(from e in db.Events
where e.StartDate <= endDate &&
e.EndDate >= startDate
select e)
.AsEnumerable();
var results =
from i in Enumerable.Range(0, totalDays)
let d = startDate.AddDays(i)
from e in eventList
where e.StartDate <= d &&
e.EndDate >= d &&
e.DayOfWeekList.Contains(d.DayOfWeek)
select new { Event = e, Date = d };
如果您最常阅读事件,并且不介意节省少量额外成本,则可以选择根据规则将事件具体化到新表中。如果需要,可以根据规则清理和重新生成此表,也可以根据需要有选择地重新填充此表。
保存事件 1时将创建一个条目,事件 ID:1
保存事件 2 时,将为日期范围创建与 DOW 属性匹配的条目,事件 ID:2
事件 3 将执行相同的操作,但日期范围/DOW 为 EventID:3
如果需要,您可以使用一些花哨的 LINQ 来确定要添加哪些行,但最终结果是您希望物理实例行能够真正轻松地进行查询。
更新事件时,只需删除该 EventID 的所有行,然后根据更改重新创建它们。
鉴于上述情况,您的读取操作可以像以下那样简单:
EventInstances.Where(i => i.Date >= startDate && i.Date <= endDate);
相反,如果您想要慢速/复杂读取和快速写入,则需要在每次读取时生成此内存映射。
更新:一些代码来显示我的意思
填充表的逻辑与可用于创建内存中表的逻辑完全相同:
// slightly updated Event class
public class Event
{
public int ID { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public DayOfWeek[] DayOfWeekList { get; set; }
public string Title { get; set; }
}
var startDate = new DateTime(2013, 9, 1);
var endDate = new DateTime(2013, 9, 16);
var totalDays = (int)endDate.Subtract(startDate).TotalDays + 1;
// sample data, including a 4th event starting a while ago with no end date
var events = new List<Event> {
new Event { ID = 1, Title = "Event 1", StartDate = new DateTime(2013, 9, 15), EndDate = new DateTime(2013, 9, 15), DayOfWeekList = new[] { DayOfWeek.Sunday } },
new Event { ID = 2, Title = "Event 2", StartDate = new DateTime(2013, 9, 15), EndDate = new DateTime(2013, 9, 16), DayOfWeekList = new[] { DayOfWeek.Sunday, DayOfWeek.Monday } },
new Event { ID = 3, Title = "Event 3", StartDate = new DateTime(2013, 9, 1), EndDate = new DateTime(2013, 9, 30), DayOfWeekList = new[] { DayOfWeek.Tuesday, DayOfWeek.Thursday } },
new Event { ID = 4, Title = "Event 4", StartDate = new DateTime(2013, 1, 1), EndDate = null, DayOfWeekList = new[] { DayOfWeek.Wednesday } },
};
var eventsInRange = events
.Where(e => e.StartDate <= endDate)
.Where(e => (e.EndDate == null || e.EndDate.Value >= startDate ))
// if you are getting from the database, force this data to be
// retrieved since the following section would not work with the DB
.AsEnumerable();
var daysInRange = Enumerable
.Range(0, totalDays)
.Select(i => startDate.AddDays(i));
var eventInstances = daysInRange
.SelectMany(d => eventsInRange
.Where(e => e.EndDate == null || d <= e.EndDate.Value)
.Where(e => d >= e.StartDate)
.Where(e => e.DayOfWeekList.Contains(d.DayOfWeek))
.Select(e => new { Date = d, Day = d.DayOfWeek, Event = e }));
如果您想使用此数据填充表格以便于查询,只需根据合理的内容设置开始日期和结束日期(例如,1 年前开始,2 年前结束)。
如果要在更新后仅为一个事件重新填充,只需删除该事件 ID 的所有记录,并将eventsInRange
限制为仅更新事件。