1, What is Expression
1. How to define
- Expression < func < tSource, bool > > is the expression directory tree
- Expression cannot contain curly braces. It can only have one line of code
2. Difference between and entrustment
-
Wrap a layer of expression < > outside the delegate, which is the expression directory tree
-
The expression tree can be converted into a delegate through Compile()
3. Expression essence
- The expression directory tree is a class encapsulation that describes a structure with body parts and parameter parts
- The body part is divided into left and right. The interior describes the relationship between the left and right, which can be continuously split down, similar to a binary tree
- Each node after the expansion of the expression directory tree is also an expression directory tree
Expression<Func<People, bool>> expression = p => p.Id == 10; Func<People, bool> func = expression.Compile(); bool bResult = func.Invoke(new People() { Id = 10, Name = "Zhang San" });
2, Expression dynamic assembly
1. Most basic version
Expression<Func<int>> expression = () => 123 + 234; //Constant expression ConstantExpression expression1 = Expression.Constant(123); ConstantExpression expression2 = Expression.Constant(234); //Binary expression BinaryExpression binaryExpression = Expression.Add(expression1, expression2); Expression<Func<int>> expressionReslut = Expression.Lambda<Func<int>>(binaryExpression); Func<int> func = expressionReslut.Compile(); int iResult = func.Invoke();
2. Version with parameters
Expression<Func<int, int>> expression1 = m => m + 1; Func<int, int> func = expression1.Compile(); int iResult = func.Invoke(5); //Parameter expression ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m"); //Constant expression ConstantExpression constant = Expression.Constant(1, typeof(int)); //Binary expression BinaryExpression addExpression = Expression.Add(parameterExpression, constant); Expression<Func<int, int>> expression = Expression.Lambda<Func<int, int>>(addExpression, new ParameterExpression[1] { parameterExpression }); Func<int, int> func1 = expression.Compile(); int iResult1 = func1.Invoke(5);
3. With multiple parameters
Expression<Func<int, int, int>> expression = (m, n) => m * n + 2; Func<int, int, int> func = expression.Compile(); int iResult = func.Invoke(10, 20); //Parameter expression ParameterExpression parameterExpressionM = Expression.Parameter(typeof(int), "m"); ParameterExpression parameterExpressionN = Expression.Parameter(typeof(int), "n"); //Binary expression BinaryExpression multiply = Expression.Multiply(parameterExpressionM, parameterExpressionN); //Constant expression ConstantExpression constantExpression = Expression.Constant(2); //Binary expression BinaryExpression plus = Expression.Add(multiply, constantExpression); Expression<Func<int, int, int>> expression1 = Expression.Lambda<Func<int, int, int>>(plus, new ParameterExpression[2] { parameterExpressionM, parameterExpressionN }); Func<int, int, int> func1 = expression1.Compile(); int iResult1 = func1.Invoke(10, 20);
4. Object field value comparison
Similar to this more complex, it is recommended that you can decompile it
Expression<Func<People, bool>> predicate = c => c.Id == 10; Func<People, bool> func = predicate.Compile(); bool bResult = func.Invoke(new People() { Id = 10 }); //Parameter expression ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c"); //Reflection get properties FieldInfo fieldId = typeof(People).GetField("Id"); //Get the call Id through parameterExpression MemberExpression idExp = Expression.Field(parameterExpression, fieldId); //Constant expression ConstantExpression constant10 = Expression.Constant(10, typeof(int)); //Binary expression BinaryExpression expressionExp = Expression.Equal(idExp, constant10); Expression<Func<People, bool>> predicate1 = Expression.Lambda<Func<People, bool>>(expressionExp, new ParameterExpression[1] { parameterExpression }); Func<People, bool> func1 = predicate1.Compile(); bool bResult1 = func1.Invoke(new People() { Id = 10 });
5. Multiple conditions
If you encounter a long expression directory tree, it is recommended to assemble it from right to left
Expression<Func<People, bool>> predicate = c => c.Id.ToString() == "10" && c.Name.Equals("Zhang San") && c.Age > 35; Func<People, bool> func = predicate.Compile(); bool bResult = func.Invoke(new People() { Id = 10, Name = "Zhang San", Age = 36 }); ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "c"); //c.Age > 35 ConstantExpression constant35 = Expression.Constant(35); PropertyInfo propAge = typeof(People).GetProperty("Age"); MemberExpression ageExp = Expression.Property(parameterExpression, propAge); BinaryExpression cagExp = Expression.GreaterThan(ageExp, constant35); //c.Name.Equals("Zhang San") ConstantExpression constantrichard = Expression.Constant("Zhang San"); PropertyInfo propName = typeof(People).GetProperty("Name"); MemberExpression nameExp = Expression.Property(parameterExpression, propName); MethodInfo equals = typeof(string).GetMethod("Equals", new Type[] { typeof(string) }); MethodCallExpression NameExp = Expression.Call(nameExp, equals, constantrichard); //c.Id.ToString() == "10" ConstantExpression constantExpression10 = Expression.Constant("10", typeof(string)); FieldInfo fieldId = typeof(People).GetField("Id"); var idExp = Expression.Field(parameterExpression, fieldId); MethodInfo toString = typeof(int).GetMethod("ToString", new Type[0]); var toStringExp = Expression.Call(idExp, toString, Array.Empty<Expression>()); var EqualExp = Expression.Equal(toStringExp, constantExpression10); //c.Id.ToString() == "10"&& c.Name. Equals ("Zhang San") & & c.age > 35 var plus = Expression.AndAlso(EqualExp, NameExp); var exp = Expression.AndAlso(plus, cagExp); Expression<Func<People, bool>> predicate1 = Expression.Lambda<Func<People, bool>>(exp, new ParameterExpression[1] { parameterExpression }); Func<People, bool> func1 = predicate1.Compile(); bool bResult1 = func1.Invoke(new People() { Id = 10, Name = "Zhang San", Age = 36 });
3, Mapper mapping for Expression application
Requirement: the People field value needs to be mapped to the PeopleCopy field
1. Hard coding
Good performance, inflexible; Cannot share
PeopleCopy peopleCopy0 = new PeopleCopy() { Id = people.Id, Name = people.Name, Age = people.Age };
2. Reflection
Flexible, but poor performance
using System; namespace MyExpression.MappingExtend { public class ReflectionMapper { /// <summary> ///Reflection /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> /// <param name="tIn"></param> /// <returns></returns> public static TOut Trans<TIn, TOut>(TIn tIn) { TOut tOut = Activator.CreateInstance<TOut>(); foreach (var itemOut in tOut.GetType().GetProperties()) { var propIn = tIn.GetType().GetProperty(itemOut.Name); itemOut.SetValue(tOut, propIn.GetValue(tIn)); } foreach (var itemOut in tOut.GetType().GetFields()) { var fieldIn = tIn.GetType().GetField(itemOut.Name); itemOut.SetValue(tOut, fieldIn.GetValue(tIn)); } return tOut; } } }
call
PeopleCopy peopleCopy1 = ReflectionMapper.Trans<People, PeopleCopy>(people);
3. Serialization
Flexible, but poor performance
using Newtonsoft.Json; namespace MyExpression.MappingExtend { public class SerializeMapper { /// <summary> ///Serialization /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> public static TOut Trans<TIn, TOut>(TIn tIn) { string strJson = JsonConvert.SerializeObject(tIn); return JsonConvert.DeserializeObject<TOut>(strJson); } } }
call
PeopleCopy peopleCopy2 = SerializeMapper.Trans<People, PeopleCopy>(people);
4. Expression dynamic splicing + normal cache
- The process of changing People into PeopleCopy is encapsulated in a delegate, which is compiled through the expression directory tree. The process is dynamically assembled to adapt to different types
- During the first generation, a delegate is saved in the cache. If it comes the second time, the delegate can be directly obtained from the cache and run the delegate directly, which is efficient
using System; using System.Collections.Generic; using System.Linq.Expressions; namespace MyExpression.MappingExtend { public class ExpressionMapper { /// <summary> ///The dictionary cache stores the delegate, and the internal delegate is the conversion action /// </summary> private static Dictionary<string, object> _Dic = new Dictionary<string, object>(); /// <summary> ///Expression dynamic splicing + normal cache /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> /// <param name="tIn"></param> /// <returns></returns> public static TOut Trans<TIn, TOut>(TIn tIn) { string key = $"funckey_{typeof(TIn).FullName}_{typeof(TOut).FullName}"; if (!_Dic.ContainsKey(key)) { ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TOut).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } foreach (var item in typeof(TOut).GetFields()) { MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); Func<TIn, TOut> func = lambda.Compile();//The assembly is disposable _Dic[key] = func; } return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn); } } }
call
PeopleCopy peopleCopy3 = ExpressionMapper.Trans<People, PeopleCopy>(people);
5. Expression dynamic splicing + generic cache
Generic caching is to generate a copy for each combination of types, with the highest performance
using System; using System.Collections.Generic; using System.Linq.Expressions; namespace MyExpression.MappingExtend { /// <summary> ///Expression dynamic splicing + generic cache /// </summary> /// <typeparam name="TIn"></typeparam> /// <typeparam name="TOut"></typeparam> public class ExpressionGenericMapper<TIn, TOut>//Mapper`2 { private static Func<TIn, TOut> _FUNC = null; static ExpressionGenericMapper() { ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p"); List<MemberBinding> memberBindingList = new List<MemberBinding>(); foreach (var item in typeof(TOut).GetProperties()) { MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } foreach (var item in typeof(TOut).GetFields()) { MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)); MemberBinding memberBinding = Expression.Bind(item, property); memberBindingList.Add(memberBinding); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray()); Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[] { parameterExpression }); _FUNC = lambda.Compile();//The assembly is disposable } public static TOut Trans(TIn t) { return _FUNC(t); } } }
call
PeopleCopy peopleCopy4 = ExpressionGenericMapper<People, PeopleCopy>.Trans(people);
6. Performance comparison
Expression dynamic splicing + generic caching has high performance and flexibility
long common = 0; long generic = 0; long cache = 0; long reflection = 0; long serialize = 0; { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { PeopleCopy peopleCopy = new PeopleCopy() { Id = people.Id, Name = people.Name, Age = people.Age }; } watch.Stop(); common = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { PeopleCopy peopleCopy = ReflectionMapper.Trans<People, PeopleCopy>(people); } watch.Stop(); reflection = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { PeopleCopy peopleCopy = SerializeMapper.Trans<People, PeopleCopy>(people); } watch.Stop(); serialize = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { PeopleCopy peopleCopy = ExpressionMapper.Trans<People, PeopleCopy>(people); } watch.Stop(); cache = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 1_000_000; i++) { PeopleCopy peopleCopy = ExpressionGenericMapper<People, PeopleCopy>.Trans(people); } watch.Stop(); generic = watch.ElapsedMilliseconds; } Console.WriteLine($"common = { common} ms"); Console.WriteLine($"reflection = { reflection} ms"); Console.WriteLine($"serialize = { serialize} ms"); Console.WriteLine($"cache = { cache} ms"); Console.WriteLine($"generic = { generic} ms");
Operation results
common = 32 ms reflection = 1026 ms serialize = 2510 ms cache = 236 ms generic = 31 ms
4, ExpressionVisitor parsing Expression
1. Expression parsing
- Expression is parsed through the visitor pattern, and the ExpressionVisitor abstract class is officially provided
- The Visit method of ExpressionVisitor is an entry for parsing the Expression directory tree. The Visit method determines what Expression directory tree Expression is and uses different subdivision methods for further parsing
- The VisitBinary method of ExpressionVisitor is to parse binary expressions. All complex expressions will be disassembled into binary expressions for parsing
2. Expression modification
Customize an OperationsVisitor, inherit from ExpressionVisitor, copy the VisitBinary method of the parent class, and modify the resolution of Expression
OperationsVisitor definition
using System.Linq.Expressions; namespace MyExpression { /// <summary> ///Custom Visitor /// </summary> public class OperationsVisitor : ExpressionVisitor { /// <summary> ///Override parent class methods// Access to binary expressions ///Change addition to subtraction and multiplication to division in the expression directory tree /// </summary> /// <param name="b"></param> /// <returns></returns> protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType == ExpressionType.Add)//Add { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); return Expression.Subtract(left, right);//subtract } else if (b.NodeType==ExpressionType.Multiply) //Multiply { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); return Expression.Divide(left, right); //be divided by } return base.VisitBinary(b); } } }
Expression parsing transformation
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2; Console.WriteLine(exp.ToString()); OperationsVisitor visitor = new OperationsVisitor(); Expression expNew = visitor.Visit(exp); Console.WriteLine(expNew.ToString());
Operation results
(m, n) => ((m * n) + 2) (m, n) => ((m / n) - 2)
3. Encapsulated multi conditional connection extension method
Extension method implementation
/// <summary> ///Merge expression And Or Not extension method /// </summary> public static class ExpressionExtend { /// <summary> ///Merge expressions expr1 AND expr2 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expr1"></param> /// <param name="expr2"></param> /// <returns></returns> public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { if (expr1 == null || expr2 == null) { throw new Exception("null Can't handle"); } ParameterExpression newParameter = Expression.Parameter(typeof(T), "x"); NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter); Expression left = visitor.Visit(expr1.Body); Expression right = visitor.Visit(expr2.Body); BinaryExpression body = Expression.And(left, right); return Expression.Lambda<Func<T, bool>>(body, newParameter); } /// <summary> ///Merge expression expr1 or expr2 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expr1"></param> /// <param name="expr2"></param> /// <returns></returns> public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { if (expr1 == null || expr2 == null) { throw new Exception("null Can't handle"); } ParameterExpression newParameter = Expression.Parameter(typeof(T), "x"); NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter); Expression left = visitor.Visit(expr1.Body); Expression right = visitor.Visit(expr2.Body); BinaryExpression body = Expression.Or(left, right); return Expression.Lambda<Func<T, bool>>(body, newParameter); } /// <summary> ///Expression take non /// </summary> /// <typeparam name="T"></typeparam> /// <param name="expr"></param> /// <returns></returns> public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr) { if (expr == null) { throw new Exception("null Can't handle"); } ParameterExpression newParameter = expr.Parameters[0]; UnaryExpression body = Expression.Not(expr.Body); return Expression.Lambda<Func<T, bool>>(body, newParameter); } }
Custom Visitor
internal class NewExpressionVisitor : ExpressionVisitor { public ParameterExpression _NewParameter { get; private set; } public NewExpressionVisitor(ParameterExpression param) { this._NewParameter = param; } protected override Expression VisitParameter(ParameterExpression node) { return this._NewParameter; } }
Data filtering method definition
/// <summary> ///Filter data execution /// </summary> /// <param name="func"></param> private static void Do(Expression<Func<People, bool>> func) { List<People> people = new List<People>() { new People(){Id=4,Name="123",Age=4}, new People(){Id=5,Name="234",Age=5}, new People(){Id=6,Name="345",Age=6}, }; List<People> peopleList = people.Where(func.Compile()).ToList(); }
Expression splicing
Expression<Func<People, bool>> lambda1 = x => x.Age > 5; Expression<Func<People, bool>> lambda2 = x => x.Id > 5; Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2);//And Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);//or Expression<Func<People, bool>> lambda5 = lambda1.Not();//wrong Do(lambda3); Do(lambda4); Do(lambda5);
5, ToSql of ExpressionVisitor application
Requirement: implement ORM framework to map Expression to SQL
Customize a ConditionBuilderVisitor
Inherited from ExpressionVisitor, the method of copying the parent class, and the splicing of SQL in the Expression parsing process
using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace MyExpression { public class ConditionBuilderVisitor : ExpressionVisitor { private Stack<string> _StringStack = new Stack<string>(); /// <summary> ///Return the assembled sql conditional expression /// </summary> /// <returns></returns> public string Condition() { string condition = string.Concat(this._StringStack.ToArray()); this._StringStack.Clear(); return condition; } /// <summary> ///If it is a binary expression /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitBinary(BinaryExpression node) { if (node == null) throw new ArgumentNullException("BinaryExpression"); this._StringStack.Push(")"); base.Visit(node.Right);//Parse right this._StringStack.Push(" " + ToSqlOperator(node.NodeType) + " "); base.Visit(node.Left);//Parse left this._StringStack.Push("("); return node; } /// <summary> ///Resolve properties /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMember(MemberExpression node) { if (node == null) throw new ArgumentNullException("MemberExpression"); if (node.Expression is ConstantExpression) { var value1 = this.InvokeValue(node); var value2 = this.ReflectionValue(node); this._StringStack.Push("'" + value2 + "'"); } else { this._StringStack.Push(" [" + node.Member.Name + "] "); } return node; } private string ToSqlOperator(ExpressionType type) { switch (type) { case (ExpressionType.AndAlso): case (ExpressionType.And): return "AND"; case (ExpressionType.OrElse): case (ExpressionType.Or): return "OR"; case (ExpressionType.Not): return "NOT"; case (ExpressionType.NotEqual): return "<>"; case ExpressionType.GreaterThan: return ">"; case ExpressionType.GreaterThanOrEqual: return ">="; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; case (ExpressionType.Equal): return "="; default: throw new Exception("The method is not supported"); } } private object InvokeValue(MemberExpression member) { var objExp = Expression.Convert(member, typeof(object));//struct needs return Expression.Lambda<Func<object>>(objExp).Compile().Invoke(); } private object ReflectionValue(MemberExpression member) { var obj = (member.Expression as ConstantExpression).Value; return (member.Member as FieldInfo).GetValue(obj); } /// <summary> ///Constant expression /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitConstant(ConstantExpression node) { if (node == null) throw new ArgumentNullException("ConstantExpression"); this._StringStack.Push("" + node.Value + ""); return node; } /// <summary> ///Method expression /// </summary> /// <param name="m"></param> /// <returns></returns> protected override Expression VisitMethodCall(MethodCallExpression m) { if (m == null) throw new ArgumentNullException("MethodCallExpression"); string format; switch (m.Method.Name) { case "StartsWith": format = "({0} LIKE '{1}%')"; break; case "Contains": format = "({0} LIKE '%{1}%')"; break; case "EndsWith": format = "({0} LIKE '%{1}')"; break; default: throw new NotSupportedException(m.NodeType + " is not supported!"); } this.Visit(m.Object); this.Visit(m.Arguments[0]); string right = this._StringStack.Pop(); string left = this._StringStack.Pop(); this._StringStack.Push(String.Format(format, left, right)); return m; } } }
ConstantSqlString generic cache generated sql
using System; using System.Linq; namespace MyExpression { public class ConstantSqlString<T> { /// <summary> ///Generic cache, one cache per type /// </summary> private static string FindSql = null; /// <summary> ///Get query sql /// </summary> static ConstantSqlString() { Type type = typeof(T); FindSql = $"Select {string.Join(',', type.GetProperties().Select(c => $"[{c.Name}]").ToList())} from {type.Name}"; } /// <summary> ///Get query sql + filter criteria /// </summary> /// <param name="exp"></param> /// <returns></returns> public static string GetQuerySql(string exp) { return $"{FindSql} where {exp}"; } } }
General multi condition
Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Id > 5 && x.Name.StartsWith("1") // like '1%' && x.Name.EndsWith("1") // like '%1' && x.Name.Contains("1");// like '%1%' ConditionBuilderVisitor vistor = new ConditionBuilderVisitor(); vistor.Visit(lambda); string sql = ConstantSqlString<People>.GetQuerySql(vistor.Condition()); Console.WriteLine(sql);
External parameter variable
string name = "AAA"; Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == name || x.Id > 5; ConditionBuilderVisitor vistor = new ConditionBuilderVisitor(); vistor.Visit(lambda); string sql = ConstantSqlString<People>.GetQuerySql(vistor.Condition()); Console.WriteLine(sql);
Internal constant multi condition
Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5); ConditionBuilderVisitor vistor = new ConditionBuilderVisitor(); vistor.Visit(lambda); string sql = ConstantSqlString<People>.GetQuerySql(vistor.Condition()); Console.WriteLine(sql);
Operation results
Select [Age],[Name] from People where ((((( [Age] > 5) AND ( [Id] > 5)) AND ( [Name] LIKE '1%')) AND ( [Name] LIKE '%1')) AND ( [Name] LIKE '%1%')) Select [Age],[Name] from People where ((( [Age] > 5) AND ( [Name] = 'AAA')) OR ( [Id] > 5)) Select [Age],[Name] from People where (( [Age] > 5) OR (( [Name] = A) AND ( [Id] > 5)))
6, Multi conditional splicing example
1. Dynamic splicing lambda expression
/// <summary> ///Lambda expression splicing extension class /// </summary> public static class Utility { /// <summary> ///Lambda expression splicing /// </summary> /// <typeparam name="T"></typeparam> /// <param name="first"></param> /// <param name="second"></param> /// <param name="merge"></param> /// <returns></returns> public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) { // Build parameter mapping (from second parameter to first parameter) var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f); // Replace the parameter in the second lambda expression with the parameter in the first lambda expression var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); // Apply a combination of lambda expression bodies to the parameters in the first expression return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters); } /// <summary> ///and extension /// </summary> /// <typeparam name="T"></typeparam> /// <param name="first"></param> /// <param name="second"></param> /// <returns></returns> public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.And); } /// <summary> ///or extension /// </summary> /// <typeparam name="T"></typeparam> /// <param name="first"></param> /// <param name="second"></param> /// <returns></returns> public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) { return first.Compose(second, Expression.Or); } } /// <summary> /// /// </summary> public class ParameterRebinder : ExpressionVisitor { private readonly Dictionary<ParameterExpression, ParameterExpression> map; public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) { this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>(); } /// <summary> /// /// </summary> /// <param name="map"></param> /// <param name="exp"></param> /// <returns></returns> public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) { return new ParameterRebinder(map).Visit(exp); } /// <summary> /// /// </summary> /// <param name="p"></param> /// <returns></returns> protected override Expression VisitParameter(ParameterExpression p) { ParameterExpression replacement; if (map.TryGetValue(p, out replacement)) { p = replacement; } return base.VisitParameter(p); } } //Common uses, for example, our data layer encapsulates the following methods: /// <summary> ///Get an entity /// </summary> ///< param name = "expwhere" > query criteria < / param > /// <returns></returns> public virtual T Find(Expression<Func<T, bool>> expWhere) { T entity = null; using (IDbContext MsSqlDB = CreateSqlContext()) { IQuery<T> q = MsSqlDB.Query<T>(); entity = q.Where(expWhere).FirstOrDefault(); } return entity; } //lambda expressions can be dynamically spliced when calling the upper layer: Expression<Func<Books, bool>> where = c => true; if (bookID == "-1") { where = where.And(a => a.Id == "001"); } if (bookName == "-1") { where = where.Or(a => a.Name == "Test"); } new BaseLocalBll<Books>().Find(where);
2. Query splicing using Expression
public static class DynamicLinqExpressions { public static Expression<Func<T, bool>> True<T>() { return f => true; } public static Expression<Func<T, bool>> False<T>() { return f => false; } public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.Or(expr1.Body, invokedExpr), expr1.Parameters); } public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>()); return Expression.Lambda<Func<T, bool>>(Expression.And(expr1.Body, invokedExpr), expr1.Parameters); } } //The code pasted above provides the functions Or And that splice two lambda expressions together. The following is an example public static IEnumerable<T> FilterBy<T>(this IEnumerable<T> collection, IEnumerable<KeyValuePair<string, string>> query) { var filtersCount = int.Parse(query.SingleOrDefault(k => k.Key.Contains("filterscount")).Value); Expression<Func<T, bool>> finalquery = null; for (var i = 0; i < filtersCount; i += 1) { var filterValue = query.SingleOrDefault(k => k.Key.Contains("filtervalue" + i)).Value; var filterCondition = query.SingleOrDefault(k => k.Key.Contains("filtercondition" + i)).Value; var filterDataField = query.SingleOrDefault(k => k.Key.Contains("filterdatafield" + i)).Value; var filterOperator = query.SingleOrDefault(k => k.Key.Contains("filteroperator" + i)).Value; Expression<Func<T, bool>> current = n => GetQueryCondition(n, filterCondition, filterDataField, filterValue); if (finalquery == null) { finalquery = current; } else if (filterOperator == "1") { finalquery = finalquery.Or(current); } else { finalquery = finalquery.And(current); } }; if (finalquery != null) collection = collection.AsQueryable().Where(finalquery); return collection; }
Note that the above Or And complete the splicing of the two conditions, And finally execute the query in where.
3. Simple multi condition splicing method
var list = new List<Expression<Func<Model.PM_PickStore, bool>>>(); list.Add(o => true); if (DevID != 0) list.Add(o => o.DevID == DevID); if (StoreID != 0) list.Add(o => o.StoreID == StoreID); if (PlanNo != "") list.Add(o => o.PlanNo.Equals(PlanNo)); Expression<Func<Model.PM_PickStore, bool>> productQueryTotal = null; foreach (var expression in list) { productQueryTotal = expression.And(expression); } PM_PickStore_List.Where(productQueryTotal.Compile()).ToList();