2012-11-22 80 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,並且大概仍然如此。 –