本文介绍如何解析lambda表达式来获取一个满足条件的查询语句。
先看个截图
通过设置实体对象Article_Content的查询表达式,就可以获取对应的参数化SQL语句,使用起来很方便,减少了代码的书写,同时提高了安全性。
本文需要了解的基础知识有:
- lambda表达式
- Expression表达式树
- 扩展方法
首先,我们应该有一个普通的实体对象和它的基类
//基类
class baseEntity
{
internal Expression whereFunc;
}
//实体对象
class Article_Content : baseEntity
{
public int? Article_Id { get; set; }
public string Article_Title { get; set; }
public string Article_SourceUrl { get; set; }
}
这个时候我们需要定义一些扩展方法以便对实体的查询表达式进行保存。我们的SQL语句的需求是要参数化的。所以,在实体对象这里还不知道数据库类型,没有办法获取对应的参数化符号。
/// <summary>
/// 设置匹配表达式
/// </summary>
public static void SetWhereFunc<T>(this T entity, Expression<Func<T, bool>> func) where T : baseEntity
{
entity.whereFunc = func;
}
好了。这个时候我们可以写出类似下面的代码了:
Article_Content art = new Article_Content();
art.SetWhereFunc(ar=>ar.Aritlce_Id == 4 && ar.Article_Title == "sss");
但是SQL语句中很常见的Like查询似乎还没有办法实现。
我们通过分析Where子句,发现where语句的做事然筛选出来的纪录要满足where子句的条件,那么where子句返回的就是一个bool,where子句其实是一个逻辑表达式。所以,就有了刚才SetWhereFunc函数里面的第二个参数中Func的返回值是bool。
既然where子句要求返回的是一个bool表达式同理:Like、In、Not等表达方法我们也只能让它返回的是bool值;
那么就有了下面的扩展函数:
public static bool In<T>(this T obj, T[] array)
{
return true;
}
public static bool Like(this string str, string likeStr)
{
return true;
}
这个时候很现在,我们的SetWhereFunc可以写出如下的代码了:
art.SetWhereFunc(ar=>ar.Aritlce_Id.In(new int?[]{4,6}) && ar.Article_Title.Like("%sss"));
到目前为止,前台编写方式已经解决了。
剩下的就是如何来分析我们设置的这个lambda表达式了。
当然分析lambda表达式的时候,我们还需要考虑生成的SQL语句的参数化。这个时候还需要另一个数据操作对象:
public abstract class DbOperate
{
/// <summary>
/// 获取此数据类型的参数形式表现
/// </summary>
/// <param name="fieldsName"></param>
/// <returns></returns>
public abstract string GetParamFlag(string fieldsName);
//其他操作方法.....
}
这个对象还有其他方法,不过在这里,我们主要使用的就是这样一个参数标识,如果它在MSSQL里,它返回的是@+fieldName,Orcale则返回的是:fieldName。
在构建SQL语句的时候,我们同样也使用扩展方法来解析表达式。
internal static string Where<T>(this T entity, Expression func, DbOperate dbop, ref ArrayList alParamNames, ref ArrayList alParamValues) where T : baseEntity
{
return ExpressionRouter(func, dbop, ref alParamNames, ref alParamValues);
}
lambda表达式也是表达式的一种。那么我们可以通过分析表达式的类型计算对应的产生式。
现在主要遇到的表达式有以下几种:
- BinaryExpression
- LambdaExpression
- MethodCallExpression
- MemberExpression
- ConstantExpression
- NewArrayExpression
- UnaryExpression
根据不同的类型,可以进行不同操作,为了简单起见,我们这里演示的是直接拼接字符串的方式,各位可以自己尝试使用参数的方式,函数原型在上面了。
Code
static string BinarExpressionProvider(Expression left, Expression right, ExpressionType type)
{
string sb = "(";
//先处理左边
sb += ExpressionRouter(left);
sb += ExpressionTypeCast(type);
//再处理右边
string tmpStr = ExpressionRouter(right);
if (tmpStr == "null")
{
if (sb.EndsWith(" ="))
sb = sb.Substring(0, sb.Length - 2) + " is null";
else if (sb.EndsWith("<>"))
sb = sb.Substring(0, sb.Length - 2) + " is not null";
}
else
sb += tmpStr;
return sb += ")";
}
//表达式路由计算
static string ExpressionRouter(Expression exp)
{
string sb = string.Empty;
if (exp is BinaryExpression)
{
BinaryExpression be = ((BinaryExpression)exp);
return BinarExpressionProvider(be.Left, be.Right, be.NodeType);
}
else if (exp is MemberExpression)
{
MemberExpression me = ((MemberExpression)exp);
return me.Member.Name;
}
else if (exp is NewArrayExpression)
{
NewArrayExpression ae = ((NewArrayExpression)exp);
StringBuilder tmpstr = new StringBuilder();
foreach (Expression ex in ae.Expressions)
{
tmpstr.Append(ExpressionRouter(ex));
tmpstr.Append(",");
}
return tmpstr.ToString(0, tmpstr.Length - 1);
}
else if (exp is MethodCallExpression)
{
MethodCallExpression mce = (MethodCallExpression)exp;
if (mce.Method.Name == "Like")
return string.Format("({0} like {1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1]));
else if (mce.Method.Name == "NotLike")
return string.Format("({0} Not like {1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1]));
else if (mce.Method.Name == "In")
return string.Format("{0} In ({1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1]));
else if (mce.Method.Name == "NotIn")
return string.Format("{0} Not In ({1})", ExpressionRouter(mce.Arguments[0]), ExpressionRouter(mce.Arguments[1]));
}
else if (exp is ConstantExpression)
{
ConstantExpression ce = ((ConstantExpression)exp);
if (ce.Value == null)
return "null";
else if (ce.Value is ValueType)
return ce.Value.ToString();
else if (ce.Value is string || ce.Value is DateTime || ce.Value is char)
return string.Format("'{0}'", ce.Value.ToString());
}
else if (exp is UnaryExpression)
{
UnaryExpression ue = ((UnaryExpression)exp);
return ExpressionRouter(ue.Operand);
}
return null;
}
static string ExpressionTypeCast(ExpressionType type)
{
switch (type)
{
case ExpressionType.And:
case ExpressionType.AndAlso:
return " AND ";
case ExpressionType.Equal:
return " =";
case ExpressionType.GreaterThan:
return " >";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.NotEqual:
return "<>";
case ExpressionType.Or:
case ExpressionType.OrElse:
return " Or ";
case ExpressionType.Add:
case ExpressionType.AddChecked:
return "+";
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
return "-";
case ExpressionType.Divide:
return "/";
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
return "*";
default:
return null;
}
}
到这里,一个基于表达式分析的实体查询就做好了。
下面是文章一开始的那个lambda表达式分析出来的对应的where语句
对应的参数和参数值顺序是一一对应的。
我这里的对Expression表达式进行了计算,所以最后一个参数对应的
string.Concat(GetStr(7), "sdfsdf".Length, "sssss" + GetStr(6)).ToUpper()
这个表达式已经被计算完成得到"GOOD76SSSSSGOOD6",GetStr(int)函数见第一张截图。
这里主要体会的是用Expression Tree的一个实际用法。实际跑起来体会下就能明白。要说的话实在内容太多。而前面的都介绍都是概念性的东西。
代码下载:https://files.cnblogs.com/Airfeeling/ExpressionTreeAndlambdaToSQL.rar