获取间隔时间和日期范围

问题描述:

编辑 它看起来像创建一个表格,以分钟为单位保存DateTimes以加入反对会最有意义。 100年的价值是〜52M行。由Ticks索引应该使查询运行得非常快。现在变成获取间隔时间和日期范围

感谢大家的反馈!

我有一个叫复发类,看起来像这样:

public class Recurrence 
{ 
    public int Id { get; protected set; } 
    public DateTime StartDate { get; protected set; } 
    public DateTime? EndDate { get; protected set; } 
    public long? RecurrenceInterval { get; protected set; } 

} 

这是一个实体框架POCO类。我希望使用标准查询操作符对这个类做两件事。 (以便查询完全运行在服务器端)。

首先,我想创建一个查询,该查询返回从给定重复间隔开始日期到结束日期的所有日期。迭代功能很简单

for(i=StartDate.Ticks; i<=EndDate.Ticks; i+=RecurrenceInterval) 
{ 
    yield return new DateTime(i); 
} 

Enumerable.Range()是一个选项,但没有长版本的Range。我在想我在这里唯一的选择是Aggregate,但我仍然不是那么强大。

最后,一旦我有这个查询工作,我想要返回那些在时间窗口内,即在不同的开始日期和结束日期之间的值。这很容易使用SkipWhile/TakeWhile。

以下是我能做到这一点,如果DateTime.Ticks是一个int

from recurrence in Recurrences 
let range = 
Enumerable 
    .Range(
    (int)recurrence.StartDate.Ticks, 
    recurrence.EndDate.HasValue ? (int)recurrence.EndDate.Value.Ticks : (int)end.Ticks) 
    .Where(i=>i-(int)recurrence.StartDate.Ticks%(int)recurrence.RecurrenceLength.Value==0) 
    .SkipWhile(d => d < start.Ticks) 
    .TakeWhile(d => d <= end.Ticks) 
from date in range 
select new ScheduledEvent { Date = new DateTime(date) }; 

我想我需要的是长程的实现,就可以执行了一个EF查询。

+0

这似乎更适合于每日/每小时的基础上,而不是每滴答返回日期。你如何使用这个? – Magnus 2012-01-31 17:53:23

+1

我认为,即使你能得到你想要的东西,你也会回过头去问如何提高它的性能。当第一步很容易,第二步很难,这可能意味着第一步走在错误的道路上。 – 2012-01-31 17:58:21

+0

你应该考虑使用一个现有的Recurrence库,例如[Quartz.NET](http://quartznet.sourceforge.net/)或[iCalendar](http://sourceforge.net/projects/dday-ical/) – Magnus 2012-01-31 18:00:44

下面是产生复发点的交叉点和指定的子区间的功能:

public class Recurrence 
{ 
    public int Id { get; protected set; } 
    public DateTime StartDate { get; protected set; } 
    public DateTime? EndDate { get; protected set; } 
    public long? RecurrenceInterval { get; protected set; } 

    // returns the set of DateTimes within [subStart, subEnd] that are 
    // of the form StartDate + k*RecurrenceInterval, where k is an Integer 
    public IEnumerable<DateTime> GetBetween(DateTime subStart, DateTime subEnd) 
    {    
     long stride = RecurrenceInterval ?? 1; 
     if (stride < 1) 
      throw new ArgumentException("Need a positive recurrence stride"); 

     long realStart, realEnd; 

     // figure out where we really need to start 
     if (StartDate >= subStart) 
      realStart = StartDate.Ticks; 
     else 
     { 
      long rem = subStart.Ticks % stride; 
      if (rem == 0) 
       realStart = subStart.Ticks; 
      else 
       // break off the incomplete stride and add a full one 
       realStart = subStart.Ticks - rem + stride; 
     } 
     // figure out where we really need to stop 
     if (EndDate <= subEnd) 
      // we know EndDate has a value. Null can't be "less than" something 
      realEnd = EndDate.Value.Ticks; 
     else 
     { 
      long rem = subEnd.Ticks % stride; 
      // break off any incomplete stride 
      realEnd = subEnd.Ticks - rem; 
     } 
     if (realEnd < realStart) 
      yield break; // the intersection is empty 

     // now yield all the results in the intersection of the sets 
     for (long t = realStart; t <= realEnd; t += stride) 
      yield return new DateTime(t); 
    } 

} 
+0

为了便于说明,当“很长?A CodeGnome 2012-01-31 19:03:31

+0

结合你的算法与奥利维尔的防守边缘情况。 – 2012-01-31 19:36:29

+0

这两个很好的答案。这是更多的通过算法,尽管谢谢! – 2012-02-02 19:30:32

你可以创建自己的日期范围方法

public static class EnumerableEx 
{ 
    public static IEnumerable<DateTime> DateRange(DateTime startDate, DateTime endDate, TimeSpan intervall) 
    { 
     for (DateTime d = startDate; d <= endDate; d += intervall) { 
      yield return d; 
     } 
    } 
} 

然后查询与

var query = 
    from recurrence in Recurrences 
    from date in EnumerableEx.DateRange(recurrence.StartDate, 
             recurrence.EndDate ?? end, 
             recurrence.RecurrenceInterval) 
    select new ScheduledEvent { Date = date }; 

这假定RecurrenceInterval被声明为TimeSpanendDateTime


编辑:这个版本会限制服务器端的重复发生吗?

var query = 
    from recurrence in Recurrences 
    where 
     recurrence.StartDate <= end && 
     (recurrence.EndDate != null && recurrence.EndDate.Value >= start || 
     recurrence.EndDate == null) 
    from date in EnumerableEx.DateRange(
     recurrence.StartDate, 
     recurrence.EndDate.HasValue && recurrence.EndDate.Value < end ? recurrence.EndDate.Value : end, 
     recurrence.RecurrenceInterval) 
    where (date >= start) 
    select new ScheduledEvent { Date = date }; 

这里返回的复发已经您考虑startend日期,因此不能返回过时的复发。 EnumerableEx.DateRange对查询的第一部分没有影响。

+0

我添加了一个应该由查询提供程序进行转换的版本,因为它只使用where子句中的正常比较,并且在此处不使用日期范围枚举。 – 2012-01-31 19:25:35

+0

我明白你在说什么。一旦我们限制了范围,选择客户端应该是微不足道的。就像我所说的那样,它适用于超过一天的时间间隔。大部分时间窗口也将持续一周。继YAGNI之后,如果性能成为问题,我们将重新审视算法。 – 2012-01-31 19:26:22

+2

对EF的查询最终将转换为SQL语句。你不能期望更多的这个SQL语句,而不是包含适当的WHERE子句。每个补充逻辑都必须在返回的记录上执行(映射到'Recurrence')。 (db)服务器唯一可以做的就是执行查询。 – 2012-01-31 19:31:37