2012-01-31 70 views
1

編輯 它看起來像創建一個表格,以分鐘爲單位保存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

回答

2

下面是產生復發點的交叉點和指定的子區間的功能:

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 <長B」的答案爲TRUE時,這意味着A有一個值。如果沒有,你會得到FALSE所有大於/小於/等於與B的比較。 – CodeGnome 2012-01-31 19:03:31

+0

結合你的算法與奧利維爾的防守邊緣情況。 – 2012-01-31 19:36:29

+0

這兩個很好的答案。這是更多的通過算法,儘管謝謝! – 2012-02-02 19:30:32

2

你可以創建自己的日期範圍方法

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

相關問題