Expression经验之二:LambdaExpression变换

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Expression经验之二:LambdaExpression变换相关的知识,希望对你有一定的参考价值。

想了好久标题的名称,姑且就叫做LambdaExpression变换吧。那到底要变换什么呢?说的简单些就是要把表达式

Expression<Func<Student, bool>> filter=s=>s.Name.Contains("a") && s.Age>=20;

这样的表达试转换成

Expression<Func<DataRow, bool>> filter = r=>((string)r["Name"]).Contains("a") && ((int)r["Age"])>=20;

也许你会问,干嘛要这样做呢?举个例子,

说DAL里有一个类StudentProvider用于对student进行数据库的增删改查的操作。我们就拿查询来说,查询可以有很多的条件。以往可能会有类似的方法:

public IEnumerable<Student> GetStudentsByName(string name);
public Student GetStudentById(int id);

但是别忘了今天的世界有了Expression,我们应该向这些落后的(别打我,窃以为的)方法说再见了。高颜值的接口当然要写成这样了:

public IEnumerable<Student> GetStudents(Expression<Func<Student, bool>> filter);

于是我们来看看这个方法的实现,

     public IEnumerable<Student> GetStudents(Expression<Func<Student, bool>> filter)
        {
            using (var connection=new SqlConnection("some connection string"))
            {
                var selectSql = "SELECT * FROM Student";
                using (var adapter = new SqlDataAdapter(selectSql, connection))
                {
                    var ds = new DataSet();
                    adapter.Fill(ds, "table");
                    return from raw in ds.Tables["table"].AsEnumerable() select new Student(raw);
                }
            }
        } 

实现用到了Linq to DataSet, 其实我们真正想做的是

return from raw in ds.Tables["table"].AsEnumerable().Where(filter) select new Student(raw)

但是问题是Where只接受

Func<DataRow, bool> predicate

技术分享

到这里,终于明白了为什么要做LambdaExpression变换了吧。

前一篇中我们看到了ExpressionVisitor的强大,这里我们还要用他来解决问题。我们引入一个ConvertMemberToColumnVisitor:

     public class ConvertMemberToColumnVisitor : ExpressionVisitor
        {
            private readonly Expression _columnOwnerExpression;
            private readonly string _memberOwnerName;

            public ConvertMemberToColumnVisitor(Expression columnOwnerExpression, string memberOwnerName)
            {
                _columnOwnerExpression = columnOwnerExpression;
                _memberOwnerName = memberOwnerName;
            }

            protected override Expression VisitMember(MemberExpression node)
            {
                var parameterExpression = node.Expression as ParameterExpression;
                if (parameterExpression != null && parameterExpression.Name == _memberOwnerName)
                {
                    return Expression.Convert(Expression.Call(_columnOwnerExpression, typeof(DataRow).GetMethod("get_Item", new []{typeof(string)}), Expression.Constant(node.Member.Name)),
                        ((PropertyInfo)node.Member).PropertyType);
                }

                return base.VisitMember(node);
            }
        }

很简单,很定一个我们要替代成的表达式,当然我们还是用parameter name来匹配所以要给定一个参数名。

有了这个Visitor后,一切问题都简单了:

     public IEnumerable<Student> GetStudents(Expression<Func<Student, bool>> filter)
        {
            using (var connection=new SqlConnection("some connection string"))
            {
                var selectSql = "SELECT * FROM Student";
                using (var adapter = new SqlDataAdapter(selectSql, connection))
                {
                    var ds = new DataSet();
                    adapter.Fill(ds, "table");

                    var p1 = Expression.Parameter(typeof(DataRow), "r");
                    var converter = new ConvertMemberToColumnVisitor(p1, filter.Parameters[0].Name);
                    var newExp = converter.Visit(filter);
                    var lambda = Expression.Lambda<Func<DataRow, bool>>(((LambdaExpression)newExp).Body, p1);
                    var predicate = lambda.Compile();
                    return from raw in ds.Tables["table"].AsEnumerable().Where(predicate) select new Student(raw);
                }
            }
        } 

当然lambda.Compile()会消耗性能,这个我们后面再想办法缓存它。

 

以上是关于Expression经验之二:LambdaExpression变换的主要内容,如果未能解决你的问题,请参考以下文章

Expression经验之前言

文本三剑客之二 ------sed

Expression Blend实例中文教程 - 动画基础快速入门Animation

pinpoint插件开发之二:从零开始新建一个插件

STM32L476应用开发之二:模拟量数据采集

C++内存管理机制学习笔记:基本构件之new/delete expression