Skip to content

Instantly share code, notes, and snippets.

@afruzan
Last active January 26, 2021 19:09
Show Gist options
  • Save afruzan/a46b528a145b59cc1c0d434701b5880c to your computer and use it in GitHub Desktop.
Save afruzan/a46b528a145b59cc1c0d434701b5880c to your computer and use it in GitHub Desktop.
Useful EF6/EfCore LINQ Query Extensions
public static class EFLinqExtensions
{
public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(this IQueryable<TOuter> outer, IQueryable<TInner> inner, Expression<Func<TOuter, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<JoinTuple<TOuter, TInner>, TResult>> resultSelector)
{
return outer.GroupJoin(inner, outerKeySelector, innerKeySelector, (b, @is) => new { b, @is }).SelectMany(i => i.@is.DefaultIfEmpty(), (b, i) => new JoinTuple<TOuter, TInner> { Outer = b.b, Inner = i }).Select(resultSelector);
}
public static IEnumerable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<JoinTuple<TOuter, TInner>, TResult> resultSelector)
{
return outer.GroupJoin(inner, outerKeySelector, innerKeySelector, (b, @is) => new { b, @is }).SelectMany(i => i.@is.DefaultIfEmpty(), (b, i) => new JoinTuple<TOuter, TInner> { Outer = b.b, Inner = i }).Select(resultSelector);
}
public static IQueryable<TResult> SelectDynamic<TSource, TResult>(this IQueryable<TSource> source, TResult resultType, IDictionary<string, string> keyPropertyNamesMap)
{
return source.Select(lambda_SelectProps<TSource, TResult>(keyPropertyNamesMap));
}
public static IQueryable<IGrouping<TKey, TElement>> GroupByDynamic<TElement, TKey>(this IQueryable<TElement> source, TKey keyType, IDictionary<string, string> keyPropertyNamesMap)
{
return source.GroupBy(lambda_SelectProps<TElement, TKey>(keyPropertyNamesMap));
}
public static IOrderedQueryable<TSource> OrderByDynamic<TSource>(this IQueryable<TSource> source, IEnumerable<string> sourcePropertyNames)
{
return source.OrderBy(lambda_SelectPropsAsTuple<TSource>(sourcePropertyNames));
}
public static IOrderedQueryable<TSource> OrderByDescendingDynamic<TSource>(this IQueryable<TSource> source, IEnumerable<string> sourcePropertyNames)
{
return source.OrderByDescending(lambda_SelectPropsAsTuple<TSource>(sourcePropertyNames));
}
private static Expression<Func<TSource, TResult>> lambda_SelectProps<TSource, TResult>(IDictionary<string, string> keyPropertyNamesMap)
{
var item = Expression.Parameter(typeof(TSource), "item");
var init = Expression.MemberInit(Expression.New(typeof(TResult)), keyPropertyNamesMap.Select((prop, j) =>
{
var names = prop.Value.Split('.');
var getter = Expression.Property(item, names[0]);
for (int i = 1; i < names.Length; i++)
{
getter = Expression.Property(getter, names[i]);
}
var setter = typeof(TResult).GetProperty(prop.Key);
if (setter == null)
{
throw new ArgumentException("property '" + prop.Key + "' was not found in " + typeof(TResult).Name);
}
return Expression.Bind(setter, getter);
}));
return Expression.Lambda<Func<TSource, TResult>>(init, item);
}
private static Expression<Func<TSource, object>> lambda_SelectPropsAsTuple<TSource>(IEnumerable<string> propertyNames)
{
var item = Expression.Parameter(typeof(TSource), "item");
var properties = propertyNames.Select(prop =>
{
var names = prop.Split('.');
var getter = Expression.Property(item, names[0]);
for (int i = 1; i < names.Length; i++)
{
getter = Expression.Property(getter, names[i]);
}
return getter;
}).ToArray();
if (properties.Length > 15)
{
throw new ArgumentOutOfRangeException("propertyNames max length is 15.");
}
var propertyTypes = Enumerable.Repeat(typeof(object), 15).ToArray();
var providedTypes = properties.Select(p => p.Type).ToArray();
Array.Copy(providedTypes, propertyTypes, providedTypes.Length);
var tupleType = typeof(QueryableTuple<,,,,,,,,,,,,,,>).MakeGenericType(propertyTypes);
var init = Expression.MemberInit(Expression.New(tupleType), properties.Select((getter, j) =>
{
var setter = tupleType.GetProperty("Prop" + (j + 1));
if (setter == null)
{
throw new ArgumentException("property '" + ("Prop" + (j + 1)) + "' was not found in " + tupleType.Name);
}
return Expression.Bind(setter, getter);
}));
return Expression.Lambda<Func<TSource, object>>(init, item);
}
private class QueryableTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
{
public T1 Prop1 { get; set; }
public T2 Prop2 { get; set; }
public T3 Prop3 { get; set; }
public T4 Prop4 { get; set; }
public T5 Prop5 { get; set; }
public T6 Prop6 { get; set; }
public T7 Prop7 { get; set; }
public T8 Prop8 { get; set; }
public T9 Prop9 { get; set; }
public T10 Prop10 { get; set; }
public T11 Prop11 { get; set; }
public T12 Prop12 { get; set; }
public T13 Prop13 { get; set; }
public T14 Prop14 { get; set; }
public T15 Prop15 { get; set; }
}
public static Func<TSource, string> GenerateConcatStringsFunc<TSource>(this IEnumerable<TSource> source, IEnumerable<string> propertyNames, string separator = null, string nullInsteadValue = null)
{
return lambda_ConcatStrings<TSource>(propertyNames, separator, nullInsteadValue).Compile();
}
public static Func<TSource, decimal> GenerateAddDecimalsFunc<TSource>(this IEnumerable<TSource> source, IEnumerable<string> propertyNames)
{
return lambda_AddDecimals<TSource>(propertyNames).Compile();
}
private static Expression<Func<TSource, string>> lambda_ConcatStrings<TSource>(IEnumerable<string> propertyNames, string separator = null, string nullInsteadValue = null)
{
var item = Expression.Parameter(typeof(TSource), "item");
var properties = propertyNames.Select(prop =>
{
var names = prop.Split('.');
var getter = Expression.Property(item, names[0]);
for (int i = 1; i < names.Length; i++)
{
getter = Expression.Property(getter, names[i]);
}
return getter;
});
var joinMethod = typeof(string).GetMethod("Join", new[] { typeof(string), typeof(object[]) });
Expression result = Expression.Call(joinMethod, Expression.Constant(separator ?? ""), Expression.NewArrayInit(typeof(object), properties.Select(g => Expression.Coalesce(Expression.Convert(g, typeof(object)), Expression.Constant(nullInsteadValue)))));
return Expression.Lambda<Func<TSource, string>>(result, item);
}
private static Expression<Func<TSource, decimal>> lambda_AddDecimals<TSource>(IEnumerable<string> propertyNames)
{
var item = Expression.Parameter(typeof(TSource), "item");
var properties = propertyNames.Select(prop =>
{
var names = prop.Split('.');
var getter = Expression.Property(item, names[0]);
for (int i = 1; i < names.Length; i++)
{
getter = Expression.Property(getter, names[i]);
}
return getter;
}).ToArray();
Expression result = properties[0];
for (int i = 1; i < properties.Length; i++)
{
result = Expression.Add(result, properties[i]);
}
return Expression.Lambda<Func<TSource, decimal>>(result, item);
}
}
public class JoinTuple<TOuter, TInner>
{
public TOuter Outer { get; set; }
public TInner Inner { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment