這個ExpressionHelper類,是整個SqlHelper中,最核心的一個類,主要功能就是將Lambda表達式轉換為Sql語句。同時這個轉換過程比較復雜,所以下面詳細講解一下思路和這個類的作用。
0x01基本
在Sql語句中,主要由表名,字段,條件語句等元素組成,而這些元素中,表名,字段可以由實體表示出來,條件語句可以由Lambda表達式表現出來。
在Sql語句中,條件語句可以說是Select中的核心,其中很多功能和Lambda中提供的方法很相似,比如:
實體:
1 [TableName("RA_MyBlog_Article")] 2 public class ArticleEntity 3 { 4 [Primary] 5 [Identity] 6 //文章ID 7 public int articleID { get; set; } 8 //分類ID 9 public int categoryID { get; set; } 10 //文章標題 11 public string articleTitle { get; set; } 12 //文章版權 13 public string articleCopyright { get; set; } 14 //文章創建時間 15 public DateTime articleDate { get; set; } 16 //文章摘要 17 public string articleAbstract { get; set; } 18 //文章內容 19 public string articleContain { get; set; } 20 //文章所屬User 21 public int userID { get; set; } 22 }
Lambda表達式 | Sql表達式 |
GetList<ArticleEntity>(a=>a.articleID > 1 && a.userID = 1) | Select * From RA_MyBlog_Article Where articleID > 1 And userID = 1 |
GetList<ArticleEntity>(a=>list.Contains(a.articleID)) | Select * From RA_MyBlog_Article Where articleID In [list中的元素] |
Join<UserEntity,ArticleEntity>((a,b)=>a.UserID == b.UserID) | Select * From RA_MyBlog_Article,RA_MyBlog_User Join RA_MyBlog_User On (RA_MyBlog_Article.UserID = RA_MyBlog_User.UserID ) |
從上表可以看出,由Lambda表達式轉換為Sql表達式是完全可能的。
0x02 Lambda表達式
Lambda設計到的內容比較多,大家有興趣可以去百度找一下這方面的介紹,這里只介紹一些涉及到的部分:
以上面的表達式GetList<ArticleEntity>(a=>a.articleID > 1 && a.userID = 1)為例,其中a=>a.articleID > 1 && a.userID = 1這部分是我們需要的部分。在C#中,由Expression對象負責對Lambda表達式的解析和處理。Expression的子類有很多,分布在System.Linq.Expressions命名空間下,這里用到的有
UnaryExpression:一元表達式,比如取反'!'
ConstantExpression:常量表達式,比如1
MemberExpression:成員表達式,一般為變量,比如a.articleID
MethodCallExpression:函數表達式,比如Contains()
BinaryExpression:二元表達式,比如a.articleID > 1
對於這個例子:a.articleID > 1 && a.userID = 1,整體是一個與類型的二元表達式,左元素為a.articleID > 1,是一個Larger類型的二元表達式,其中左元素是成員表達式,右元素是常量表達式。右元素為a.userID = 1,是一個相等類型的二元表達式,其中左元素是成員表達式,右元素是常量表達式。
2.用到的枚舉,沒啥可說的,包含了Lambda表達式中常見的元素類型。
1 namespace RA.DataAccess.Enumerations 2 { 3 public enum EnumNodeType 4 { 5 [Description("二元運算符")] 6 BinaryOperator = 1, 7 [Description("一元運算符")] 8 UndryOperator = 2, 9 [Description("常量表達式")] 10 Constant = 3, 11 [Description("成員(變量)")] 12 MemberAccess = 4, 13 [Description("函數")] 14 Call = 5, 15 [Description("未知")] 16 Unknown = -99, 17 [Description("不支持")] 18 NotSupported = -98 19 } 20 }
0x03 功能部分
1.判斷表達式類型
1 /// <summary> 2 /// 判斷表達式類型 3 /// </summary> 4 /// <param name="func">lambda表達式</param> 5 /// <returns></returns> 6 private static EnumNodeType CheckExpressionType(Expression func) 7 { 8 switch (func.NodeType) 9 { 10 case ExpressionType.AndAlso: 11 case ExpressionType.OrElse: 12 case ExpressionType.Equal: 13 case ExpressionType.GreaterThanOrEqual: 14 case ExpressionType.LessThanOrEqual: 15 case ExpressionType.GreaterThan: 16 case ExpressionType.LessThan: 17 case ExpressionType.NotEqual: 18 return EnumNodeType.BinaryOperator; 19 case ExpressionType.Constant: 20 return EnumNodeType.Constant; 21 case ExpressionType.MemberAccess: 22 return EnumNodeType.MemberAccess; 23 case ExpressionType.Call: 24 return EnumNodeType.Call; 25 case ExpressionType.Not: 26 case ExpressionType.Convert: 27 return EnumNodeType.UndryOperator; 28 default: 29 return EnumNodeType.Unknown; 30 } 31 }
2.判斷一元表達式:
1 /// <summary> 2 /// 判斷一元表達式 3 /// </summary> 4 /// <param name="func"></param> 5 /// <returns></returns> 6 private static string VisitUnaryExpression(UnaryExpression func) 7 { 8 var result = ExpressionTypeToString(func.NodeType); 9 var funcType = CheckExpressionType(func.Operand); 10 switch (funcType) 11 { 12 case EnumNodeType.BinaryOperator: 13 return result + VisitBinaryExpression(func.Operand as BinaryExpression); 14 case EnumNodeType.Constant: 15 return result + VisitConstantExpression(func.Operand as ConstantExpression); 16 case EnumNodeType.Call: 17 return result + VisitMethodCallExpression(func.Operand as MethodCallExpression); 18 case EnumNodeType.UndryOperator: 19 return result + VisitUnaryExpression(func.Operand as UnaryExpression); 20 case EnumNodeType.MemberAccess: 21 return result + VisitMemberAccessExpression(func.Operand as MemberExpression); 22 default: 23 throw new NotSupportedException("不支持的操作在一元操作處理中:" + funcType.GetDescription()); 24 } 25 }
3.判斷常量表達式:
1 /// <summary> 2 /// 判斷常量表達式 3 /// </summary> 4 /// <param name="func"></param> 5 /// <returns></returns> 6 private static string VisitConstantExpression(ConstantExpression func) 7 { 8 if (func.Value.ToString() == "") 9 { 10 return "\'\' "; 11 } 12 else if (func.Value.ToString() == "True") 13 { 14 return "1 = 1 "; 15 } 16 else if (func.Value.ToString() == "False") 17 { 18 return "0 = 1 "; 19 } 20 else 21 { 22 return "'" + func.Value.ToString() + "' "; 23 24 } 25 }
4.判斷變量表達式
1 /// <summary> 2 /// 判斷包含變量的表達式 3 /// </summary> 4 /// <param name="func"></param> 5 /// <returns></returns> 6 private static string VisitMemberAccessExpression(MemberExpression func) 7 { 8 try 9 { 10 var tablename = EntityHelper.GetTableName(func.Expression.Type); 11 return tablename + "." + func.Member.Name + " "; 12 }catch 13 { 14 object value; 15 switch (func.Type.Name) 16 { 17 case "Int32": 18 { 19 var getter = Expression.Lambda<Func<int>>(func).Compile(); 20 value = getter(); 21 } 22 break; 23 case "String": 24 { 25 var getter = Expression.Lambda<Func<string>>(func).Compile(); 26 value = "'" + getter() + "'"; 27 } 28 break; 29 case "DateTime": 30 { 31 var getter = Expression.Lambda<Func<DateTime>>(func).Compile(); 32 value = "'" + getter() + "'"; 33 } 34 break; 35 default: 36 { 37 var getter = Expression.Lambda<Func<object>>(func).Compile(); 38 value = getter(); 39 } 40 break; 41 } 42 return value.ToString(); 43 } 44 }
5.判斷函數表達式:為了演示,此處這個表達式只支持Contains()函數,其他的函數可以按需添加。
1 /// <summary> 2 /// 判斷包含函數的表達式 3 /// </summary> 4 /// <param name="func"></param> 5 /// <returns></returns> 6 private static String VisitMethodCallExpression(MethodCallExpression func) 7 { 8 if (func.Method.Name.Contains("Contains")) 9 { 10 //獲得調用者的內容元素 11 var getter = Expression.Lambda<Func<object>>(func.Object).Compile(); 12 var data = getter() as IEnumerable; 13 //獲得字段 14 var caller = func.Arguments[0]; 15 while (caller.NodeType == ExpressionType.Call) 16 { 17 caller = (caller as MethodCallExpression).Object; 18 } 19 var field = VisitMemberAccessExpression(caller as MemberExpression); 20 var list = (from object i in data select "'" + i + "'").ToList(); 21 return field + " IN (" + string.Join(",", list.Cast<string>().ToArray()) + ") "; 22 } 23 else 24 { 25 throw new NotSupportedException("不支持的函數操作:" + func.Method.Name); 26 } 27 }
6.判斷二元表達式:二元表達式一般由其他表達式組成,有時還會有嵌套的情況,所以此處使用遞歸來解析。
1 /// <summary> 2 /// 判斷包含二元運算符的表達式 3 /// </summary> 4 /// <remarks>注意,這個函數使用了遞歸,修改時注意不要修改了代碼順序和邏輯</remarks> 5 /// <param name="func"></param> 6 private static string VisitBinaryExpression(BinaryExpression func) 7 { 8 var result = "("; 9 var leftType = CheckExpressionType(func.Left); 10 switch (leftType) 11 { 12 case EnumNodeType.BinaryOperator: 13 result += VisitBinaryExpression(func.Left as BinaryExpression);break; 14 case EnumNodeType.Constant: 15 result += VisitConstantExpression(func.Left as ConstantExpression);break; 16 case EnumNodeType.MemberAccess: 17 result += VisitMemberAccessExpression(func.Left as MemberExpression);break; 18 case EnumNodeType.UndryOperator: 19 result += VisitUnaryExpression(func.Left as UnaryExpression);break; 20 case EnumNodeType.Call: 21 result += VisitMethodCallExpression(func.Left as MethodCallExpression);break; 22 default: 23 throw new NotSupportedException("不支持的操作在二元操作處理中:" + leftType.GetDescription()); 24 } 25 26 result += ExpressionTypeToString(func.NodeType) + " "; 27 28 var rightType = CheckExpressionType(func.Right); 29 switch (rightType) 30 { 31 case EnumNodeType.BinaryOperator: 32 result += VisitBinaryExpression(func.Right as BinaryExpression); break; 33 case EnumNodeType.Constant: 34 result += VisitConstantExpression(func.Right as ConstantExpression); break; 35 case EnumNodeType.MemberAccess: 36 result += VisitMemberAccessExpression(func.Right as MemberExpression); break; 37 case EnumNodeType.UndryOperator: 38 result += VisitUnaryExpression(func.Right as UnaryExpression); break; 39 case EnumNodeType.Call: 40 result += VisitMethodCallExpression(func.Right as MethodCallExpression); break; 41 default: 42 throw new NotSupportedException("不支持的操作在二元操作處理中:" + rightType.GetDescription()); 43 } 44 45 result += ") "; 46 return result; 47 }
7.將Lambda表達式轉換為Sql語句。整個類的入口點:
1 /// <summary> 2 /// 通過Lambda解析為Sql 3 /// </summary> 4 /// <param name="func"></param> 5 /// <returns></returns> 6 public static string GetSqlByExpression(Expression func) 7 { 8 var funcType = CheckExpressionType(func); 9 switch (funcType) 10 { 11 case EnumNodeType.BinaryOperator: 12 return FormatSqlExpression(VisitBinaryExpression(func as BinaryExpression)); 13 case EnumNodeType.Constant: 14 return FormatSqlExpression(VisitConstantExpression(func as ConstantExpression)); 15 case EnumNodeType.Call: 16 return FormatSqlExpression(VisitMethodCallExpression(func as MethodCallExpression)); 17 case EnumNodeType.UndryOperator: 18 return FormatSqlExpression(VisitUnaryExpression(func as UnaryExpression)); 19 case EnumNodeType.MemberAccess: 20 return FormatSqlExpression(VisitMemberAccessExpression(func as MemberExpression)); 21 default: 22 throw new NotSupportedException("不支持的操作在表達式處理中:" + funcType.GetDescription()); 23 } 24 }