2012-01-31 3 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은() 옵션이 될 것입니다 간단하지만 범위의 더 긴 버전은 없습니다. 내 유일한 옵션은 Aggregate라고 생각하지만 아직 그 기능이 그리 강하지는 않습니다.

마지막으로 쿼리가 작동하면 시간 창 내에있는 다른 시작일과 종료일 사이의 값을 반환하고 싶습니다. 그것은 SkipWhile/TakeWhile을 사용하기에 충분합니다. 여기

는 DateTime.Ticks 나는 내가 필요한 것은 EF 쿼리를 통해 실행할 수 LongRange의 구현입니다 생각하는 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) }; 

있다면 내가 할 수있는 방법입니다.

+0

매일/시간 단위가 아닌 모든 틱에 대한 날짜를 반환하는 것이 더 적절한 것 같다 : – Magnus

+1

나는 네가 원하는 것을 얻을 수 있다고하더라도, 그 성능을 향상시키는 방법을 물어볼 것이다. 첫 번째 단계가 쉽고 두 번째 단계가 어려워지면 첫 번째 단계가 잘못된 경로에 있었음을 의미 할 수 있습니다. –

+0

[Quartz.NET] (http://quartznet.sourceforge.net/) 또는 [iCalendar] (http://sourceforge.net/projects/dday-ical/) – Magnus

답변

2

다음은 재발 포인트의 교회법 및 지정된 서브 구간을 산출하는 기능입니다. 이것을 어떻게 사용해야합니까?
+0

설명을 위해 "길이가 길면 A CodeGnome

+0

올리비에의 경비원과 알고리즘을 결합하여 엣지 케이스를 만듭니다. –

+0

모두 좋은 답변입니다. 덕분에 더 많은 thourough 알고리즘입니다! –

2

당신은이 RecurrenceIntervalTimeSpanDateTimeend로 선언되어 있다고 가정

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

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 
    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은 쿼리의 첫 번째 부분에는 영향을 미치지 않습니다.

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

where 절에서 일반적인 비교 만 사용하고 여기에 날짜 범위 열거를 사용하지 않으므로 쿼리 공급자가 변환해야하는 버전을 추가했습니다. –

+0

당신이 말하는 것을 봅니다. 일단 우리가 범위를 제한, 클라이언트 측면을 선택 하찮은해야합니다. 내가 말했듯이, 그것은 하루보다 더 큰 간격으로 작용합니다. 대부분의 경우 창은 1 주일 동안 유지됩니다. YAGNI에 이어 성능이 문제가되면 알고리즘을 다시 살펴 보겠습니다. –

+2

EF에 대한 쿼리는 결국 SQL 문으로 변환됩니다. 적절한 WHERE 절을 포함하는 것보다 더 많은 SQL 문을 기대할 수는 없습니다. 모든 보충 논리는 반환 된 레코드 ('되풀이'에 매핑 됨)에 대해 수행되어야합니다. 서버가 할 수있는 유일한 방법은 쿼리를 실행하는 것입니다. –

관련 문제