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()會消耗性能,這個我們后面再想辦法緩存它。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM