.net中的条件查询需要支持表达式有两个理由:
1 更为灵活,也更为直观;
2 结合c#的强类型检查,代码可靠性可以得到增强。
要在条件查询中支持表达式,有几个要求要能得到满足:
1 能支持多表联合查询;
2 子类能自动关联父类;
3 对表达式的支持要尽可能的少限制;
4 不能要求一次性输入所有的表达式,而是要能根据条件逐步的拼入表达式。
在条件查询中支持表达式,说起来好像很玄奥,但仔细想下,不就是将表达式转换为sql语句吗?所以,最简单的考虑,就是解析表达式,然后来构造sql语句的from和where字句而已。
而from字句比较简单,其实就是识别表达式中用的类,没有用过的类,就添加表名列表中,最后在构造时按顺序逐一添加到from字句即可,当然,需要为其按序号分配一个别名。
所以核心的工作就是解析表达式来构造where字句。
首先就是表达式的解析:
private string Visit(Expression exp)
{
if (exp != null)
switch (exp.NodeType)
{
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
return VisitUnary((UnaryExpression)exp);
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.Or:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
return VisitBinary((BinaryExpression)exp);
case ExpressionType.Constant:
return VisitConstant((ConstantExpression)exp);
case ExpressionType.Parameter:
return VisitParameter((ParameterExpression)exp);
case ExpressionType.MemberAccess:
return VisitMemberAccess((MemberExpression)exp);
case ExpressionType.Call:
return this.VisitMethodCall((MethodCallExpression)exp);
default:
throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType));
}
return null;
}
一元表达式和两元表达式的实现都是将表达式类型转换为一个操作,然后将该操作和进一步的解析出的表达式拼接到一起,如一元表达式的解析是:
private string VisitUnary(UnaryExpression u)
{
if (u == null) return null;
string rs = null;
string op = null;
switch (u.NodeType)
{
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
op = "-";
break;
case ExpressionType.Not:
op = "Not ";
break;
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
return Visit(u.Operand);
}
rs = String.Format("({0}{1})", op, Visit(u.Operand));
return rs;
}
常量的解析首先看能否常量的类型是否能直接转换,如果不能则将该表达式动态执行取其结果:
private string VisitConstant(ConstantExpression c)
{
if (c == null) return null;
string rs = Common.TransObjToSqlValue(c.Value);
if (rs == null && !Common.CanTrans(c.Value))
{
LambdaExpression lambda = Expression.Lambda(c);
Delegate fn = lambda.Compile();
var obj = fn.DynamicInvoke(null);
rs = Common.TransObjToSqlValue(obj);
}
return rs;
}
参数的解析稍微复杂点,主要需要判断该参数是否是给的类的父类/祖类中的成员,如果是还需要判断该父类/祖类是否已经被添加到所有数据表中了,如果没有需要将其引用过来并和该子类自动进行关联。
成员引用的解析主要是将成员首先判断是否是参数,如果是那么则按参数进行解析,否则还是要视为一个动态的表达式,执行后取其值。
方法调用的解析最为复杂,因为,方法可以有多个参数、参数还有可能需要进一步的解析,后来考虑到为了进行查询而构造的表达式,其调用的方法应该不会如此复杂,所以对方法调用所支持的表达式进行了限制,如参数只能由一个,且为简单数值等等:
private string VisitMethodCall(MethodCallExpression c)
{
if (c == null) return null;
string rs = null;
if (c.Object != null)
{
//实例对象的方法调用,没有参数调用
if (c.Object.NodeType == ExpressionType.Constant)
{
//实例对象是常量
LambdaExpression lambda = Expression.Lambda(c);
Delegate fn = lambda.Compile();
rs = Common.TransObjToSqlValue(fn.DynamicInvoke(null));
}
else if (c.Object.NodeType == ExpressionType.MemberAccess)
{
LambdaExpression lambda = Expression.Lambda(c.Object);
Delegate fn = lambda.Compile();
var obj = fn.DynamicInvoke(null);
ParameterExpression paramObj = Expression.Parameter(typeof(object), "entity");
var paramEntityObj = Expression.Convert(paramObj, c.Object.Type);
var exp = Expression.Call(paramEntityObj, c.Method);
var l = Expression.Lambda<Func<object, object>>(exp, paramObj);
Func<object, object> d = l.Compile();
var o = d(obj);
rs = Common.TransObjToSqlValue(o);
}
}
else
{
//静态方法调用,只有一个参数
LambdaExpression lambda = Expression.Lambda(c.Arguments[0]);
Delegate fn = lambda.Compile();
var obj = fn.DynamicInvoke(null);
var o = c.Method.Invoke(null, new object[] { obj });
rs = Common.TransObjToSqlValue(o);
}
return rs;
}
有了表达式的解析,我们就可以定义创建函数和按条件添加表达式的函数了:
public static ExpressionVisitor_Sql Create<T>(Expression<Func<T, bool>> predicate)
{
var ev = new ExpressionVisitor_Sql();
var ty = typeof(T);
ev.m_TableList.Add(ty.Name, 1);
if (predicate != null)
ev.m_SqlWhere = " Where " + ev.Visit(predicate.Body);
return ev;
}
以及:
public void Append<T1, T2>(bool AppendIf, Expression<Func<T1, T2, bool>> predicate)
{
var ty = typeof(T1);
if (AppendIf && m_TableList.InList(ty.Name) && predicate != null)
{
int count = m_TableList.Count + m_TableList2.Count;
ty = typeof(T2);
if (m_TableList.InList(ty.Name))
m_TableList2.Add(ty.Name, count + 1);
else
m_TableList.Add(ty.Name, count + 1);
if (m_SqlWhere == null)
m_SqlWhere = " Where " + Visit(predicate.Body);
else
m_SqlWhere += " And " + Visit(predicate.Body);
}
}
当然还可以定义支持更多泛型参数表达式的类似函数。
这里面有一个比较特殊的地方,就是字符串和其它类型有一个不同点在于,字符串有一个包含操作,而包含操作不像大于、等于之类的比较操作在表达式中有明确的对应关系可供解析,所以最后用一个单独的Append_Like函数进行单独的解析。
目前还存在一个问题难以解决,即如果需要反复用到某个类,由于表达式中无法确定到底是指的哪一个类,所以没法使用重名的类进行关联查询。