2012-11-22 51 views
5

使用来自各种SO帖子的信息,特别是这个blog(更正为使用AndAlso而不是And)我已经设法将类似的类型linq表达式组合成一个谓词。但是现在我想要将两个表达式结合起来,其中一个是另一个的输入。这是完全扩展的原始Expression;组合两个Linq表达式

private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames) 
    { 
     // works 
     Expression<Func<T, bool>> Texpr = x => x.Security.Readers.Any(n => AccessorNames.ToStringArray().Contains(n.Text)); 

     return Texpr; 
    } 

注意,重要的是,我需要管理这些为表达式因为我的数据库驱动程序需要走的树转换成因此使用编译(本地通话)结合是不是一种选择。

所以下面是我想要与上面的Any()呼叫相结合的功能。最终输出表达式需要为Expression<Func<T, bool>>,我需要将x.Security.Readers传递给这一个。

public static Expression<Func<IEnumerable<EntityName>,bool>> AccessCheckExpression(IEnumerable<EntityName> AccessorNames) 
    { 
     return accessList => accessList.Any(n => AccessorNames.ToStringArray().Contains(n.Text)); 
    } 

我得尽可能这一点,但我在努力解决如何从accessCheck换出​​并让它在一个表达式中使用accessList。到目前为止,我有这个;

private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames) 
    { 
     Expression<Func<T, IEnumerable<EntityName>>> accessList = (T x) => x.Security.Readers; 
     Expression<Func<IEnumerable<EntityName>, bool>> accessCheck = SecurityDescriptor.AccessCheckExpression(AccessorNames); 

     // Combine? 
     Expression<Func<T, bool>> Texpr = ??? accessCheck + accessList ??? 

     return Texpr; 
    } 

[更新]

所以我得远一点;

class ParameterUpdateVisitor : System.Linq.Expressions.ExpressionVisitor 
    { 
     private ParameterExpression _oldParameter; 
     private ParameterExpression _newParameter; 

     public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter) 
     { 
      _oldParameter = oldParameter; 
      _newParameter = newParameter; 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (object.ReferenceEquals(node, _oldParameter)) 
       return _newParameter; 

      return base.VisitParameter(node); 
     } 
    } 

    static Expression<Func<T, bool>> UpdateParameter<T>(
     Expression<Func<T, IEnumerable<EntityName>>> expr, 
     ParameterExpression newParameter) 
    { 
     var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter); 
     var body = visitor.Visit(expr.Body); 

     return Expression.Lambda<Func<T, bool>>(body, newParameter); 
    } 

然后我可以编译;

UpdateParameter(accessList, accessCheck.Parameters[0]); 

感谢所有那些Invoke()建议,但我的猜测是,当他们到了MongoDB的驱动程序的时候也不会像InvocationExpression。但是,Invoke和我上面的代码现在都以完全相同的方式失败。即;

System.ArgumentException: Expression of type 
'System.Func`2[MyLib.Project,System.Collections.Generic.IEnumerable`1[MyLib.EntityName]]' 
cannot be used for parameter of type 
          'System.Collections.Generic.IEnumerable`1[MyLib.EntityName]' 

所以这样看来,隐含参数x.Security.Readers是一个不同类型的普通老式IEnumerable<EntityName>

+0

您错过了'ExpressionIsNamed'定义中的类型参数。 –

+0

@HamletHakobyan:不一定。它们的泛型参数可以在包含这个私有方法的类上定义。 –

+0

@DanielHilgarth行。谢谢! –

回答

5

VisitorExpression这里是你的朋友。这里的合并类似的简化,但完整的例子:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Linq.Expressions; 

class Source { 
    public List<Value> Values {get;set;} 
} 
class Value { 
    public int FinalValue {get;set;} 
} 
static class Program { 
    static void Main() { 
     Expression<Func<Source, IEnumerable<Value>>> f1 = 
      source => source.Values; 
     Expression<Func<IEnumerable<Value>, bool>> f2 = 
      vals => vals.Any(v => v.FinalValue == 3); 

     // change the p0 from f2 => f1 
     var body = SwapVisitor.Swap(f2.Body, f2.Parameters[0], f1.Body); 
     var lambda = Expression.Lambda<Func<Source, bool>>(body,f1.Parameters); 
     // which is: 
     // source => source.Values.Any(v => (v.FinalValue == 3)) 
    } 

} 
class SwapVisitor : ExpressionVisitor { 
    private readonly Expression from, to; 
    private SwapVisitor(Expression from, Expression to) { 
     this.from = from; 
     this.to = to; 
    } 
    public static Expression Swap(Expression body, 
     Expression from, Expression to) 
    { 
     return new SwapVisitor(from, to).Visit(body); 
    } 
    public override Expression Visit(Expression node) 
    { 
     return node == from ? to : base.Visit(node); 
    } 
} 

编辑:你的情况,这将是:

private Expression<Func<T, bool>> ExpressionIsNamed(
    IEnumerable<EntityName> AccessorNames) 
{ 
    Expression<Func<T, IEnumerable<EntityName>>> accessList = 
     (T x) => x.Security.Readers; 
    Expression<Func<IEnumerable<EntityName>, bool>> accessCheck = 
     SecurityDescriptor.AccessCheckExpression(AccessorNames); 


    var body = SwapVisitor.Swap(accessCheck.Body, 
     accessCheck.Parameters[0], accessList.Body); 
    return Expression.Lambda<Func<T, bool>>(body, accessList.Parameters); 
} 
+0

谢谢。我有类似的工作(请参阅我的udpated文章),但我有一个不匹配的IEnumerable类型的问题。 – cirrus

+1

@cirrus你没有正确合并它们;让我编辑,在代码的上下文中显示上述内容... –

+0

真棒,谢谢!这不仅是编译和运行,它也没有在DB驱动程序中泄露(这就是我需要结合表达式的原因),并且我有一列通过测试的证明。 – cirrus

0

我不知道,如果是去上班,但试试这个:

private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames) 
{ 
    Expression<Func<T, IEnumerable<EntityName>>> accessList = (x) => x.Security.Readers; 
    Expression<Func<IEnumerable<EntityName>, bool>> accessCheck = AccessCheckExpression(AccessorNames); 

    var result = Expression.Lambda<Func<T, bool>>(
     Expression.Invoke(accessCheck, accessList.Body), // make invokation of accessCheck, and provide body of accessList (x.Security.Readers) as parameter 
     accessList.Parameters.First() // parameter 
    ); 

    return result; 
} 
+1

调用是不可靠的;它不适用于所有的提供商 - 英孚非常讨厌Invoke,并且大概仍然如此。 –