干货!表达式树解析"框架"(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 == &&
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)
*/
源码下载
结束语
关于表达式树的解析已经全部讲完了,自己回头看看,如果没有一定功力确实看起来比较费力
虽然我已经将我知道的内容基本写出来了,不过由于表达能力有限的缘故,所以可能还有很多人看不懂吧
关于表达能力的问题,我只能说抱歉了,今后慢慢改进吧
还是那句话,如果看完觉得有不明白的地方可以跟帖提问,我有空都会回答的,如果看完觉得完全看不懂,那我就没办法了...
最后,希望大家过个好年,我自己也要过个好年,年前不发文章了,哈哈
相关链接
干货!表达式树解析"框架"(3)的更多相关文章
- 干货!表达式树解析"框架"(1)
最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 关于我和表达式树 其实我也没有深入了解表达式树一些内在实现的原理 ...
- 干货!表达式树解析"框架"(2)
最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定 ...
- 轻量级表达式树解析框架Faller
有话说 之前我写了3篇关于表达式树解析的文章 干货!表达式树解析"框架"(1) 干货!表达式树解析"框架"(2) 干货!表达式树解析"框架" ...
- 表达式树解析"框架"
干货!表达式树解析"框架"(2) 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回 ...
- Lambda表达式树解析(下)
概述 前面章节,总结了Lambda树的构建,那么怎么解析Lambda表达式树那?Lambda表达式是一种委托构造而成,如果能够清晰的解析Lambda表达式树,那么就能够理解Lambda表达式要传递的正 ...
- Lambda表达式树解析(下)包含自定义的provider和查询
概述 前面章节,总结了Lambda树的构建,那么怎么解析Lambda表达式树那?Lambda表达式是一种委托构造而成,如果能够清晰的解析Lambda表达式树,那么就能够理解Lambda表达式要传递的正 ...
- 介绍一个可以将Expression表达式树解析成Transact-SQL的项目Expression2Sql
一.Expression2Sql介绍 Expression2Sql是一个可以将Expression表达式树解析成Transact-SQL的项目.简单易用,几分钟即可上手使用,因为博主在设计Expres ...
- C#3.0新增功能10 表达式树 03 支持表达式树的框架类型
连载目录 [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...
- 借助表达式树感受不一样的CRUD
借助表达式树感受不一样的CRUD Intro 最近有个想法,想不写 sql 语句,做一个类似于 ORM 的东西,自己解析表达式树,生成要执行的 sql 语句,最后再执行 sql 语句,返回相应结果. ...
随机推荐
- RANSAC算法笔记
最近在做平面拟合,待处理的数据中有部分噪点需要去除,很多论文中提到可以使用Ransac方法来去除噪点. 之前在做图像配准时,用到了Ransac算法,但是没有去仔细研究,现在好好研究一番. 参考: ht ...
- EditText取消自动调用键盘事件(方法之一)
直接上代码,这只是其中一种方法: 重点在于是在该EditText的父空间中设置 <LinearLayout android:layout_width="match_parent&quo ...
- 【vuejs小项目——vuejs2.0版本】单页面搭建
http://router.vuejs.org/zh-cn/essentials/nested-routes.html 使用嵌套路由开发,这里会出错主要把Vue.use(VueRouter);要进行引 ...
- python学习 2数学公式
递归 def fact(n): if n <= 1: return 1 else: return n * fact(n - 1) 斐波那契数列: 第0项是0,第1项是1,从第2项开始,每一项都等 ...
- 【转】Oracle 执行计划(Explain Plan) 说明
转自:http://blog.chinaunix.net/uid-21187846-id-3022916.html 如果要分析某条SQL的性能问题,通常我们要先看SQL的执行计划,看看SQ ...
- Android SDK Manager 更新代理配置
转自:http://www.cnblogs.com/tao560532/p/4483067.html 出现问题: 消除SDK更新时,有可能会出现这样的错误:Download interrupted: ...
- AppDomain 详解(转)
AppDomain是CLR的运行单元,它可以加载Assembly.创建对象以及执行程序. AppDomain是CLR实现代码隔离的基本机制. 每一个AppDomain可以单独运行.停止:每个AppDo ...
- SUBLIME 添加PHP控制台
原文地址:http://www.libenfu.com/sublime-%E6%B7%BB%E5%8A%A0php%E6%8E%A7%E5%88%B6%E5%8F%B0/ 点击工具 > 编译系统 ...
- Linux下Nano命令使用指南
1.什么时候用nano? 一般网络很卡,ssh时一用vim/vi 就死窗口,或者死机的情况 2.如何使用? 打开或新建文件 #nano 文件名 禁用自动换行 #nano -w /etc/fs ...
- errno
关于errno有以下需要注意: 1 A common mistake is to do if (somecall() == -1) { printf("som ...