zoukankan      html  css  js  c++  java
  • 表达式目录树——自定义LINQ Provider实现LINQ to LDAP查询(其二)

    引言

    说明

    由于博客园是个技术社区,所以我得显得严谨点,这里留下几点说明,我会在接下来的几篇文章中(如果有的话)重复这个说明。

    其一,这篇(或者系列,如果有的话)文章是为了和大家一起入门(注意不是指导)。所以所编写的代码仅仅是示例的,或者说是处于编写中(完善中)的。

    其二,至于为什么在学习的过程中就着手写这些文章,那是因为我深深觉得作为入门,这些内容还是容易的,但是常常让人却而退步。比如在一周之前,我还问博客园中的另一位博主,请求资料。那个时候我还觉得非常困难,非常苦恼。但是,经过一些摸索,一些文章的指导之后,却轻轻叩开了LINQ的门,一窥其瑰丽了。

    其三,其实网上并不是没有LINQ的教程(指编写Provider)。但是“会”和不会往往隔了一点顿悟。就像“水门事件”一样。所以作为初学者来和大家一起探讨可以让彼此更同步。

    其四,这真的是一个非常有挑战,非常有趣的内容。我接触了之后就忍不住和大家一起分享,邀大家一起参与冒险。

    最后,这里列出所有我参考的,觉得有价值的资源。

    其一,MSDN的博客: http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx

    这系列文章直接和本系列文章相关。07年的帖子,13年才发现,真该面壁思过。

    其二,http://weblogs.asp.net/mehfuzh/archive/2007/10/04/writing-custom-linq-provider.aspx

    待会会在文章中引用到这个博主写的一个非常短小的Provider示例。

    其三,博客园中某个博主的作品http://www.cnblogs.com/Terrylee/category/48778.html

    大神的文章读起来有点累,所以这系列我访问了好几次,愣是没看懂怎么回事,不过里面有张图挺不错。

    接上文

    在上文中,我们从接口着手,为了构造自己的LINQ Provider而努力。我们把要实现两个接口减少为只要实现一个接口。通过对剩下的一个接口中要实现的一组方法提供固定实现,现在我们仅需要实现一组方法就能完成工作了。但是,这正是最具挑战的部分。我们将在这个方法中完成“转换”,“查询”,“转化”三个步骤。其中,如何将LINQ查询通过合适的逻辑转换为一种目标查询语句是最核心的部分。此外,为了进行优化,我们还需要处理缓存等一系列操作。是不是很复杂?我承认,如果一开始就考虑全部事项,对于我们这种初学者而言简直就是灾难。但是不要忘了,我们的目的是尽快的构造自己的Hello World,以让自己充满信心。就像编写第一个App的时候,没有考虑一开始就考虑using(或者include)是什么,System.Console(或者printf)是什么,为什么要用大括号,以及为什么要使用引号(同样的,上篇文章中所采取的一些操作,都没有进行仔细的梳理,姑且就像使用VS设计WinForm的时候生成的designer文件一样,认同它)。Just do it,我们先搞定一种情形,然后再搞定第二种,第三种...

    Just do it

    目标

    在SQL查询中,使用的最频繁的可能就是Where筛选了,LDAP查询也一样。所以我们就从Where开始。Where是扩展方法,从根本上讲,它有两个参数,第一个表示调用者(this),第二个接受一个谓词,姑且认为是一个返回值为bool的委托好了。再简单一点,我们认为这个委托的主题部分是string类型的Contains方法。嗯,其实就是“选取xx部分包含关键字xxx的用户”这样的筛选表达式。使用一个常见的表达式的话,就是形如:context.User.Where(u => u.Name.Contains("sample"))或者from u in context.User where u.Name.Contains("sample")【记为表达式A】的LINQ查询。然后如果是直接使用LDAP查询呢?Filter字符串就应该是(假定我们检索UPN)(&(objectClass=User)(userPrincipalName=*sample*))【记为表达式B,虽然他只是字符串】。也就是说我们要把A转换为B。

    表达式目录树

    http://www.cnblogs.com/Terrylee/archive/2008/08/01/custom-linq-provider-part-1-expression-tree.html

    这篇文章初步说明了表达式目录树是啥。但是和我们现在要做的事情还不够接近。所以我现在定义了一个User类型,然后在VS中写了一个表达式来一窥究竟。

    如图,总体是一个lambda,但是我们只需要关心其body部分,users参数只是为了提供一个引用。Body部分是一个MethodCallExpression(NodeType部分被遮挡了),他包含两个参数,第一个参数是数据源,第二个参数是谓词表达式。观察第二个参数的时候,我们或许有点惊讶,因为不管怎么看,它都是个lambda表达式,而这里的NodeType却是Quote。需要注意的就是:作为参数的lambda表达式会被封装为Quote,实际上是一个一元操作表达式,UnaryExpression。我们访问时把他转换为UnaryExpression,然后调用Operand属性,就可以访问到lambda表达式了。然后继续分析,u=>u.Name.Contains("sample”)这个表达式是lambda,其body是u.Name.Contains("sample"),是一个MethodCallExpression。但是这里有个不同,Contains方法是一个实例方法,而Where方法是一个扩展方法(在静态类型中定义的静态方法),所以上一个表达式的Object(表示调用者)属性为null,而这个并非是Null而是u.Name。u.Name是一个MemberExpression,NodeType为MemberAccess,表示成员访问。重要的是可以从中访问到CustomAttribute。接下来看表达式u.Name.Contains,访问他的第一个参数可以获取到关键字“sample”。

    访问表达式目录树

    如果直接访问表达式目录树,是不是显得有点无从着手呢?在System.Linq.Expression名称空间下已经存在了一个类型,ExpressionVisiter,提供了很多虚方法。其中提供方法Visit,该方法接受一个Expression作为参数,并通过判断这个参数的类型而将参数传递给其他方法。简单地说就是switch case然后“重定向”。我引用的MSDN中的文章给出了这个方法的部分实现(说部分是因为这个类型的实现由于时间推移可能已经发生的变更,但大致是这样)。以下是源码:(昨天太困了...代码贴错了...)

    View Code
    public abstract class ExpressionVisitor {
     
        protected ExpressionVisitor() {
     
        }
     
     
     
        protected virtual Expression Visit(Expression exp) {
     
            if (exp == null)
     
                return exp;
     
            switch (exp.NodeType) {
     
                case ExpressionType.Negate:
     
                case ExpressionType.NegateChecked:
     
                case ExpressionType.Not:
     
                case ExpressionType.Convert:
     
                case ExpressionType.ConvertChecked:
     
                case ExpressionType.ArrayLength:
     
                case ExpressionType.Quote:
     
                case ExpressionType.TypeAs:
     
                    return this.VisitUnary((UnaryExpression)exp);
     
                case ExpressionType.Add:
     
                case ExpressionType.AddChecked:
     
                case ExpressionType.Subtract:
     
                case ExpressionType.SubtractChecked:
     
                case ExpressionType.Multiply:
     
                case ExpressionType.MultiplyChecked:
     
                case ExpressionType.Divide:
     
                case ExpressionType.Modulo:
     
                case ExpressionType.And:
     
                case ExpressionType.AndAlso:
     
                case ExpressionType.Or:
     
                case ExpressionType.OrElse:
    
                case ExpressionType.LessThan:
     
                case ExpressionType.LessThanOrEqual:
     
                case ExpressionType.GreaterThan:
     
                case ExpressionType.GreaterThanOrEqual:
     
                case ExpressionType.Equal:
     
                case ExpressionType.NotEqual:
     
                case ExpressionType.Coalesce:
     
                case ExpressionType.ArrayIndex:
     
                case ExpressionType.RightShift:
     
                case ExpressionType.LeftShift:
     
                case ExpressionType.ExclusiveOr:
     
                    return this.VisitBinary((BinaryExpression)exp);
     
                case ExpressionType.TypeIs:
     
                    return this.VisitTypeIs((TypeBinaryExpression)exp);
     
                case ExpressionType.Conditional:
     
                    return this.VisitConditional((ConditionalExpression)exp);
     
                case ExpressionType.Constant:
     
                    return this.VisitConstant((ConstantExpression)exp);
     
                case ExpressionType.Parameter:
     
                    return this.VisitParameter((ParameterExpression)exp);
     
                case ExpressionType.MemberAccess:
     
                    return this.VisitMemberAccess((MemberExpression)exp);
     
                case ExpressionType.Call:
     
                    return this.VisitMethodCall((MethodCallExpression)exp);
     
                case ExpressionType.Lambda:
     
                    return this.VisitLambda((LambdaExpression)exp);
     
                case ExpressionType.New:
     
                    return this.VisitNew((NewExpression)exp);
     
                case ExpressionType.NewArrayInit:
     
                case ExpressionType.NewArrayBounds:
     
                    return this.VisitNewArray((NewArrayExpression)exp);
     
                case ExpressionType.Invoke:
     
                    return this.VisitInvocation((InvocationExpression)exp);
     
                case ExpressionType.MemberInit:
     
                    return this.VisitMemberInit((MemberInitExpression)exp);
     
                case ExpressionType.ListInit:
     
                    return this.VisitListInit((ListInitExpression)exp);
     
                default:
     
                    throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType));
     
            }
     
        }
     
     
     
        protected virtual MemberBinding VisitBinding(MemberBinding binding) {
     
            switch (binding.BindingType) {
     
                case MemberBindingType.Assignment:
     
                    return this.VisitMemberAssignment((MemberAssignment)binding);
     
                case MemberBindingType.MemberBinding:
     
                    return this.VisitMemberMemberBinding((MemberMemberBinding)binding);
     
                case MemberBindingType.ListBinding:
     
                    return this.VisitMemberListBinding((MemberListBinding)binding);
     
                default:
     
                    throw new Exception(string.Format("Unhandled binding type '{0}'", binding.BindingType));
     
            }
     
        }
     
     
     
        protected virtual ElementInit VisitElementInitializer(ElementInit initializer) {
     
            ReadOnlyCollection<Expression> arguments = this.VisitExpressionList(initializer.Arguments);
     
            if (arguments != initializer.Arguments) {
     
                return Expression.ElementInit(initializer.AddMethod, arguments);
     
            }
     
            return initializer;
     
        }
     
     
     
        protected virtual Expression VisitUnary(UnaryExpression u) {
     
            Expression operand = this.Visit(u.Operand);
     
            if (operand != u.Operand) {
     
                return Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method);
     
            }
     
            return u;
     
        }
     
     
     
        protected virtual Expression VisitBinary(BinaryExpression b) {
     
            Expression left = this.Visit(b.Left);
     
            Expression right = this.Visit(b.Right);
     
            Expression conversion = this.Visit(b.Conversion);
     
            if (left != b.Left || right != b.Right || conversion != b.Conversion) {
     
                if (b.NodeType == ExpressionType.Coalesce && b.Conversion != null)
     
                    return Expression.Coalesce(left, right, conversion as LambdaExpression);
     
                else
     
                    return Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
     
            }
     
            return b;
     
        }
     
     
     
        protected virtual Expression VisitTypeIs(TypeBinaryExpression b) {
     
            Expression expr = this.Visit(b.Expression);
     
            if (expr != b.Expression) {
     
                return Expression.TypeIs(expr, b.TypeOperand);
     
            }
     
            return b;
     
        }
     
     
     
        protected virtual Expression VisitConstant(ConstantExpression c) {
     
            return c;
     
        }
     
     
     
        protected virtual Expression VisitConditional(ConditionalExpression c) {
     
            Expression test = this.Visit(c.Test);
     
            Expression ifTrue = this.Visit(c.IfTrue);
     
            Expression ifFalse = this.Visit(c.IfFalse);
     
            if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse) {
     
                return Expression.Condition(test, ifTrue, ifFalse);
     
            }
     
            return c;
     
        }
     
     
     
        protected virtual Expression VisitParameter(ParameterExpression p) {
     
            return p;
     
        }
     
     
     
        protected virtual Expression VisitMemberAccess(MemberExpression m) {
     
            Expression exp = this.Visit(m.Expression);
     
            if (exp != m.Expression) {
     
                return Expression.MakeMemberAccess(exp, m.Member);
     
            }
     
            return m;
     
        }
     
     
     
        protected virtual Expression VisitMethodCall(MethodCallExpression m) {
     
            Expression obj = this.Visit(m.Object);
     
            IEnumerable<Expression> args = this.VisitExpressionList(m.Arguments);
     
            if (obj != m.Object || args != m.Arguments) {
     
                return Expression.Call(obj, m.Method, args);
     
            }
     
            return m;
     
        }
     
     
     
        protected virtual ReadOnlyCollection<Expression> VisitExpressionList(ReadOnlyCollection<Expression> original) {
     
            List<Expression> list = null;
     
            for (int i = 0, n = original.Count; i < n; i++) {
     
                Expression p = this.Visit(original[i]);
     
                if (list != null) {
     
                    list.Add(p);
     
                }
     
                else if (p != original[i]) {
     
                    list = new List<Expression>(n);
     
                    for (int j = 0; j < i; j++) {
     
                        list.Add(original[j]);
     
                    }
     
                    list.Add(p);
     
                }
     
            }
     
            if (list != null) {
     
                return list.AsReadOnly();
     
            }
     
            return original;
     
        }
     
     
     
        protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment assignment) {
     
            Expression e = this.Visit(assignment.Expression);
     
            if (e != assignment.Expression) {
     
                return Expression.Bind(assignment.Member, e);
     
            }
     
            return assignment;
     
        }
     
     
     
        protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding binding) {
     
            IEnumerable<MemberBinding> bindings = this.VisitBindingList(binding.Bindings);
     
            if (bindings != binding.Bindings) {
     
                return Expression.MemberBind(binding.Member, bindings);
     
            }
     
            return binding;
     
        }
     
     
     
        protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding binding) {
     
            IEnumerable<ElementInit> initializers = this.VisitElementInitializerList(binding.Initializers);
     
            if (initializers != binding.Initializers) {
     
                return Expression.ListBind(binding.Member, initializers);
     
            }
     
            return binding;
     
        }
     
     
     
        protected virtual IEnumerable<MemberBinding> VisitBindingList(ReadOnlyCollection<MemberBinding> original) {
     
            List<MemberBinding> list = null;
     
            for (int i = 0, n = original.Count; i < n; i++) {
     
                MemberBinding b = this.VisitBinding(original[i]);
     
                if (list != null) {
     
                    list.Add(b);
     
                }
     
                else if (b != original[i]) {
     
                    list = new List<MemberBinding>(n);
     
                    for (int j = 0; j < i; j++) {
     
                        list.Add(original[j]);
     
                    }
     
                    list.Add(b);
     
                }
     
            }
     
            if (list != null)
     
                return list;
     
            return original;
     
        }
     
     
     
        protected virtual IEnumerable<ElementInit> VisitElementInitializerList(ReadOnlyCollection<ElementInit> original) {
     
            List<ElementInit> list = null;
     
            for (int i = 0, n = original.Count; i < n; i++) {
     
                ElementInit init = this.VisitElementInitializer(original[i]);
     
                if (list != null) {
     
                    list.Add(init);
     
                }
     
                else if (init != original[i]) {
     
                    list = new List<ElementInit>(n);
     
                    for (int j = 0; j < i; j++) {
     
                        list.Add(original[j]);
     
                    }
     
                    list.Add(init);
     
                }
     
            }
     
            if (list != null)
     
                return list;
     
            return original;
     
        }
     
     
     
        protected virtual Expression VisitLambda(LambdaExpression lambda) {
     
            Expression body = this.Visit(lambda.Body);
     
            if (body != lambda.Body) {
     
                return Expression.Lambda(lambda.Type, body, lambda.Parameters);
     
            }
     
            return lambda;
     
        }
     
     
     
        protected virtual NewExpression VisitNew(NewExpression nex) {
     
            IEnumerable<Expression> args = this.VisitExpressionList(nex.Arguments);
     
            if (args != nex.Arguments) {
     
                if (nex.Members != null)
     
                    return Expression.New(nex.Constructor, args, nex.Members);
     
                else
     
                    return Expression.New(nex.Constructor, args);
     
            }
     
            return nex;
     
        }
     
     
     
        protected virtual Expression VisitMemberInit(MemberInitExpression init) {
     
            NewExpression n = this.VisitNew(init.NewExpression);
     
            IEnumerable<MemberBinding> bindings = this.VisitBindingList(init.Bindings);
     
            if (n != init.NewExpression || bindings != init.Bindings) {
     
                return Expression.MemberInit(n, bindings);
     
            }
     
            return init;
     
        }
     
     
     
        protected virtual Expression VisitListInit(ListInitExpression init) {
     
            NewExpression n = this.VisitNew(init.NewExpression);
     
            IEnumerable<ElementInit> initializers = this.VisitElementInitializerList(init.Initializers);
     
            if (n != init.NewExpression || initializers != init.Initializers) {
     
                return Expression.ListInit(n, initializers);
     
            }
     
            return init;
     
        }
     
     
     
        protected virtual Expression VisitNewArray(NewArrayExpression na) {
     
            IEnumerable<Expression> exprs = this.VisitExpressionList(na.Expressions);
     
            if (exprs != na.Expressions) {
     
                if (na.NodeType == ExpressionType.NewArrayInit) {
     
                    return Expression.NewArrayInit(na.Type.GetElementType(), exprs);
     
                }
     
                else {
     
                    return Expression.NewArrayBounds(na.Type.GetElementType(), exprs);
     
                }
     
            }
     
            return na;
     
        }
     
     
     
        protected virtual Expression VisitInvocation(InvocationExpression iv) {
     
            IEnumerable<Expression> args = this.VisitExpressionList(iv.Arguments);
     
            Expression expr = this.Visit(iv.Expression);
     
            if (args != iv.Arguments || expr != iv.Expression) {
     
                return Expression.Invoke(expr, args);
     
            }
     
            return iv;
     
        }
     
    }

    分析表达式是一件很有难度的事情,我必须承认我没有从大学的《编译原理》这门课中学到任何有意思的东西,并且现在很后悔。好在我们现在的目标不是特别远,可以找一些规律然后就开始编码。首先,对于(&(objectClass=User)(userPrincipalName=*sample*)),&表示同时满足后面的两个条件。外层括号是属于整个表达式的。而只有第二个括号是来自Where的。那么我们就可以约定:在Where开始的时候附加左括号,在Where分析结束(包括其子表达式)之后附加右括号。由于设想的情形简单,所以顺序分析就可以了。由于是针对性非常强的转换,实现起来就比较简单,以下的我的实现。

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    
    namespace Providers
    {
        public class DSTranslator:ExpressionVisitor
        {
            //用于拼接查询字符串
            private StringBuilder sb = new StringBuilder();
    
            protected override Expression VisitMethodCall(MethodCallExpression node)
            {
                /*在LINQ查询中,这往往是入口*/
                if (node.Method.Name == "Where" 
                       && node.Method.DeclaringType == typeof(Queryable))
                {
                    /*保证是Queryable中定义的Where的方法*/
                    var arg1 = node.Arguments[0];
                    var arg2 = node.Arguments[1];
                    
                    //顺序分析
                    Visit(arg1);
                    sb.Append("(");
                    Visit(arg2);
                    sb.Append(")");
                    
                }
                else if (node.Method.Name == "Contains"
                    && node.Method.DeclaringType == typeof(string))
                {
                    var invoker = node.Object;
                    Visit(invoker);
                    //已经确定参数是字符串
                    string value = string.Format("=*{0}*", 
                        (node.Arguments[0] as ConstantExpression).Value);
                    sb.Append(value);
                }
                else if (node.Method.Name == "StartsWith"
                    && node.Method.DeclaringType == typeof(string))
                {
                    var invoker = node.Object;
                    Visit(invoker);
                    string value = string.Format("={0}*",
                        (node.Arguments[0] as ConstantExpression).Value);
                    sb.Append(value);
                }
    
                return node;
            }
    
            protected override Expression VisitConstant(ConstantExpression node)
            {
                var query = node.Value as IQueryable;
                if (query != null)
                {
                    var type = query.ElementType;
                    object categoryAttr = type.GetCustomAttributes(
                                            typeof(CategoryAttribute), false)
                                            .FirstOrDefault();
                    if (categoryAttr != null)
                    {
                        //如果是类型特性
                        sb.AppendFormat("(objectClass={0})",
                                     (categoryAttr as CategoryAttribute).Name);
                    }
                }
                return node;
            }
    
            protected override Expression VisitUnary(UnaryExpression node)
            {
                /*访问一元表达式*/
                var opdNode = GetOperand(node);
                Visit(opdNode);
                return node;
            }
    
            public static Expression GetOperand(Expression node)
            {
                /*如果是Quot表达式就把获取其操作数*/
                while (node.NodeType == ExpressionType.Quote)
                {
                    node = (node as UnaryExpression).Operand;
                }
                return node;
            }
    
            protected override Expression VisitLambda<T>(Expression<T> node)
            {
                /*这里通常是筛选的正文*/
                var body = node.Body;
                Visit(body);
                return node;
            }
    
            protected override Expression VisitMember(MemberExpression node)
            {
                //获取直接应用的Property特性
                var propertyAttr = node.Member.GetCustomAttributes(
                                        typeof(PropertyAttribute), false)
                                        .FirstOrDefault();
                if (propertyAttr != null)
                {
                    sb.Append((propertyAttr as PropertyAttribute).Name);
                }
                return node;
            }
            
    
            public string Translate(Expression expression)
            {
                Visit(expression);
                return string.Format("(&{0})", sb.ToString());
            }
    
            public void Clear()
            {
                sb.Clear();
            }
        }
    }

    我使用了Attribute来表示某个属性代表LDAP中的什么属性,以及某种类型代表LDAP中的哪种类型。并模拟了一个Context。代码如下:

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace Providers
    {
        [Category("User")]
        public class User
        {
            /*模型类型,用来构造查询*/
            [Property("userPrincipalName")]
            public string UserPrincipalName { get; set; }
            [Property("cn")]
            public string Name { get; set; }
        }
    
        [Category("groupOfUniqueNames")]
        public class Group
        {
    
        }
    
        public class Context
        {
            public OLC.LINQ.Queryable<User> Users { get; private set; }
            public OLC.LINQ.Queryable<Group> Groups { get; private set; }
            public Context()
            {
                var provider = new DSProvider();
                Groups = new OLC.LINQ.Queryable<Group>(provider);
                Users = new OLC.LINQ.Queryable<User>(provider);
            }
        }
    
        public class CategoryAttribute : Attribute
        {
            public CategoryAttribute(string categoryName)
            {
                Name = categoryName;
            }
            public string Name { get; private set; }
        }
    
        public class PropertyAttribute : Attribute
        {
            public PropertyAttribute(string propertyName)
            {
                Name = propertyName;
            }
            public string Name { get; private set; }
        }
    }

    如你所见,这些代码都是生造出来的,纯粹为了我们的HelloWorld,没有经过任何改进。最后看一看测试代码以及结果。

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Windows.Forms;
    
    namespace Providers
    {
        class Program
        {
            static void Main(string[] args)
            {
                Context context = new Context();
                var lambda1 = context.Users.Where(u => u.Name.Contains("sample"));
                var lambda2 = context.Users.Where(u => u.UserPrincipalName.StartsWith("sample"));
                var lambda3 = context.Groups;
                
                DSTranslator translater = new DSTranslator();
                string translatedStr = translater.Translate(lambda1.Expression);
                Console.WriteLine(translatedStr);
                translater.Clear();
                translatedStr = translater.Translate(lambda2.Expression);
                Console.WriteLine(translatedStr);
                translater.Clear();
                translatedStr = translater.Translate(lambda3.Expression);
                Console.WriteLine(translatedStr);
    
            }
        }
    }

    稍微扩宽点

    有结果出来就会觉得很开心,作为一个程序员我一直这么觉得。但是尽管是这么一个非常局限,非常确定的表达式转换,我仍然花了很长时间来调试。正如@李永京博主说的,这是一条很长的路;有趣的路不是么。现在我们把问题放宽一点,如果谓词表达式的主体是若干个逻辑运算的结果呢?还有就是我们现在的谓词表达式通常和一个常量相关,比如StartsWith(“”),但如果是UserPrincipal.StartsWith(Name)呢...不知道LDAP服务端支不支持这样的查询,但是就算不支持,我们可以把数据加载了之后进行筛选,而使用的时候却丝毫不需要考虑这个,这正是LINQ的美妙和美丽之处吧。

    但暂时先不考虑这个,先考虑多个逻辑运算联结的事情好了,毕竟很常见。用TreeView写了个WinForm,结果发现多个逻辑表达式做“&&”和“||”的时候根节点的选取很奇怪,如果都是“&&”或者都是“||”则选取最后一个表达式作为右操作数,其余作为左操作数。但是“||”和“&&”混合的时候,规律就不明显了。这里附上测试代码和结果,让大家帮忙分析下。还是第一次考虑“&&”和“||”的优先级以及结合性。但是网上的结果不大一致,有的说“||”和“&&”的优先级一致,也有的说“&&”的优先级高于“||”。需要时间把这个弄清楚。

    以下是代码。

    View Code
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Windows.Forms;
    
    namespace Providers
    {
        public partial class ExpressionTree : Form
        {
            private ExpressionTree()
            {
                InitializeComponent();
            }
    
            public ExpressionTree(Expression expression)
                : this()
            {
                //分析表达式目录树
                analysis(expression);
            }
    
            public ExpressionTree(IEnumerable<Expression> expressions)
                : this()
            {
                foreach (var exp in expressions)
                    analysis(exp);
            }
    
            public void analysis(Expression expression)
            {
                //深度遍历
                while (expression != null)
                {
                    if (expression is LambdaExpression)
                    {
                        var lambdaExpression = expression as LambdaExpression;
                        expression = lambdaExpression.Body;
                    }
                    else if (expression is MethodCallExpression)
                    {
                        //对于一个参数的扩展方法
                        expression = (expression as MethodCallExpression).Arguments.LastOrDefault();
                    }
                    else if (expression.NodeType == ExpressionType.Quote)
                    {
                        expression = (expression as UnaryExpression).Operand;
                    }
                    else if (expression is BinaryExpression)
                    {
                        TreeNode root = new TreeNode(expression.ToString());
                        this.expressionViewer.Nodes.Add(root);
                        analysisBinary(expression, root);
                        break;
                    }
                }
            }
    
            private void analysisBinary(Expression expression, TreeNode node)
            {
                //分析二元运算符
                if (expression is BinaryExpression)
                {
                    /*为了明了起见,这里附加上节点本身的运算符。*/
                    BinaryExpression be = expression as BinaryExpression;
                    if (be.NodeType == ExpressionType.AndAlso || be.NodeType == ExpressionType.OrElse)
                    {
                        string nodeStr = node.Text;
                        node.Text = string.Format("{0}[{1}]", nodeStr, be.NodeType);
                    }
    
                    TreeNode childNodeLeft = new TreeNode(be.Left.ToString());
                    node.Nodes.Add(childNodeLeft);
                    analysisBinary(be.Left, childNodeLeft);
                    TreeNode childNodeRight = new TreeNode(be.Right.ToString());
                    node.Nodes.Add(childNodeRight);
                    analysisBinary(be.Right, childNodeRight);
                }
                //不处理非二元运算
            }
        }
    }
    View Code
    static void showWindow()
            {
                Expression<Func<IEnumerable<User>, IEnumerable<User>>> expression1 = users =>
                    users.Where(u => (u.Name.Contains("test")
                        || u.Name.Length < 10)
                        && u.UserPrincipalName.StartsWith("H")
                        || u.Name != ""
                        && u.UserPrincipalName.Length > 10);
    
                Expression<Func<IEnumerable<User>, IEnumerable<User>>> expression2 = users =>
                   users.Where(u => (u.Name.Contains("test")
                       || u.Name.Length < 10)
                       || u.Name == "1"
                       || u.Name == "2"
                       || u.Name != "3"
                       || u.Name != "4");
    
                Expression<Func<IEnumerable<User>, IEnumerable<User>>> expression3 = users =>
                    users.Where(u => (u.Name.Contains("test")
                       || u.Name.Length < 10)
                       && (u.UserPrincipalName.StartsWith("H")
                       || u.Name != ""
                       && u.UserPrincipalName.Length > 10)
                       && u.Name == "1");
    
    
                ExpressionTree tree = new ExpressionTree(new Expression[] { expression1, expression2, expression3 });
                Application.Run(tree);
            }

    以下是结果。

    [13/1/28补充逻辑运算(&&,||)优先级]

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    
    namespace Sample
    {
        class Program
        {
            static void Main(string[] args)
            {
                bool value1 = true && false || true && false || false;
                /*如果&&的优先级比||高或者相反,则右结合和左结合都无所谓*/
                bool value2 = true && false || true && false || false && true || true;
                Console.WriteLine(value1);
                Console.WriteLine(value2);
                Console.ReadKey();
                /*结果是
                 * false
                  true*/
            }
        }
    }

    由上面的例子可以看出,&&的优先级高于||的优先级。后来又查阅了C#高级编程,P169。这里贴上一个表格来。如此一来,结合性就不那么重要了。

    结语

    看到这个标题就意味着这一篇也结束了。我个人性子比较急,想到什么就像写出来,所以会导致内容质量很差,还希望大家别见怪。其实这篇本来可以作为完结篇的,因为接下来的事情无非是查询,转换。不过我又想到了一些东西,所以想在最后的时候作为补充。LINQ之路漫长,不过我会坚持下去的。:)

  • 相关阅读:
    mysql 锁
    python——三道贪心算法例题
    mysql 常见面试题
    API 管理工具
    如何实现一个简化版的 jQuery
    js设计模式--发布订阅模式
    React应该如何优雅的绑定事件?
    js数据结构-链表
    前端开发中的Error以及异常捕获
    vue-router和webpack懒加载,页面性能优化篇
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/2877965.html
Copyright © 2011-2022 走看看