使用Linq在C#中的日期范围插值

时间:2013-09-08 22:56:58

标签: c# linq linq-to-sql

我有一个Event类,其中包含开始日期和结束日期以及事件可能发生的特定日期:

public class Event
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public DayOfWeek[] DayOfWeekList { get; set; }
    public string Title { get; set; }
}

我可以发生一次发生的事件:

Event 1: StartDate = 9/15/2013, EndDate = 9/15/2013, DOW = Sunday

我可以举办多日活动:

Event 2: StartDate = 9/15/2013, EndDate = 9/16/2013, DOW = Sunday, Monday

我可以在一个日期范围内发生事件,但只能发生一周中特定的,可能是非连续的日子,例如9月份,但只有周二和周四:

Event 3: StartDate = 9/1/2013, EndDate = 9/30/2013, DOW = Tuesday, Thursday

鉴于日期范围为9/1 - 9/16,我想将事件显示为网格中的一个事件日记录,如下所示:

Event 3: 9/3/2013 Tuesday
Event 3: 9/5/2013 Thursday
Event 3: 9/10/2013 Tuesday
Event 3: 9/12/2013 Thursday
Event 1: 9/15/2013 Sunday
Event 2: 9/15/2013 Sunday
Event 2: 9/16/2013 Monday

我只会在数据库中存储一条事件记录来表示事件日,而不是每天记录一条记录。因此,对于上面的日期范围,我将从数据库中检索3个事件记录,并且需要根据使用事件的星期几的计划为该日期范围生成一系列事件记录。

我将使用Linq to SQL来检索3个事件记录,但随后需要扩展每个事件的计划以创建新的事件记录,每天一个。这似乎不太有效,特别是如果网站被广泛使用。

使用Linq有什么办法可以实现吗?

另一种方法是在数据库中每天发生不同的子记录,但如果有人将计划从每个星期一更改为每个星期二进行检查,这将是一个难以维护的问题。我们也可以在没有定义结束日期的情况下举办活动。

2 个答案:

答案 0 :(得分:3)

这是一个暴力解决方案,但这样的事情应该有效:

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-to-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 :(得分:1)

如果您经常阅读事件,并且不介意节省一些额外费用,则可以选择根据规则将事件实现到新表中。如果您希望根据规则清理和重新生成此表,或根据需要选择性地重新填充。

保存时的事件1将创建一个条目,其中EventID为:

保存后的事件2将创建与DOW属性匹配的日期范围的条目,其中EventID为:

事件3会做同样的事情,但其日期范围为/ DOW,事件ID为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限制为更新事件。