这个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 }