表達式目錄樹——自定義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之路漫長,不過我會堅持下去的。:)


免責聲明!

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



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