Laden...

[ExtensionMethods] LeftOuterJoin für IQueryable (und IEnumerable)

Erstellt von dN!3L vor 13 Jahren Letzter Beitrag vor 13 Jahren 3.778 Views
dN!3L Themenstarter:in
2.891 Beiträge seit 2004
vor 13 Jahren
[ExtensionMethods] LeftOuterJoin für IQueryable (und IEnumerable)

Die Standard-Extensions für IEnumerable/IQueryable enthalten ja bekanntlich kein Äquivalent für ein left outer join. Dieses kann jedoch leicht mit einem GroupJoin und DefaultIfEmpty nachgebildet werden(vgl. Join Operators):


public static class EnumerableExtensions
{
	public static IEnumerable<TResult> LeftOuterJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IQueryable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector)
	{
		return outer
			.GroupJoin(inner, outerKeySelector, innerKeySelector, (outerItem, innerGroup) => innerGroup.DefaultIfEmpty().Select(innerGroupItem => resultSelector(outerItem, innerGroupItem)))
			.SelectMany(value => value);
	}
}

Das knifflige ist nun aber, das ganze als Erweiterungsmethode für IQueryable bereitzustellen. Damit es z.B. der LINQ2SQL-Provider auch korrekt verarbeiten kann, muss nämlich komplett mit Expressions gearbeitet werden:


public static class QueryableExtensions
{
	public static IQueryable<TResult> LeftOuterJoin<TOuter, TInner, TKey, TResult>(this IQueryable<TOuter> outer, IQueryable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TOuter, TInner, TResult>> resultSelector)
	{
		ParameterExpression outerItemParameterExpression = resultSelector.Parameters[0]; // Parameter(typeof(TOuter), "outerItem");
		ParameterExpression innerGroupParameterExpression = Expression.Parameter(typeof(IEnumerable<TInner>), "innerGroup");
		ParameterExpression innerGroupItemParameterExpression = resultSelector.Parameters[1]; // Parameter(typeof(TInner), "innerGroupItem");

		MethodInfo enumerableSelectMethodInfo = typeof(Enumerable).GetGenericMethod("Select", typeof(IEnumerable<>), typeof(Func<,>)).MakeGenericMethod(typeof(TInner), typeof(TResult));
		MethodInfo enumerableDefaultIfEmptyMethodInfo = typeof(Enumerable).GetGenericMethod("DefaultIfEmpty", typeof(IEnumerable<>)).MakeGenericMethod(typeof(TInner));

		var callResultSelectorExpression = Expression.Invoke(resultSelector, outerItemParameterExpression, innerGroupItemParameterExpression);
		var callDefaultIfEmtptyExpression = Expression.Call(null, enumerableDefaultIfEmptyMethodInfo, innerGroupParameterExpression);
		var callSelectExpression = Expression.Call(null, enumerableSelectMethodInfo, callDefaultIfEmtptyExpression, Expression.Lambda<Func<TInner, TResult>>(callResultSelectorExpression, innerGroupItemParameterExpression));
		var selectManyExpression = Expression.Lambda<Func<TOuter, IEnumerable<TInner>, IEnumerable<TResult>>>(callSelectExpression, outerItemParameterExpression, innerGroupParameterExpression);

		return outer
			.GroupJoin(inner, outerKeySelector, innerKeySelector, selectManyExpression)
			.SelectMany(value => value);
	}
}

public static class TypeExtensions
{
	public static MethodInfo GetGenericMethod(this Type type, string name, params Type[] parameterTypes)
	{
		return type.GetMethods()
			.Where(method => method.Name == name)
			.Where(method => parameterTypes.SequenceEqual(method.GetParameters().Select(p =>(p.ParameterType.IsGenericType) ? p.ParameterType.GetGenericTypeDefinition() : p.ParameterType)))
			.SingleOrDefault();
	}
}

Schlagwörter: Left Outer Join, Extension Method, LINQ, IEnumerable, IQueryable, Expressions

(Der Anhang enthält zusätzlich noch XML-Kommentare)