Assume you have products and categories. A client says that it is necessary to use other business processes for the categories with the rating value higher than 50. You have a solid experience and you understand that tomorrow this value may be different – 127.37. As you want to avoid this situation, you write the code in the following way:
public class Category : HasIdBase<int>
{
public static readonly Expression<Func<Category, bool>> NiceRating = x => x.Rating > 50;
//...
}
var niceCategories = db.Query<Category>.Where(Category.NiceRating);
Unfortunately, this will not work if you need to select products from the corresponding categories. NiceRating has the Expression<Func<Category, bool>> type. In the case of Product, you will need to use Expression<Func<Product, bool>>.
Thus, we need to convert Expression<Func<Category, bool>> into Expression<Func<Product, bool>>.
public class Product: HasIdBase<int>
{
public virtual Category Category { get; set; }
//...
}
var niceProductsCompilationError = db.Query<Product>.Where(Category.NiceRating);
Luckily, it is quite easy!
// In fact, we implement a composition of statements,
// which returns the statement matching the composition of target functions
public static Expression<Func<TIn, TOut>> Compose<TIn, TInOut, TOut>(
this Expression<Func<TIn, TInOut>> input,
Expression<Func<TInOut, TOut>> inOutOut)
{
// this is the X parameter => blah-blah. For a lambda, we need null
var param = Expression.Parameter(typeof(TIn), null);
// we get an object, to which this statement is applied
var invoke = Expression.Invoke(input, param);
// and execute “get an object and apply its statement”
var res = Expression.Invoke(inOutOut, invoke);
// return a lambda of the required type
return Expression.Lambda<Func<TIn, TOut>>(res, param);
}
// add an “advanced” variant of Where
public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable,
Expression<Func<T, TParam>> prop, Expression<Func<TParam, bool>> where)
{
return queryable.Where(prop.Compose(where));
}
// check
[Fact]
public void AdvancedWhere_Works()
{
var product = new Product(new Category() {Rating = 700}, "Some Product", 100500);
var q = new[] {product}.AsQueryable();
var values = q.Where(x => x.Category, Category.NiceRating).ToArray();
Assert.Equal(700, values[0].Category.Rating);
}
This is the implementation of statement composition in LinqKit. However, Entity Framework does not work with InvokeExpression and throws NotSupportedException. Do you know that LINQ has drawbacks? To work around this restriction, in LinqKit we use an extension method AsExpandable. Pete Montgomery described this issue in his blog. His version of Predicate Builder works both for IEnumerable<T> and IQueryable<T>.
Here is the code as is.
public static class PredicateBuilder
{
/// <summary>
/// Creates a predicate that evaluates to true.
/// </summary>
public static Expression<Func<T, bool>> True<T>() { return param => true; }
/// <summary>
/// Creates a predicate that evaluates to false.
/// </summary>
public static Expression<Func<T, bool>> False<T>() { return param => false; }
/// <summary>
/// Creates a predicate expression from the specified lambda expression.
/// </summary>
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
/// <summary>
/// Combines the first predicate with the second using the logical "and".
/// </summary>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
/// <summary>
/// Combines the first predicate with the second using the logical "or".
/// </summary>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
/// <summary>
/// Negates the predicate.
/// </summary>
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
/// <summary>
/// Combines the first expression with the second using the specified merge function.
/// </summary>
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
Tags: .net framework, linq Last modified: September 23, 2021



