最新设计请移步 轻量级表达式树解析框架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. MySQL开发规范

    字段设计 (1)建议使用UNSIGNED存储非负数值. (2)建议使用INT UNSIGNED存储IPV4. (4)INT类型固定占用4字节存储,例如INT(4)仅代表显示字符宽度为4位,不代表存储长 ...

  2. ibatis order by 防止sql注入

    (1) 排序控制 select TABLE_NAME, TABLESPACE_NAME from user_tables order by TABLE_NAME $ordertype$ Where t ...

  3. 2016多校联合训练4 F - Substring 后缀数组

    Description ?? is practicing his program skill, and now he is given a string, he has to calculate th ...

  4. dedecms搜索框写法

    <div class="bg_search"> <form id="forms" name="formsearch" ac ...

  5. Shell运算符:Shell算数运算符、关系运算符、布尔运算符、字符串运算符等

    摘自:http://c.biancheng.net/cpp/view/2736.html

  6. Django 1.7 throws django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet

    在程序中要添加django.setup() 整个程序如下所示 import os import django def populate(): python_cat = add_cat('Python' ...

  7. Day 1:学习Windows Phone 使用 SQLite

    private void move(string fn) { StreamResourceInfo sr = Application.GetResourceStream(new Uri(fn, Uri ...

  8. StringBuilder(字符串拼接类)

    StringBuilder是在using System.Text命名空间下的一个成员. 在做字符串拼接的时候,因为字符串是引用类型,新的字符串是会再内存中创建的,所以用+号拼接字符串是比较耗效率的. ...

  9. HDU Cow Sorting (树状数组)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2838 Cow Sorting Problem Description Sherlock's N (1  ...

  10. WINFORM 输出txt文件

    SaveFileDialog saveFile1 = new SaveFileDialog(); saveFile1.Filter = "文本文件(.txt)|*.txt"; sa ...