C# Expression (Advanced)

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();

Keywords: C# linq

Added by manalnor on Wed, 19 Jan 2022 12:35:28 +0200