最新设计请移步 轻量级表达式树解析框架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 == &&
args.Builder[length - ] == ' ' &&
args.Builder[length - ] == 'N' &&
args.Builder[length - ] == 'U' &&
args.Builder[length - ] == 'L' &&
args.Builder[length - ] == '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 > && s[] == '(' && s[] == '(';
} 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[], 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[], 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[], 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 = { , , , ,  };
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 = { , , , ,  };
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 >= && code <= ) //如果expr.Value是数字类型
{
args.Builder.Append(val);
}
else
{
args.Builder.Append('\'');
args.Builder.Append(val);
args.Builder.Append('\'');
}
}
}
... ...
}

结果

int[] arr = { , , , ,  };
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 >= && code <= ) //如果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 >= && code <= ) //如果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[].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 > && (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 = { , , , , };
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)

干货!表达式树解析"框架"(3)的更多相关文章

  1. 干货!表达式树解析"框架"(1)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 关于我和表达式树 其实我也没有深入了解表达式树一些内在实现的原理 ...

  2. 干货!表达式树解析"框架"(2)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定 ...

  3. 轻量级表达式树解析框架Faller

    有话说 之前我写了3篇关于表达式树解析的文章 干货!表达式树解析"框架"(1) 干货!表达式树解析"框架"(2) 干货!表达式树解析"框架" ...

  4. 表达式树解析"框架"

    干货!表达式树解析"框架"(2)   为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回 ...

  5. Lambda表达式树解析(下)

    概述 前面章节,总结了Lambda树的构建,那么怎么解析Lambda表达式树那?Lambda表达式是一种委托构造而成,如果能够清晰的解析Lambda表达式树,那么就能够理解Lambda表达式要传递的正 ...

  6. Lambda表达式树解析(下)包含自定义的provider和查询

    概述 前面章节,总结了Lambda树的构建,那么怎么解析Lambda表达式树那?Lambda表达式是一种委托构造而成,如果能够清晰的解析Lambda表达式树,那么就能够理解Lambda表达式要传递的正 ...

  7. 介绍一个可以将Expression表达式树解析成Transact-SQL的项目Expression2Sql

    一.Expression2Sql介绍 Expression2Sql是一个可以将Expression表达式树解析成Transact-SQL的项目.简单易用,几分钟即可上手使用,因为博主在设计Expres ...

  8. C#3.0新增功能10 表达式树 03 支持表达式树的框架类型

    连载目录    [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...

  9. 借助表达式树感受不一样的CRUD

    借助表达式树感受不一样的CRUD Intro 最近有个想法,想不写 sql 语句,做一个类似于 ORM 的东西,自己解析表达式树,生成要执行的 sql 语句,最后再执行 sql 语句,返回相应结果. ...

随机推荐

  1. 为你的Android App实现自签名的 SSL 证书(转)

    介绍 网络安全已成为大家最关心的问题. 如果你利用服务器存储客户资料, 那你应该考虑使用 SSL 加密客户跟服务器之间的通讯. 随着这几年手机应用迅速崛起. 黑客也开始向手机应用转移, 原因有下列3点 ...

  2. string与int互换

    1:将string转化为int 1.) int i = Integer.parseInt(String s); 2.) int i = Integer.valueOf(my_str).intValue ...

  3. 工作中常用的awk命令

    http://man.linuxde.net/awk 1.为一列id加逗号 awk 'BEGIN {ORS=","}{print $1}' test.txt 2.统计一列id中重复 ...

  4. fragment嵌套问题

    fragment嵌套里面不能用再用getActivity().getFragmentManager()要用getChildFragmentManager()

  5. Problem of saving images in WPF (RenderTargetBitmap)zz

      To save a visual to an image file need to use RenderTargetBitmap, detail is reference to Save and ...

  6. C#开发笔记

    Dictionary 检查后获取值:Dictionary.TryGetValue() KeyValuePair<T, K> 的非泛型形式:DictionaryEntry List 由ILi ...

  7. [BZOJ1106][POI2007] Tet 立方体大作战

    Description 一个叫做立方体大作战的游戏风靡整个Byteotia.这个游戏的规则是相当复杂的,所以我们只介绍他的简单规则:给定玩家一个有2n个元素的栈,元素一个叠一个地放置.这些元素拥有n个 ...

  8. 区间DP LightOJ 1422 Halloween Costumes

    http://lightoj.com/volume_showproblem.php?problem=1422 做的第一道区间DP的题目,试水. 参考解题报告: http://www.cnblogs.c ...

  9. JavaScript 中string方法

    注意:JavaScript的字符串是不可变的(immutable),String类定义的方法不能改变原来字符串内容,例如String.toUpperCase()这样的方法,返回的是全新的字符串,而不是 ...

  10. 39个让你受益的HTML5教程

    1. 五分钟入门HTML5 (Learn HTML5 in 5 Minutes!) By Jennifer Marsman 毫无疑问,HTML5是一个热门话题.如果你需要一个迅速了解HTML基础的速成 ...