Linq To Objects Extension: Full Outer Join
I need a report which shows a comparison of the sales of each service with the previous year. Some services might be new and they don't appear in the previous year, others might be put off and so there are no sales for them in the current year. There can be services sold in both periods.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static IEnumerable<TResult> FullOuterJoin<TOuter, TInner, TKey, TResult> | |
(this IEnumerable<TOuter> outer, | |
IEnumerable<TInner> inner, | |
Func<TOuter, TKey> outerKeySelector, | |
Func<TInner, TKey> innerKeySelector, | |
Func<TOuter, TInner, TResult> resultSelector) | |
{ | |
// left side | |
foreach (var current in outer) | |
{ | |
if (!inner.Select(i => innerKeySelector(i)).Contains(outerKeySelector(current))) | |
yield return resultSelector(current, default(TInner)); | |
} | |
// middle side | |
var innerJoin = outer.Join(inner, outerKeySelector, innerKeySelector, resultSelector); | |
foreach (var current in innerJoin) | |
yield return current; | |
//right side | |
foreach (var current in inner) | |
{ | |
if (!outer.Select(i => outerKeySelector(i)).Contains(innerKeySelector(current))) | |
yield return resultSelector(default(TOuter), current); | |
} | |
yield break; | |
} | |
public static IEnumerable<TResult> FullOuterJoin<TOuter, TInner, TKey, TResult> | |
(this IEnumerable<TOuter> outer, | |
IEnumerable<TInner> inner, | |
Func<TOuter, TKey> outerKeySelector, | |
Func<TInner, TKey> innerKeySelector, | |
Func<TOuter, TInner, TResult> resultSelector, | |
IEqualityComparer<TKey> comparer) | |
{ | |
foreach (var current in outer) | |
{ | |
if (!inner.Select(i => innerKeySelector(i)).Contains(outerKeySelector(current), comparer)) | |
yield return resultSelector(current, default(TInner)); | |
} | |
var innerJoin = outer.Join(inner, outerKeySelector, innerKeySelector, resultSelector, comparer); | |
foreach (var current in innerJoin) | |
yield return current; | |
foreach (var current in inner) | |
{ | |
if (!outer.Select(i => outerKeySelector(i)).Contains(innerKeySelector(current), comparer)) | |
yield return resultSelector(default(TOuter), current); | |
} | |
yield break; | |
} | |
An a sample: | |
var lastYearServices = new [] { | |
new { | |
Year = 2009, | |
Services = new []{ new { Service = "A", Amount = 2009200m }, | |
new { Service = "B", Amount = 2009100m }} | |
}, | |
new { | |
Year = 2010, | |
Services = new []{ new { Service = "X", Amount = 20102000m }} | |
}, | |
new { | |
Year = 2011, | |
Services = new []{ new { Service = "AA", Amount = 2011200m }} | |
} | |
}; | |
var currentYearServices = new [] { | |
new { | |
Year = 2010, | |
Services = new []{ new { Service = "B", Amount = 2010200m }, | |
new { Service = "C", Amount = 2010100m }} | |
}, | |
new { | |
Year = 2011, | |
Services = new []{ new { Service = "X", Amount = 2011200m }} | |
}, | |
new { | |
Year = 2012, | |
Services = new []{ new { Service = "AA", Amount = 2012200m }, | |
new { Service = "BB", Amount = 2012100m }} | |
} | |
}; | |
var query = from l in lastYearServices | |
join c in currentYearServices on l.Year + 1 equals c.Year | |
select new { | |
Year = c.Year, | |
Services = l.Services.FullOuterJoin(c.Services, ls => ls.Service, cs => cs.Service, (ls, cs) => new { | |
LastYearAmount = ls != null ? ls.Amount : default(decimal?), | |
CurrentYearAmount = cs != null ? cs.Amount : default(decimal?), | |
Sevice = ls != null ? ls.Service : cs.Service | |
}) | |
}; | |
query.Dump(); |

Comments
Post a Comment