一、定义解析Lambda实体类
!!!注意: 一定要继承 ExpressionVisitor 解析表达式目录树的抽象类,不然咱自己定义的方法不可使用
二、定义解析需要的方法
1、定义一个解析存储sql语句的栈(使用先进后出的逻辑)
/// <summary>
/// Where语句存储栈
/// 使用先进后出原则
/// </summary>
private Stack<string> _stack = new Stack<string>();
2、重写解析二元表达式方法: VisitBinary (例如: X.Id > 8)
/// <summary>
/// 解析二元表达式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression node)
{
if (node == null) //如果解析的节点是空,抛出异常
throw new ArgumentNullException(nameof(node) + ": 为空!");
this._stack.Push(")");//添加where语句最右侧的括号
base.Visit(node.Right);//解析右边
//解析拼接条件 例: And Or > < >= <= = Not
this._stack.Push($" {MyExpressionVisitor.GetYymbol(node.NodeType)}");
base.Visit(node.Left);//解析左边
this._stack.Push("(");//添加where语句最左侧括号
return node;
}
GetYymbol这个方法是我自己定义的,用来把lambda的符号,如 ‘&&‘ 转译成sql语句中对应的符号’And‘
代码如下:
/// <summary>
/// 获取简单条件拼接符号
/// </summary>
/// <param name="expType"></param>
/// <returns></returns>
public static string GetYymbol(ExpressionType expType)
{
switch(expType)
{
case (ExpressionType.AndAlso):
case (ExpressionType.And):
return "AND";
case (ExpressionType.OrElse):
case (ExpressionType.Or):
return "OR";
case (ExpressionType.Not):
return "NOT";
case (ExpressionType.NotEqual):
return "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case (ExpressionType.Equal):
return "=";
default:
throw new Exception("Lanbda存在不识别的符号:" + expType.ToString());
}
}
3、重写解析属性的方法: VisitMember (例如: X.Id)
/// <summary>
/// 重写解析属性 例: x.Id => Id
/// 向where语句存储栈中添加解析成功的属性 例如: Id
/// </summary>
/// <param name="node">属性,例: x.Id</param>
/// <returns></returns>
protected override Expression VisitMember(MemberExpression node)
{
if(node == null) throw new ArgumentNullException(nameof(node)+ ": 为空!");
this._stack.Push(node.Member.Name);
return node;
}
4、重写解析常量的方法: VisitConstant (例如: 数字1 或者 字符串”王”)
/// <summary>
/// 重写解析常量 例如: 1
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");
this._stack.Push($"'{node.Value}'");
return node;
}
5、重写解析系统方法的方法: VisitMethodCall (例如: x.Name.Contains(“abc”) )
/// <summary>
/// 重写解析方法
/// 获取复杂条件拼接字符串
/// </summary>
/// <param name="node">方法,例:x.Name.Contains("abc"),x.Number.IsNullOrEmpty()......</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");
string result = null;
switch (node.Method.Name)
{
case "StartsWith":
result = "({0} LIKE '{1}%')";
break;
case "Contains":
result = "({0} LIKE '%{1}%')";
break;
case "EndsWith":
result = "({0} LIKE '%{1}')";
break;
default:
throw new NotSupportedException("存在不识别方法: " + node.NodeType);
}
//解析属性 例: x.Name
this.Visit(node.Object);
//解析方法中的参数, 例: Contains("abc")中的 abc
this.Visit(node.Arguments[0]);
string right = this._stack.Pop();//在栈中剪切出来一个右侧 abc
string left = this._stack.Pop(); //在栈中剪切出来一个左侧 name
//重新拼接,存储到栈中
this._stack.Push(String.Format(result, left, right));
return node;
}
到这里我们就简单的定义好解析的方法了
三、生成Sql语句字符串
把最上方,栈中存储的数据转成字符串
/// <summary>
/// 获取Lambda转译成的where语句
/// </summary>
/// <returns></returns>
private string StackToString()
{
string result = string.Concat(this._stack.ToArray());//把栈数据转译字符串
this._stack.Clear(); //清空栈
return result;
}
四、封装调用方法
我在这里封装了一个参数为Lambad表达式,返回值为字符串的泛型方法
/// <summary>
/// 获取where字符串语句
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
public static string FindWhere<T>(Expression<Func<T,bool>> expression)
{
MyExpressionVisitor myExpression = new MyExpressionVisitor();
myExpression.Visit(expression);
return myExpression.StackToString();
}
五、方法调用
我在这里简单写了一个调用方法
//调用FindWhereInfo示例
string sql = $@"SELECT * FROM [dbo].[Users] ";
string where = MyExpressionVisitor.FindWhere<Users>(x => x.UserName.Contains("小王") && x.Password == "123");
if (!string.IsNullOrEmpty(where))
{
sql += " WHERE " + where;
}
Console.WriteLine(sql);
如图所示,可以看到我们生成的sql语句跟在数据库中写的一模一样,代表我们写的代码非常成功
下面我将封装的全部代码奉上,供大家使用!!!
using Microsoft.SqlServer.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace Common.Expressions
{
/// <summary>
/// 解析Lambda表达式 => Sql语句的Where条件
/// </summary>
public class MyExpressionVisitor : ExpressionVisitor
{
/// <summary>
/// Where语句存储栈
/// 使用先进后出原则
/// </summary>
private Stack<string> _stack = new Stack<string>();
/// <summary>
/// 获取where字符串语句
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expression"></param>
/// <returns></returns>
public static string FindWhere<T>(Expression<Func<T,bool>> expression)
{
MyExpressionVisitor myExpression = new MyExpressionVisitor();
myExpression.Visit(expression);
return myExpression.StackToString();
}
/// <summary>
/// 获取Lambda转译成的where语句
/// </summary>
/// <returns></returns>
private string StackToString()
{
string result = string.Concat(this._stack.ToArray());//把栈数据转译字符串
this._stack.Clear(); //清空栈
return result;
}
/// <summary>
/// 解析二元表达式
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected override Expression VisitBinary(BinaryExpression node)
{
if (node == null) //如果解析的节点是空,抛出异常
throw new ArgumentNullException(nameof(node) + ": 为空!");
this._stack.Push(")");//添加where语句最右侧的括号
base.Visit(node.Right);//解析右边
//解析拼接条件 例: And Or > < >= <= = Not
this._stack.Push($" {MyExpressionVisitor.GetYymbol(node.NodeType)}");
base.Visit(node.Left);//解析左边
this._stack.Push("(");//添加where语句最左侧括号
return node;
}
/// <summary>
/// 获取简单条件拼接符号
/// </summary>
/// <param name="expType"></param>
/// <returns></returns>
public static string GetYymbol(ExpressionType expType)
{
switch(expType)
{
case (ExpressionType.AndAlso):
case (ExpressionType.And):
return "AND";
case (ExpressionType.OrElse):
case (ExpressionType.Or):
return "OR";
case (ExpressionType.Not):
return "NOT";
case (ExpressionType.NotEqual):
return "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case (ExpressionType.Equal):
return "=";
default:
throw new Exception("Lanbda存在不识别的符号:" + expType.ToString());
}
}
/// <summary>
/// 重写解析属性 例: x.Id => Id
/// 向where语句存储栈中添加解析成功的属性 例如: Id
/// </summary>
/// <param name="node">属性,例: x.Id</param>
/// <returns></returns>
protected override Expression VisitMember(MemberExpression node)
{
if(node == null) throw new ArgumentNullException(nameof(node)+ ": 为空!");
this._stack.Push(node.Member.Name);
return node;
}
/// <summary>
/// 重写解析常量 例如: 1
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");
this._stack.Push($"'{node.Value}'");
return node;
}
/// <summary>
/// 重写解析方法
/// 获取复杂条件拼接字符串
/// </summary>
/// <param name="node">方法,例:x.Name.Contains("abc"),x.Number.IsNullOrEmpty()......</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node == null) throw new ArgumentNullException(nameof(node) + ": 为空!");
string result = null;
switch (node.Method.Name)
{
case "StartsWith":
result = "({0} LIKE '{1}%')";
break;
case "Contains":
result = "({0} LIKE '%{1}%')";
break;
case "EndsWith":
result = "({0} LIKE '%{1}')";
break;
case "IsNullOrEmpty":
result = "({0} Is Null)";
break;
default:
throw new NotSupportedException("存在不识别方法: " + node.NodeType);
}
//解析属性 例: x.Name
this.Visit(node.Object);
//解析方法中的参数, 例: Contains("abc")中的 abc
this.Visit(node.Arguments[0]);
string right = this._stack.Pop();//在栈中剪切出来一个右侧 abc
string left = this._stack.Pop(); //在栈中剪切出来一个左侧 name
//重新拼接,存储到栈中
this._stack.Push(String.Format(result, left, right));
return node;
}
}
}
好了,今天的分享就到这里来,大家有对表达式目录树更深的见解,欢迎到评论区讨论1!!!
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END