C# Expression Tree Lambda Extension

I. Preface

Originally, this article was written when we need to use it later, but since we write about the expansion of expressions, we should finish it together.

Looking at this heading, there is a question: Lambda expression is an expression tree, how do we extend it? Look at the following and you will see.

Expressions Series Directory

C# Expression Tree Explanation (1)

C# expression tree traversal (2)

Paging Extension of C# Expression Tree (3)

C# Expression Tree Lambda Extension (IV)

II. Lambda Extension

Let's not rush to answer the above questions. Let's first look at such an application scenario.

A page request with some conditional queries. The request class is as follows

public class ScoreRequest
{
    public string CourseName { get; set; }
    public string StudentName { get; set; }
}

Require queries for data matching course names and student names

Data Source Let's look at the data source of the above example.

Data source class

public class ScoreClass
{
    public string CourseName { get; set; }
    public string StudentName { get; set; }
    public decimal Score { get; set; }
}

Add data

var datas = new List<ScoreClass>();
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student A",
    Score = 60
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student B",
    Score = 65
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student C",
    Score = 70
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student D",
    Score = 75
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student E",
    Score = 80
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student F",
    Score = 81
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student G",
    Score = 82
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student H",
    Score = 83
});
datas.Add(new ScoreClass
{
    CourseName = "Mathematics",
    StudentName = "Student I",
    Score = 84
});

Okay, now let's query the data.

var request = new ScoreRequest()
            {
                CourseName = "number",
                StudentName = "H"
            };
            var resultDatas = datas.Where(e => e.CourseName.Contains(request.CourseName) && e.StudentName.Contains(request.StudentName))
                .ToList();

If both CourseName and Student Name fields in the query object are worthwhile, it's OK to write that. If it's not worth it, then the final data will be inaccurate.

If sql statements are directly pieced together, we can use if(String.IsNullOrEmpty()) to judge, but now that we have judged, how do we piece together Lambda expressions?

So we need to extend Lambda expression to support this situation. That above question, do not need to answer specifically!!!!

Create a Lambda Extension class with the following code

public static class LambdaExtension
{
    public static Expression<Func<T, bool>> True<T>() { return param => true; }
    public static Expression<Func<T, bool>> False<T>() { return param => false; }
    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);
    }
    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);
    }
    private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
    {
        var map = first.Parameters
            .Select((f, i) => new { f, s = second.Parameters[i] })
            .ToDictionary(p => p.s, p => p.f);
        var secondBody = PFTParameterExtension.ReplaceParameters(map, second.Body);
        return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
    }

    private class PFTParameterExtension : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, ParameterExpression> map;

        public PFTParameterExtension()
        {

        }

        public PFTParameterExtension(Dictionary<ParameterExpression, ParameterExpression> map)
        {
            this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
        }

        /// <summary>
        /// Replacement parameters
        /// </summary>
        /// <param name="map">The map.</param>
        /// <param name="exp">The exp.</param>
        /// <returns>Expression</returns>
        public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
        {
            return new PFTParameterExtension(map).Visit(exp);
        }

        protected override Expression VisitParameter(ParameterExpression p)
        {
            ParameterExpression replacement;
            if (map != null && map.Count > 0 && map.TryGetValue(p, out replacement))
            {
                p = replacement;
            }
            return base.VisitParameter(p);
        }

    }

}

This privatizes an expression tree accessor, which is mainly used to synchronize the parameters in Lambda expressions.

Here's how to call

            var expression = LambdaExtension.True<ScoreClass>();
            if (!string.IsNullOrWhiteSpace(request.CourseName))
                expression = expression.And(e => e.CourseName.Contains(request.CourseName));
            if (!string.IsNullOrWhiteSpace(request.StudentName))
                expression = expression.And(et => et.StudentName.Contains(request.StudentName));

            var resultDatas = datas.Where(expression.Compile())
                .ToList();
            Console.WriteLine($"Query results:\n{string.Join("\n", resultDatas.Select(e => $"{e.StudentName} {e.CourseName} {e.Score}"))}");

where conditions only have delegates, and our expression is a Lambda expression, so we need Compile to compile delegates.

Operation results:

Looking at the code carefully, the parameter in the first condition Andis "e", And the parameter in the second condition is et. In the same Lambda expression (there is only one parameter), the parameters must be identical, so in the Lambda Extension class, when merging two Lambda expressions, it is necessary to merge the parameters into one. One.

After such expansion, we can patch up the expression we need according to our actual situation and get the result we want.

Three, summary

Finally, the explanation of expression tree can be concluded. I haven't written any articles like this since then. Now I think it's really tiring to write articles. The three days of Mid-Autumn Festival this year are all for the blog park. However, the three-day presentation basically paves the way for the technology needed for the later expansion of Dapper, and we will continue to explain ORM later. In fact, I did not write a blog, snails will list and sort out the relevant knowledge points, which also makes snails benefit a lot, but also hope that snail's blog can help gardeners, this is the so-called "donation of roses, hand residual fragrance" bar.

Keywords: C# Lambda SQL

Added by gnathan87 on Sun, 15 Sep 2019 18:43:19 +0300