int expectedCount = 4; Expression<Func<Thing,bool>> expression = ...; //Expression looks like (LocaleID = 1 && GenderID ==1 && (TimeFrameID == 2007 || TimeFrameID == 2008)) using (XYZDataContext context = new XYZDataContext()) { int count = context.Things.Where(expression).Count(); //... }
这是表达式的DebugView:
.Lambda #Lambda1<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { .Invoke (.Lambda #Lambda2<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) & .Invoke (.Lambda #Lambda3<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) } .Lambda #Lambda2<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { .Invoke (.Lambda #Lambda4<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) & .Invoke (.Lambda #Lambda5<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) } .Lambda #Lambda3<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { .Invoke (.Lambda #Lambda6<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) | .Invoke (.Lambda #Lambda7<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) } .Lambda #Lambda4<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { $o.LocaleID == .Constant<System.Nullable`1[System.Int32]>(1) } .Lambda #Lambda5<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { $o.GenderID == .Constant<System.Nullable`1[System.Int32]>(1) } .Lambda #Lambda6<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { $o.TimeframeID == .Constant<System.Nullable`1[System.Int32]>(2007) } .Lambda #Lambda7<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o) { $o.TimeframeID == .Constant<System.Nullable`1[System.Int32]>(2008) }
这个表达对我来说是正确的,这是相当微不足道的.当我读取调试视图时,我看到:
((LocaleID == 1 && GenderID == 1) && (TimeFrameID == 2007 || TimeFrameID == 2008))
…哪个是对的.
更新1
删除其中一个内部或子句,它工作正常.因此,无论如何,同时使用内部或子句都会破坏从LINQ到sql的转换.
更新2
我无法让调试器进入.NET Framework代码 – 我尝试使用Reflector和Visual Studio一样.我进去了一次,但总的来说,踩踏不起作用.我在StackOverflowException中遇到的一次是在:
ExecutionContext.Run(ExecutionContext executionContext,ContextCallback callback,object state,bool ignoreSyncCtx)
更新3
以下是用于创建Expression的代码.有太多的代码要发布,但它的核心在下面.我有一些类,允许我构建一个复杂的多级查询并将其序列化为JSON和XML.核心是,每个查询都是使用以下方法构建的,然后是Or’d和And’d:
public class LinqSearchField<T,V> : ISearchField { public string Name { get; private set; } public Expression<Func<T,V>> Selector { get; private set; } public Expression<Func<T,bool>> LessThan(V value) { return Expression.Lambda<Func<T,bool>>(Expression.LessThan(this.Selector.Body,GetConstant(value)),this.Selector.Parameters); } public Expression<Func<T,bool>> LessThanOrEqual(V value) { return Expression.Lambda<Func<T,bool>>(Expression.LessThanOrEqual(this.Selector.Body,bool>> Equal(V value) { return Expression.Lambda<Func<T,bool>>(Expression.Equal(this.Selector.Body,bool>> NotEqual(V value) { return Expression.Lambda<Func<T,bool>>(Expression.NotEqual(this.Selector.Body,bool>> GreaterThan(V value) { return Expression.Lambda<Func<T,bool>>(Expression.GreaterThan(this.Selector.Body,bool>> GreaterThanOrEqual(V value) { return Expression.Lambda<Func<T,bool>>(Expression.GreaterThanOrEqual(this.Selector.Body,this.Selector.Parameters); } private ConstantExpression GetConstant(V value) { return Expression.Constant(value,typeof(V)); } public Expression<Func<T,bool>> Null() { return Expression.Lambda<Func<T,Expression.Constant(null)),bool>> NotNull() { return Expression.Lambda<Func<T,this.Selector.Parameters); } }
这是和代码(OR代码是相同的,但使用Expression.And代替):
public static Expression<Func<T,bool>> And<T>(this Expression<Func<T,bool>> expression1,Expression<Func<T,bool>> expression2) { ParameterExpression[] parameters = expression1.Parameters.Union(expression2.Parameters).Distinct(new ParameterExpressionComparer()).ToArray(); InvocationExpression invocationExpression1 = Expression.Invoke(expression1,parameters); InvocationExpression invocationExpression2 = Expression.Invoke(expression2,parameters); Expression binaryExpression = null; //And the current expression to the prevIoUs one. binaryExpression = Expression.AndAlso(invocationExpression1,invocationExpression2); //Or OrElse. //Wrap the expression in a lambda. return Expression.Lambda<Func<T,bool>>(binaryExpression,parameters); }
更新4
它可能会不受欢迎,但这是一个sample which reproduces this issue.我真的需要弄清楚这里发生了什么.
解决方法
你正在组合两个lambda,它们有两个完全不同的参数实例.参数实例不可交换,即使它们具有相同的名称和相同的类型.它们是不同范围内的有效参数.当您尝试使用错误的参数对象调用其中一个表达式时,会出现混乱,在这种情况下,堆栈溢出.
你应该做的是创建一个新的参数实例(或重用一个)并重新绑定lambda的主体以使用该新参数.我怀疑这会解决这个问题.为了更进一步,你应该通过重建它们来正确地组合这些表达式,而不是将它们作为方法调用一起修补.我怀疑查询提供程序会以任何方式将这些视为调用.
尝试使用And()和Or()方法的这个实现以及此辅助方法来进行重新绑定:
public static Expression<Func<T,bool>> expression2) { // reuse the first expression's parameter var param = expression1.Parameters.Single(); var left = expression1.Body; var right = RebindParameter(expression2.Body,expression2.Parameters.Single(),param); var body = Expression.AndAlso(left,right); return Expression.Lambda<Func<T,bool>>(body,param); } public static Expression<Func<T,bool>> Or<T>(this Expression<Func<T,bool>> expression2) { var param = expression1.Parameters.Single(); var left = expression1.Body; var right = RebindParameter(expression2.Body,param); var body = Expression.OrElse(left,param); } private static Expression RebindParameter(Expression expr,ParameterExpression oldParam,ParameterExpression newParam) { switch (expr.NodeType) { case ExpressionType.Parameter: var asParameterExpression = expr as ParameterExpression; return (asParameterExpression.Name == oldParam.Name) ? newParam : asParameterExpression; case ExpressionType.MemberAccess: var asMemberExpression = expr as MemberExpression; return asMemberExpression.Update( RebindParameter(asMemberExpression.Expression,oldParam,newParam)); case ExpressionType.AndAlso: case ExpressionType.OrElse: case ExpressionType.Equal: case ExpressionType.NotEqual: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: var asBinaryExpression = expr as BinaryExpression; return asBinaryExpression.Update( RebindParameter(asBinaryExpression.Left,newParam),asBinaryExpression.Conversion,RebindParameter(asBinaryExpression.Right,newParam)); case ExpressionType.Call: var asMethodCallExpression = expr as MethodCallExpression; return asMethodCallExpression.Update( RebindParameter(asMethodCallExpression.Object,asMethodCallExpression.Arguments.Select(arg => RebindParameter(arg,newParam))); case ExpressionType.Invoke: var asInvocationExpression = expr as InvocationExpression; return asInvocationExpression.Update( RebindParameter(asInvocationExpression.Expression,asInvocationExpression.Arguments.Select(arg => RebindParameter(arg,newParam))); case ExpressionType.Lambda: var asLambdaExpression = expr as LambdaExpression; return Expression.Lambda( RebindParameter(asLambdaExpression.Body,asLambdaExpression.Parameters.Select(param => (ParameterExpression)RebindParameter(param,newParam))); default: // you should add cases for any expression types that have subexpressions return expr; } }
重新绑定方法的作用是(按名称)搜索并返回一个表达式,其中表达式树中的所有ParameterExpression都被另一个ParameterExpression的实例替换.这不会修改现有表达式,但会在需要时重建创建新更新表达式的表达式.换句话说,它返回一个新表达式,该表达式应该用作替换重绑定的表达式.
我们的想法是检查表达式并确定它是什么类型.如果是ParameterExpression,请检查它是否与我们要查找的参数同名.如果是,则返回我们的新参数,否则返回它,因为我们不应该更改它.如果表达式不是参数,则它可能是包含子表达式的表达式,并且必须被替换.
BinaryExpression将具有Left操作数和Right操作数,两个表达式.他们都需要反弹,因为他们的表达树可能是一个需要替换的参数. Update()方法将使用与新子表达式类似的表达式替换当前表达式.在这种情况下,我们只想(可能)更新左和右子表达式.
MethodCallExpression和InvocationExpression具有相同的想法,但它的树略有不同.它具有Object表达式(或者在调用的情况下表达式),它表示要调用的实例(或委托/ lambda). (MethodCallExpression还有一个MethodInfo,表示要调用的实例方法)它们还有Arguments(所有表达式),用作调用的参数.这些表达可能需要反弹.
您可以将RebindParameter()方法视为“超级”-Update()方法,该方法更新整个表达式树中的参数.
进一步说明,帮助可视化树的外观和变化.请注意,由于此处存在替换,因此大多数子树将是新实例.
[
现在这里有一些我没有意识到的东西,ExpressionVisitor
.希望我能早点注意到它.这将使反弹器更好地与之合作.而不是在这里发布完整的代码,这是在pastebin.然后使用它:
public static Expression<Func<T,bool>> expression2) { // reuse the first expression's parameter var param = expression1.Parameters.Single(); var left = expression1.Body; var right = ParameterRebinder.Rebind(expression2.Body,bool>> expression2) { var param = expression1.Parameters.Single(); var left = expression1.Body; var right = ParameterRebinder.Rebind(expression2.Body,param); }