The cool thing with LINQ and Entity framework is that if you pass an expression through to you context, it creates an SQL Query based of that expression to fetch the data you need, instead of fetching loads of data down and having to sift through them in C#.
A recurring issue that plagued me was that if I needed to pass through a slightly different LINQ expressions based on certain conditions it looked like I was repeating code, and I don’t like repeating code.
after a few hours of playing around with different techniques I devised a little way to append to an expression.
first we need to make use of something known as an expression visitor. so what is an expression visitor ?
well, it’s cool object that allows you to visit expression trees and rewrite expressions/
for more information on expression visitors please go here
alright to the code!
here is an implementation of the Expression Visitor implementation
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; /// <summary> /// The Expression re-binder /// </summary> public class ExpressionRebinder : ExpressionVisitor { /// <summary> /// The map /// </summary> private readonly Dictionary<ParameterExpression, ParameterExpression> map; /// <summary> /// Initializes a new instance of the <see cref="ExpressionRebinder"/> class. /// </summary> /// <param name="map">The map.</param> public ExpressionRebinder(Dictionary<ParameterExpression, ParameterExpression> map) { this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); } /// <summary> /// Replacements the expression. /// </summary> /// <param name="map">The map.</param> /// <param name="exp">The exp.</param> /// <returns>Returns replaced expression</returns> public static Expression ReplacementExpression(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) { return new ExpressionRebinder(map).Visit(exp); } /// <summary> /// Visits the <see cref="T:System.Linq.Expressions.ParameterExpression" />. /// </summary> /// <param name="node">The expression to visit.</param> /// <returns> /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. /// </returns> protected override Expression VisitParameter(ParameterExpression node) { ParameterExpression replacement; if (this.map.TryGetValue(node, out replacement)) { node = replacement; } return base.VisitParameter(node); } }
the concept is very simple, we have two LINQ Expressions and we’d like to combine them. The Expression visitor is a middle piece that makes it really easy for us to do this, however so far we’ve only won half the battle – we have only set up a sort of mediator to help us achieve expression concatenation. now lets put this mediator to good use.
The following extension methods allow us to apply an AND to the two linq expressions, I’ve thrown in a little extra just to show what else you can achieve, I’ve also given you an OR implementation.
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using Jonsson.WMS.Core.Helpers; /// <summary> /// Lambda expression extensions /// </summary> public static class LambdaExtensions { /// <summary> /// Composes the specified left expression. /// </summary> /// <typeparam name="T">Param Type</typeparam> /// <param name="leftExpression">The left expression.</param> /// <param name="rightExpression">The right expression.</param> /// <param name="merge">The merge.</param> /// <returns>Returns the expression</returns> public static Expression<T> Compose<T>(this Expression<T> leftExpression, Expression<T> rightExpression, Func<Expression, Expression, Expression> merge) { var map = leftExpression.Parameters.Select((left, i) => new { left, right = rightExpression.Parameters[i] }).ToDictionary(p => p.right, p => p.left); var rightBody = ExpressionRebinder.ReplacementExpression(map, rightExpression.Body); return Expression.Lambda<T>(merge(leftExpression.Body, rightBody), leftExpression.Parameters); } /// <summary> /// Performs an "AND" operation /// </summary> /// <typeparam name="T">Param Type</typeparam> /// <param name="left">The left.</param> /// <param name="right">The right.</param> /// <returns>Returns the expression</returns> public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) { return left.Compose(right, Expression.And); } /// <summary> /// Performs an "OR" operation /// </summary> /// <typeparam name="T">Param Type</typeparam> /// <param name="left">The left.</param> /// <param name="right">The right.</param> /// <returns>Returns the expression</returns> public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right) { return left.Compose(right, Expression.Or); } }
So what’s going here ?
all the magic happens in the compose method, it basically takes the parameters from the left expression and the right expression, places them into what we can call a parameter dictionary or a map and attempts to join them based on the type of merge supplied.
the other 2 static methods make it really simple for you to do this in fluid while working with LINQ expressions I will demonstrate.
Expression<Func<Ticket, bool>> predicate = x => !x.IsDeleted; if (isSearch) { predicate = predicate.And(x => x.SearchString.Contains(searchParam)); }
Does that not look elegant ?
Great demo thanks
Just used your classes to implement enhanced custom filtering for data in a grid. Works perfectly, thanks!
Thank you, I will be correcting this issue.
Really great, thanks. You would have thought they’d build this into the framework.
Hopefully in the near future MS will simplify this.
Hi again,
I’ve come across something I can’t workout with your solution and I have posted it on stackoverflow:http://stackoverflow.com/questions/24690063/building-a-linq-expression-to-transate-to-parentheses-in-the-sql-output
Not sure if you can help but at least you may find it interesting.
Thanks again.
Hi Ben, sorry for a delayed answer, we had some power issues down this side. I have devised a simple solution, using the expression binding technique in this article.
The visual studios solution is available at http://www.waseem-sabjee.com/code/LinqExpressionFilterIssue.zip
I will reply to the stackoverflow post as soon as I sign up 🙂