开发者

Linq-Entities: Fetch data excluding the overlapping data ranges, pick the largest period

I have 2 tables, Imports and Periods.

Imports has the following structure:

AdminID, PeriodID, Some more fields
1,       1
1,       2
1,       6
1,       50

Periods table has the following structure:

PeriodID, PeriodType, StartDate,   EndDate,    Description
1,        1,          2007-01-01,  2007-12-31, Year 2007
2,        2,          2007-01-01,  2007-03-31, Quarter 1 2007
3,        2,          2007-04-01,  2007-06-30, Quarter 2 2007
4,        2,          2007-07-01,  2007-09-30, Quarter 3 2007
5,        2,          2007-10-01,  2007-12-31, Quarter 4 2007
6,        3,          2007-01-01,  2007-01-31, January 2007
.
.
.
50,       2,          2011-01-01,  2011-03-31, Quarter 1 2011

Now, I need to build a linq query to fetch only the largest period(ignoring the smaller overlapping periods) based on the data in Imports table!

When I query for AdminID = 1, I should only get PeriodID 开发者_如何学Python= 1 & 50, ignoring/excluding the PeriodIDs 2 & 6 as they overlap in 1 and 50 as there is no overlapping data yet!


You, can the max help for picking the largest period and while retrieving the values by comparing the PeriodIDs in both tables right.


I'm not sure whether there is a convenient way to do this in the database, but when you pull the data locally, you can do in-memory LINQ queries, if this is appropriate. You need to do this in thee steps.

Step 1: Define a Range class that allows you to do comparisons on periods (see below).

Step 2: Pulling the periods from the database:

var ranges = (
    from period in context.Periods
    where period.Imports.Any(i => i.AdminID == adminId)
    select new Range(period.StartDate, period.EndDate.AddDays(1)))
    .ToArray();

Note the .ToArray() to pull everything locally.

Step 3: Aggregating / merging all the periods into a list of non-overlapping periods:

var mergedPeriods = (
    from range in ranges
    select ranges.Where(p => p.OverlapsWith(range))
        .Aggregate((r1, r2) => r1.Merge(r2)))
    .Distinct();

For this to work you need a specially designed Range type that contains OverlapsWith, Merge and Equals methods. It might look like this:

public class Range : IEquatable<Range>
{
    public Range(DateTime start, DateTime exclusiveEnd)
    {
        if (exclusiveEnd < start)
            throw new ArgumentException();

        this.Start = start; this.End = exclusiveEnd;
    }

    public DateTime Start { get; private set; }
    public DateTime End { get; private set; }
    public TimeSpan Duration { get { return this.End - this.Start; } }

    public Range Merge(Range other)
    {
        if (!this.OverlapsWith(other)) throw new ArgumentException();

        var start = this.Start < other.Start ? this.Start : other.Start;
        var end = this.End > other.End ? this.End : other.End;

        return new Range(start, end);
    }

    public bool Contains(Range other)
    {
        return this.Start <= other.Start && this.End > other.End;
    }

    public bool OverlapsWith(Range other)
    {
        return this.OverlapsOnStartWith(other) ||
            other.OverlapsOnStartWith(this) ||
            this.Contains(other) ||
            other.Contains(this);
    }

    private bool OverlapsOnStartWith(Range other)
    {
        return this.Start >= other.Start && this.Start < other.End;
    }

    public bool Equals(Range other)
    {
        return this.Start == other.Start && this.End == other.End;
    }
}

I hope this helps.


Well, after a long struggle, I did find an answer! With a single query to database! And for everyone's benefit posting the same.

var oImportPeriods = 
    from o in Imports
    where o.Administration.AdminID == 143
    orderby o.Period.PeriodID
    select o.Period;

var oIgnorePeriodList = (
    from oPeriod in oImportPeriods
    from oSearchPeriod in oImportPeriods
        .Where(o => o.PeriodID != oPeriod.PeriodID)
    where oPeriod.StartDate >= oSearchPeriod.StartDate
    where oPeriod.EndDate <= oSearchPeriod.EndDate
    select oPeriod.PeriodID)
    .Distinct();

var oDeletablePeriods = oAdministrationPeriods
    .Where(o => !oIgnorePeriodList.Contains(o.PeriodID));   

foreach(var o in oDeletablePeriods)
    Console.WriteLine(o.Name);
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜