干货!表达式树解析框架(3)
最新設計請移步 輕量級表達式樹解析框架Faller?http://www.cnblogs.com/blqw/p/Faller.html
?
?
這應該是年前最后一篇了,接下來的時間就要陪陪老婆孩子了
關于表達式樹解析也是最后一篇了,該說到的中心思想都已經說到了,理解接受能力比較好的童鞋應該已經可以舉一反三了
研究表達式樹的過程讓我感覺微軟的設計真的是非常的巧妙,也為今后我的開發之路增添了新的思路
好了 廢話不多說了 這篇主要是為了解決上篇中的提問的
聲明
解決問題的辦法有很多,我只是根據我的個人習慣和風格介紹我的解決方案,并不一定就是最好的,僅僅只是提供一種思路,大家可以根據自己或項目的實際情況酌情對待
關于問題請參考干貨!表達式樹解析"框架"(2)結尾
問題一
db.Where<User>(u => u.Name != null); //u.Name is not null 而非( u.Name <> null )分析
這個問題主要是在Sql中`二元表達式`有一個非常特別的情況,如果和null進行比較,那么應該用is或is not 而不是=或者<>(!=)
so~我的做法是在解析二元表達式的類中處理,如第二個參數是null,且符號是Equals或NotEqual,則使用is/is not
怎么判斷第二個參數是null?
這里我打算直接判斷ParserArgs.Builder中最后5個字符,如果是 " NULL" 就算是NULL了
但是這里有個問題,就是原來的操作是先加入符號,再加入Right的,所以這里也要改,改為先放入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);}var index = args.Builder.Length;if (ExistsBracket(expr.Right)){args.Builder.Append(' ');args.Builder.Append('(');Parser.Where(expr.Right, args);args.Builder.Append(')');}else{Parser.Where(expr.Right, args);}var length = args.Builder.Length;if (length - index == 5 &&args.Builder[length - 5] == ' ' &&args.Builder[length - 4] == 'N' &&args.Builder[length - 3] == 'U' &&args.Builder[length - 2] == 'L' &&args.Builder[length - 1] == 'L'){Sign(expr.NodeType, index, args, true);}else{Sign(expr.NodeType, index, 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, int index, ParserArgs args, bool useis = false){switch (type){case ExpressionType.And:case ExpressionType.AndAlso:args.Builder.Insert(index, " AND");break;case ExpressionType.Equal:if (useis){args.Builder.Insert(index, " IS");}else{args.Builder.Insert(index, " =");}break;case ExpressionType.GreaterThan:args.Builder.Insert(index, " >");break;case ExpressionType.GreaterThanOrEqual:args.Builder.Insert(index, " >=");break;case ExpressionType.NotEqual:if (useis){args.Builder.Insert(index, " IS NOT");}else{args.Builder.Insert(index, " <>");}break;case ExpressionType.Or:case ExpressionType.OrElse:args.Builder.Insert(index, " OR");break;case ExpressionType.LessThan:args.Builder.Insert(index, " <");break;case ExpressionType.LessThanOrEqual:args.Builder.Insert(index, " <=");break;default:throw new NotImplementedException("無法解釋節點類型" + type);}}... ...}結果
db.Where<User>(u => u.Name != null); //打印 SELECT * FROM [User] u WHERE u.[Name] IS NOT NULL問題二
db.Where<User>(u => u.Name.StartsWith("bl")); //u.Name like 'bl%'分析
這2個表達式只要運行一下就可以知道,他們是無法被解析的,原因就是:
尚未實現MethodCallExpression的解析
因為2個都屬性MethodCall表達式
所以只需要實現MethodCallExpressionParser即可
MethodCallExpression 方法調用表達式
Method 表示調用的方法
Arguments 表示方法中用到的參數
Object 表示調用方法的實例對象
每種方法對應的解析都是不同的,所以我為每個方法都實現一個單獨的解析函數
比如String類中的3個操作,分別對應3種Like的情況
public static void String_StartsWith(MethodCallExpression expr, ParserArgs args){ }public static void String_Contains(MethodCallExpression expr, ParserArgs args){}public static void String_EndsWith(MethodCallExpression expr, ParserArgs args){}? 然后將他們加入到一個鍵值對中(因為方法是有重載的,所以會有一樣名字的方法,但是解析方式是相同的)
static Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> _Methods = MethodDitcInit();private static Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> MethodDitcInit(){Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> dict = new Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>>();var type = typeof(string);foreach (var met in type.GetMethods()){switch (met.Name){case "StartsWith":dict.Add(met, String_StartsWith);break;case "Contains":dict.Add(met, String_Contains);break;case "EndsWith":dict.Add(met, String_EndsWith);break;default:break;}}return dict;}調用where
public override void Where(MethodCallExpression expr, ParserArgs args){Action<MethodCallExpression, ParserArgs> act;if (_Methods.TryGetValue(expr.Method,out act)){act(expr, args);return;}throw new NotImplementedException("無法解釋方法" + expr.Method);}現在分別完成3個String_的函數就可以了
public static void String_StartsWith(MethodCallExpression expr, ParserArgs args){Parser.Where(expr.Object, args);args.Builder.Append(" LIKE");Parser.Where(expr.Arguments[0], args);args.Builder.Append(" + '%'");}public static void String_Contains(MethodCallExpression expr, ParserArgs args){Parser.Where(expr.Object, args);args.Builder.Append(" LIKE '%' +");Parser.Where(expr.Arguments[0], args);args.Builder.Append(" + '%'");}public static void String_EndsWith(MethodCallExpression expr, ParserArgs args){Parser.Where(expr.Object, args);args.Builder.Append(" LIKE '%' +");Parser.Where(expr.Arguments[0], args);}?結果
db.Where<User>(u => u.Name.StartsWith("bl")); db.Where<User>(u => u.Name.Contains("bl")); db.Where<User>(u => u.Name.EndsWith("bl")); /*打印 SELECT * FROM [User] u WHERE u.[Name] LIKE 'bl' + '%' SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + 'bl' + '%' SELECT * FROM [User] u WHERE 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)分析
這個問題和剛才那個問題有很多相似之處,所以首先需要在MethodCallExpressionParser類中實現一個對應Enumerable.Contains的解析函數
但是這個方法有一個比較特殊的地方就是 他是泛型方法, 所以在從鍵值對中獲取處理函數的時候,需要把他轉為`泛型方法定義`(MethodInfo.GetGenericMethodDefinition)的才可以
public override void Where(MethodCallExpression expr, ParserArgs args){Action<MethodCallExpression, ParserArgs> act;var key = expr.Method;if (key.IsGenericMethod){key = key.GetGenericMethodDefinition();} if (_Methods.TryGetValue(key, out act)){act(expr, args);return;}throw new NotImplementedException("無法解釋方法" + expr.Method);}對應的處理函數
public static void Enumerable_Contains(MethodCallExpression expr, ParserArgs args){Parser.Where(expr.Arguments[1], args);args.Builder.Append(" IN");Parser.Where(expr.Arguments[0], args);}看上去似乎已經完成了,但是結果是....
int[] arr = { 13, 15, 17, 19, 21 }; db.Where<User>(u => arr.Contains(u.Age)); //打印 //SELECT * FROM [User] u WHERE u.[Age] IN 'Demo.Program+<>c__DisplayClass0'[arr]問題
問題在于arr和u.Age一樣都是MemberExpression,而MemberExpression的解析之前是這樣寫的
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(']');}... ...}顯然MemberExpression有兩種,一種是`虛擬的`,是不存在值的,比如u.Age,
還有一種是真實的比如上面例子中的arr,他是有真實值的
所以這個地方要改一改
代碼
這塊地方比較難,需要理解一下
class MemberExpressionParser : ExpressionParser<MemberExpression>{public override void Where(MemberExpression expr, ParserArgs args){if (expr.Expression is ParameterExpression){Parser.Where(expr.Expression, args);args.Builder.Append('[');args.Builder.Append(expr.Member.Name);args.Builder.Append(']');}else{object val = GetValue(expr);args.Builder.Append(' ');IEnumerator array = val as IEnumerator;if (array != null){AppendArray(args, array);}else if(val is IEnumerable){AppendArray(args, ((IEnumerable)val).GetEnumerator());}else{AppendObject(args, val);} }}/// <summary> 獲取成員表達式中的實際值/// </summary>private static object GetValue(MemberExpression expr){object val;var field = expr.Member as FieldInfo;if (field != null){val = field.GetValue(((ConstantExpression)expr.Expression).Value);}else{val = ((PropertyInfo)expr.Member).GetValue(((ConstantExpression)expr.Expression).Value, null);}return val;}/// <summary> 追加可遍歷對象(數組或集合或簡單迭代器)/// </summary>private static void AppendArray(ParserArgs args, IEnumerator array){if (array.MoveNext()){args.Builder.Append('(');AppendObject(args, array.Current);while (array.MoveNext()){args.Builder.Append(',');AppendObject(args, array.Current);}args.Builder.Append(')');}else{args.Builder.Append("NULL");}}/// <summary> 追加一般對象/// </summary>public static void AppendObject(ParserArgs args, object val){if (val == null || val == DBNull.Value){args.Builder.Append("NULL");}else if (val is bool){args.Builder.Append(val.GetHashCode());}else{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('\'');}}}... ...}?結果
int[] arr = { 13, 15, 17, 19, 21 }; db.Where<User>(u => arr.Contains(u.Age)); //打印 //SELECT * FROM [User] u WHERE u.[Age] IN (13,15,17,19,21)問題四
如果需要使用參數化傳遞參數,又需要怎樣修改源碼呢?
分析
其實這個問題是最簡單的一個問題,如果已經理解這個`框架`的工作原理可以輕松解決這個問題
代碼
1.修改ParserArgs,使其中包含一個SqlParamete的集合,并且為了方便操作,將AppendObject的方法也移入ParserArgs,變為AddParameter
使用參數化傳遞還有一個好處 可以不用判斷參數類型,來確定是否添加 單引號(')
public class ParserArgs{public ParserArgs(){Builder = new StringBuilder();SqlParameters = new List<SqlParameter>();}public List<SqlParameter> SqlParameters { get; set; }public StringBuilder Builder { get; private set; }/// <summary> 追加參數/// </summary>public void AddParameter(object obj){if (obj == null || obj == DBNull.Value){Builder.Append("NULL");}else{string name = "p" + SqlParameters.Count;SqlParameters.Add(new SqlParameter(name, obj));Builder.Append('@');Builder.Append(name);}}}2.修改本來應該輸出值的位置,改為輸出參數名,并將參數加入集合
ConstantExpressionParser.Where
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('\'');}} 原方法改為
public override void Where(ConstantExpression expr, ParserArgs args){args.Builder.Append(' ');args.AddParameter(expr.Value);}MemberExpressionParser.AppendObject
/// <summary> 追加一般對象/// </summary>public static void AppendObject(ParserArgs args, object val){if (val == null || val == DBNull.Value){args.Builder.Append("NULL");}else if (val is bool){args.Builder.Append(val.GetHashCode());}else{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('\'');}}} 原方法改為, ? ?當然你也可以考慮刪除這個方法
/// <summary> 追加一般對象/// </summary>public static void AppendObject(ParserArgs args, object val){args.AddParameter(val);}最后,調用方式進行一些修改
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)){adp.SelectCommand.Parameters.AddRange(a.SqlParameters.ToArray());//添加這一句DataSet ds = new DataSet();adp.Fill(ds);return ds;}}結果
ORM db = new ORM("server=192.168.0.96;database=tempdb;uid=sa;pwd=123456"); db.Where<User>(u => u.Age > 18 && (u.Sex == true || u.Name == "blqw")); db.Where<User>(u => u.Name != null); db.Where<User>(u => u.Name.StartsWith("bl")); db.Where<User>(u => u.Name.Contains("bl")); db.Where<User>(u => u.Name.EndsWith("bl")); int[] arr = { 13, 15, 17, 19, 21 }; db.Where<User>(u => arr.Contains(u.Age)); /*打印 SELECT * FROM [User] u WHERE u.[Age] > @p0 AND ( u.[Sex] = @p1 OR u.[Name] = @p2) SELECT * FROM [User] u WHERE u.[Name] IS NOT NULL SELECT * FROM [User] u WHERE u.[Name] LIKE @p0 + '%' SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + @p0 + '%' SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + @p0 SELECT * FROM [User] u WHERE u.[Age] IN (@p0,@p1,@p2,@p3,@p4) */源碼下載
ExpressionParser3.rar
結束語
關于表達式樹的解析已經全部講完了,自己回頭看看,如果沒有一定功力確實看起來比較費力
雖然我已經將我知道的內容基本寫出來了,不過由于表達能力有限的緣故,所以可能還有很多人看不懂吧
關于表達能力的問題,我只能說抱歉了,今后慢慢改進吧
還是那句話,如果看完覺得有不明白的地方可以跟帖提問,我有空都會回答的,如果看完覺得完全看不懂,那我就沒辦法了...
最后,希望大家過個好年,我自己也要過個好年,年前不發文章了,哈哈
?相關鏈接
干貨!表達式樹解析"框架"(1)?
干貨!表達式樹解析"框架"(2)?
轉載于:https://www.cnblogs.com/blqw/p/3533045.html
總結
以上是生活随笔為你收集整理的干货!表达式树解析框架(3)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 匿名内部类的使用总结
- 下一篇: ASP.NET MVC 自定义路由中几个