干貨!表達式樹解析"框架"(2)


最新設計請移步 輕量級表達式樹解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html

 

為了過個好年,我還是趕快把這篇完成了吧

聲明

  本文內容需要有一定基礎的開發人員才可輕松閱讀,如果有難以理解的地方可以跟帖詢問,但我也不一定能回答你,原因參考文章干貨!表達式樹解析"框架"(1)

  本文中的栗子偏向於結構簡單,便於理解,主要將的是思路,而不是具體的解決方案;了解思路之后各位程序猿們可以根據自身需求加以變化,俗話說的好:

  授人以(dai)漁(ma)不如授之以(fang)漁(fa)

  本文主要想表達的就是一種方法和思路,高玩們如果覺得栗子太簡單,請不要做無意義的舉動,謝謝合作... ...

書接上文

  上文中已經說到了,"框架"的基本成員都已經創建完畢了,其實這時候我們還缺少主要角色:解析器實例,但是不要着急,因為這步是最難,也是最麻煩的

  所以我這個框架的主要核心也是在這里,怎樣方便靈活的編寫`表達式樹解析器實例`

  下面的內容我盡量說的易懂一些,因為希望照顧到一些程度比較差的同學,如果有人覺得幼稚,請忍耐

  好了,大家跟我一起做

建立新項目

  建立一個新的項目,引用ExpressionParser項目,然后建立一個實體類

  

編寫演示代碼

  在Main方法中寫一個測試用代碼,當然要引用命名空間 (我在項目ExpressionParser中的命名空間是blqw.Linq)

        static void Main()
        {
            Expression<Func<User, bool>> expr = u => u.Age == 1;//實例化表達式樹
            ParserArgs arg = new ParserArgs();//實例化解析用參數
            Parser.Where(expr.Body, arg);     //調用解析方法,因為所有的Expression<T>都是繼承LamdaExpression,所以解析的時候需要調用.Body獲得真正的表達式樹對象
        }

  直接調試運行

  好吧 看懂的同學可能要說了,不是沒寫解析器的實例嗎,這樣運行不是報錯了嗎

  沒錯,就是讓他報錯

觀察錯誤信息

  果然,不錯所料的報錯了

  仔細觀察錯誤信息,`尚未實現BinaryExpression的解析`,這句話說明了,剛才那個委托中,至少包含一個BinaryExpression,而我們沒有對應BinaryExpression的解析器,所以出現了異常

  現在需要實現一個,繼承ExpressionParser<T>,並將T限定為BinaryExpression

    class BinaryExpressionParser : ExpressionParser<BinaryExpression>
    {
    ... ...
    }

  根據前文中的介紹,解析器的命名如果遵循 xxxExpression + Parser 則會自動被框架識別,並注冊;

  所以我們類的名稱叫 BinaryExpressionParser 

  現在把斷點加在Where方法中

調試分析

  注意觀察傳遞進來的expr參數

  "很巧"的是,BinaryExpression在前文中也有介紹,其主要屬性就是Left Right 和 NodeType

  現在需要進行進一步的解析了

  Left和Right都是表達式樹 Expression對象,所以將他們繼續交給框架處理

  NodeType才是需要立即處理的,由於他是枚舉,所以處理起來相對容易

 

  還有一點,處理的結果保存在哪里呢?

  關於這點,暫時我把它放在ParserArgs中的Builder屬性中,這個屬性是一個StringBuilder

    public class ParserArgs
    {
        public ParserArgs()
        {
            Builder = new StringBuilder();
        }

        public StringBuilder Builder { get; private set; }

        public IList<ParameterExpression> Forms { get; set; }

        public readonly string[] FormsAlias = { "it", "A", "B", "C", "D", "E" };
    }
ParserArgs

  Ps一下:ParserArgs是一個靈活的對象,在這個`框架`中它沒有固定的屬性和字段;大家可以根據自己的需要去修改它

        private static void Sign(ExpressionType type, ParserArgs args)
        {
            switch (type)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    args.Builder.Append(" AND");
                    break;
                case ExpressionType.Equal:
                    args.Builder.Append(" =");
                    break;
                case ExpressionType.GreaterThan:
                    args.Builder.Append(" >");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    args.Builder.Append(" >=");
                    break;
                case ExpressionType.NotEqual:
                    args.Builder.Append(" <>");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    args.Builder.Append(" OR");
                    break;
                case ExpressionType.LessThan:
                    args.Builder.Append(" <");
                    break;
                case ExpressionType.LessThanOrEqual:
                    args.Builder.Append(" <=");
                    break;
                default:
                    throw new NotImplementedException("無法解釋節點類型" + type);
            }
        }
處理ExpressionType

  所以處理ExpressionType 后,結果直接追加到Builder中

  Left和Right繼續交給框架處理,最后的代碼就是:

    class BinaryExpressionParser : ExpressionParser<BinaryExpression>
    {
        public override void Where(BinaryExpression expr, ParserArgs args)
        {
            if (ExistsBracket(expr.Left))
            {
                args.Builder.Append(' ');
                args.Builder.Append('(');
                Parser.Where(expr.Left, args);
                args.Builder.Append(')');
            }
            else
            {
                Parser.Where(expr.Left, args);
            }
            Sign(expr.NodeType,  args);
            if (ExistsBracket(expr.Right))
            {
                args.Builder.Append(' ');
                args.Builder.Append('(');
                Parser.Where(expr.Right, args);
                args.Builder.Append(')');
            }
            else
            {
                Parser.Where(expr.Right, args);
            }
        }

        /// <summary> 判斷是否需要添加括號
        /// </summary>
        private static bool ExistsBracket(Expression expr)
        {
            var s = expr.ToString();
            return s != null && s.Length > 5 && s[0] == '(' && s[1] == '(';
        }

        private static void Sign(ExpressionType type, ParserArgs args)
        {
            switch (type)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    args.Builder.Append(" AND");
                    break;
                case ExpressionType.Equal:
                    args.Builder.Append(" =");
                    break;
                case ExpressionType.GreaterThan:
                    args.Builder.Append(" >");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    args.Builder.Append(" >=");
                    break;
                case ExpressionType.NotEqual:
                    args.Builder.Append(" <>");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    args.Builder.Append(" OR");
                    break;
                case ExpressionType.LessThan:
                    args.Builder.Append(" <");
                    break;
                case ExpressionType.LessThanOrEqual:
                    args.Builder.Append(" <=");
                    break;
                default:
                    throw new NotImplementedException("無法解釋節點類型" + type);
            }
        }
        ... ...
    }

 

 

再次運行調試

  錯誤又出現了

  

  這次是MemberExpression,繼續創建MemberExpressionParser,在Where方法中加斷點

  

  首先自動忽略上面2個屬性

  然后分析一下剩下的幾個屬性,有那些是有用的,可以參考MSDN

  ...  

  根據觀察Expression屬性是需要進一步解析的,依照慣例,交給`框架`  Parser.Where(expr.Expression,args);

  Member對應的類型是MemberInfo,這里需要有一點點反射的知識了,至少你需要知道PropertyInfo和FieldInfo都是繼承自Member的

  So,最終代碼如下:

    class MemberExpressionParser:ExpressionParser<MemberExpression>
    {
        public override void Where(MemberExpression expr, ParserArgs args)
        {
            Parser.Where(expr.Expression, args);
            args.Builder.Append('[');
            args.Builder.Append(expr.Member.Name);
            args.Builder.Append(']');
        }
        ... ...
    }

  繼續運行,異常,添加解析器,斷點,分析,編寫解析器,... ...

    class ParameterExpressionParser:ExpressionParser<ParameterExpression>
    {
        public override void Where(ParameterExpression expr, ParserArgs args)
        {
            args.Builder.Append(' ');
            args.Builder.Append(expr.Name);
            args.Builder.Append('.');
        }
        ... ...
    }

    class ConstantExpressionParser:ExpressionParser<ConstantExpression>
    {
        public override void Where(ConstantExpression expr, ParserArgs args)
        {
            args.Builder.Append(' ');
            var val = expr.Value;
            if (val == null || val == DBNull.Value)
            {
                args.Builder.Append("NULL");
                return;
            }
            if (val is bool)
            {
                args.Builder.Append(val.GetHashCode());
                return;
            }
            var code = (int)Type.GetTypeCode(val.GetType());
            if (code >= 5 && code <= 15)            //如果expr.Value是數字類型
            {
                args.Builder.Append(val);
            }
            else
            {
                args.Builder.Append('\'');
                args.Builder.Append(val);
                args.Builder.Append('\'');
            }
        }
        ... ...
    }

第一次運行通過

  修改一下Main方法,讓他可以輸出結果

        static void Main()
        {
            
            Expression<Func<User, bool>> expr = u => u.Age == 1;//實例化表達式樹
            ParserArgs arg = new ParserArgs();//實例化解析用參數
            Parser.Where(expr.Body, arg);     //調用解析方法,因為所有的Expression<T>都是繼承LamdaExpression,所以解析的時候需要調用.Body獲得真正的表達式樹對象
            Console.WriteLine(arg.Builder.ToString());
        }

  很顯然,已經有一些成果了

讓我來構造一個簡單的ORM

    class ORM
    {
        public DataSet Where<T>(Expression<Func<T, bool>> expr)
        {
            var sql = "SELECT * FROM [" + typeof(T).Name + "] ";
            ParserArgs a = new ParserArgs();
            Parser.Where(expr.Body, a);
            sql += expr.Parameters[0].Name + " WHERE" + a.Builder.ToString();
            Console.WriteLine(sql);
            //using (var adp = new SqlDataAdapter(sql, ConnectionString))
            //{
                DataSet ds = new DataSet();
            //    adp.Fill(ds);
                return ds;
            //}
        }

        public ORM(string connectionString)
        {
            ConnectionString = connectionString;
        }

        public string ConnectionString { get; private set; }
    }

  因為實際上,我的電腦是不存在這樣的數據庫的,所以我注釋了一部分代碼

  再修改一下Main中的調用方式

static void Main()
{
    ORM db = new ORM("server=192.168.0.96;database=tempdb;uid=sa;pwd=123456");
    var ds = db.Where<User>(u => u.Age > 18 && (u.Sex == true || u.Name == "blqw"));
}

  打印結果

SELECT * FROM [User] u WHERE u.[Age] > 18 AND ( u.[Sex] = 1 OR u.[Name] = 'blqw')
請按任意鍵繼續. . .

課后練習1

  至此,解析已經初見成效了,不過這只是一個最簡單的栗子

  當然正因為是最簡單的,所以遇到稍微復雜一點的表達式的時候就無法繼續處理了

  而且還有bug

  不過,如果你的理解和接受能力很強,那么你可以嘗試修改源碼,處理以下幾種情況

db.Where<User>(u => u.Name != null);            //u.Name is not null  而非( u.Name <> null )
db.Where<User>(u => u.Name.StartsWith("bl"));   //u.Name like 'bl%'
int[] arr = { 13, 15, 17, 19, 21 };
db.Where<User>(u => arr.Contains(u.Age));        //u.Age in (13,15,17,19,21)

課后練習2

  如果需要使用參數化傳遞參數,又需要怎樣修改源碼呢?

本章結束

  以上2個練習的答案將在第三章中得到解答

未完待續... ...

文章中出現的源碼下載:ExpressionParser2.rar

前文鏈接:干貨!表達式樹解析"框架"(1)

后文鏈接:干貨!表達式樹解析"框架"(3)


免責聲明!

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



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