2013-06-24 41 views
1

是否可以通過編寫一個函數來創建一個「元謂語」,該函數需要2個(或4個,如果需要)lambda表示左側和右側屬性操作數)並讓它生成一個謂詞。類似下面的代碼示例:創建一個接受lambda操作數參數的可重用的「元謂語」函數

​​

OR:

public Expression<Func<Something,bool>> DatesActive<Something>(Expression<Something> startDateOperandSelector, Expression<Something> endDateOperandSelector){ 
    return (startDateOperandSelector, endDateOperandSelector) => (
    (startDatePropertySelector >= DateTime.Now) 
    && (endDatePropertySelector <= DateTime.Now) 
    ); 
} 

其中對於每個側或startDatePropertySelectorendDatePropertySelectorSomeStringProperty由拉姆達定義?我還沒有想出如何動態傳遞謂詞表達式的操作數。

我非常希望能夠爲內聯這樣的:

return new Expression<Func<Request,bool>>[]{ 
    r => (r.Id != request.Id) && (!r.Reviewed), 
    StringEquals(r => r.VendorName, request=>request.VendorName), 
    NotExpired(r => r.ContractStart, request=>request.ContractEnd),       
    ... 
}; 

* 是否有人有對最佳方式的點子接近呢?我的興趣在於創建「meta」表達式,以便在多個屬性中重複使用相同的表達式時使用。給出具體的例子僅供參考/解釋。非常願意知道這是否愚蠢,如果有更好的方法,很高興學習。 *

如果需要更多背景。

背景:後該表達的更簡單的形式是在地方,我被迫重構處理事實的EntityFramework不把平等,你會如何期待,與其解釋C#這樣的: (rightSide.SomeStringProperty == leftSide.SomeStringProperty) 作爲這樣

(
    (rightSide.SomeStringProperty IS NULL AND leftSide.SomeStringProperty IS NULL) 
    OR (rightSide.SomeStringProperty = leftSide.SomeStringProperty) 
) 

兩個部分SQL表達式更字面上tranlates它爲:

(rightSide.SomeStringProperty = leftSide.SomeStringProperty) 

當然不會返回雙方都爲空的值。顯然,這已在EF6得到糾正(修正:@Slauma指出,這是提供EF5通過UseCSharpNullComparisonBehavior我使用EF4及此版本無法升級。)

我想避免更多的重複代碼,如以下內容:

  var where = new Expression<Func<Request,bool>>[]{ 
        r => (r.Id != request.Id) && (!r.Reviewed) 
        && (
         (r.Address == request.Address) 
         || (string.IsNullOrEmpty(r.Address) && string.IsNullOrEmpty(request.Address)) 
        ) 
        && (
         (r.City == request.City) 
         || (string.IsNullOrEmpty(r.City) && string.IsNullOrEmpty(request.City)) 
        ) 
        && (
         (r.Province == request.Province) 
         || (string.IsNullOrEmpty(r.Province) && string.IsNullOrEmpty(request.Province)) 
        ) 
        && (
         (r.PostalCode == request.PostalCode) 
         || (string.IsNullOrEmpty(r.PostalCode) && string.IsNullOrEmpty(request.PostalCode)) 
        ) 
        && (
         (r.Website == request.Website) 
         || (string.IsNullOrEmpty(r.Website) && string.IsNullOrEmpty(request.Website)) 
        ) 
       }; 
+2

您可以使用?運營商? '(r.City ??「」)==(request.City ??「」)' – Jay

+0

這讓我重構了更簡潔的東西。謝謝!但是,如果我不處理空字符串和空同樣這不會在這裏工作。我更感興趣的是能夠參數化類似於將在多個字段中重複的元表達式的東西。 – j0tt

+0

這樣的事情變得棘手。 EF必須將您編寫的代碼轉換爲實際的SQL語句('?== ISNUL()'),否則它將炸燬。通常在這種情況下,我構建查詢鏈並檢查查詢本身之外的條件。 – Jay

回答

2

您可以使用System.Linq.Expressions namespace手動構建表達式。對於已公佈的兩個例子,下面應該工作:

public static Expression<Func<T, bool>> StringEquals<T>(Expression<Func<T, string>> leftOperand, Expression<Func<T, string>> rightOperand) 
{ 
    var p = leftOperand.Parameters[0]; 
    var leftOperandBody = leftOperand.Body; 
    var rightOperandBody = ReplacementVisitor.Transform(rightOperand, rightOperand.Parameters[0], p); 

    var isNullOrEmptyMethod = typeof(string).GetMethod("IsNullOrEmpty"); 
    var leftNullOrEmpty = Expression.Call(isNullOrEmptyMethod, leftOperandBody); 
    var rightNullOrEmpty = Expression.Call(isNullOrEmptyMethod, rightOperandBody); 
    var bothNullOrEmpty = Expression.AndAlso(leftNullOrEmpty, rightNullOrEmpty); 
    var areEqual = Expression.Equal(leftOperandBody, rightOperandBody); 
    var body = Expression.OrElse(bothNullOrEmpty, areEqual); 

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

public static Expression<Func<T, bool>> DatesActive<T>(Expression<Func<T, DateTime>> startDate, Expression<Func<T, DateTime>> endDate) 
{ 
    var p = startDate.Parameters[0]; 
    var startDateBody = startDate.Body; 
    var endDateBody = ReplacementVisitor.Transform(endDate, endDate.Parameters[0], p); 

    var nowProperty = typeof(DateTime).GetProperty("Now"); 
    var nowValue = Expression.Property(null, nowProperty); 
    var startValid = Expression.GreaterThanOrEqual(startDateBody, nowValue); 
    var endValid = Expression.LessThanOrEqual(endDateBody, nowValue); 
    var body = Expression.AndAlso(startValid, endValid); 

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

internal sealed class ReplacementVisitor : ExpressionVisitor 
{ 
    private IList<ParameterExpression> SourceParameters { get; set; } 
    private Expression Find { get; set; } 
    private Expression Replace { get; set; } 

    public static Expression Transform(LambdaExpression source, Expression find, Expression replace) 
    { 
     var visitor = new ReplacementVisitor 
     { 
     SourceParameters = source.Parameters, 
     Find = find, 
     Replace = replace, 
     }; 

     return visitor.Visit(source.Body); 
    } 

    private Expression ReplaceNode(Expression node) 
    { 
     return (node == Find) ? Replace : node; 
    } 

    protected override Expression VisitConstant(ConstantExpression node) 
    { 
     return ReplaceNode(node); 
    } 

    protected override Expression VisitBinary(BinaryExpression node) 
    { 
     var result = ReplaceNode(node); 
     if (result == node) result = base.VisitBinary(node); 
     return result; 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     if (SourceParameters.Contains(node)) return ReplaceNode(node); 
     return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node; 
    } 
} 
+0

謝謝!我很感激你花時間向我展示如何做到這一點,我沒有看到你在這裏做的很多。雖然我真的希望能讓這些方法的主體更接近直的lambda表達式,所以它們會更易於閱讀和維護,這與我所尋找的非常接近。我會測試它,讓你知道它是如何發展的。 – j0tt

0

這可以通過動態合成表達式樹來完成,理查德證實,雖然它不一定是這麼複雜。具體來說,訪問者模式會增加不必要的開銷考慮這個簡單的解決方案,以你的第一個例子:

using E = System.Linq.Expressions.Expression; 

/* ... */ 

private static readonly MethodInfo IsNullOrEmptyMethod = typeof(string).GetMethod("IsNullOrEmpty"); 

public static Expression<Func<bool>> StringEquals(Expression<Func<String>> leftAccessor, Expression<Func<String>> rightAccessor) 
{ 
    var left = E.Parameter(typeof(string), "left"); 
    var right = E.Parameter(typeof(string), "left"); 

    //() => { 
    //  string left = leftAccessor(); 
    //  string right = rightAccessor(); 
    //  
    //  return left == right || 
    //   string.IsNullOrEmpty(left) && string.IsNullOrEmpty(right); 
    // } 

    return E.Lambda<Func<bool>>(
     E.Block(
      new[] { left, right }, 
      E.Assign(left, E.Invoke(leftAccessor)), 
      E.Assign(right, E.Invoke(rightAccessor)), 
      E.OrElse(
       E.Equal(left, right), 
       E.AndAlso(
        E.Call(IsNullOrEmptyMethod, left), 
        E.Call(IsNullOrEmptyMethod, right))))); 
} 

您可以將類似的技術來設計一個解決方案,你的第二個例子。請注意缺少任何通用參數:包含屬性的實際項目不需要公開。您可以使用此方法比較同一對象上的兩個屬性;兩個不同對象的屬性相同;或任何任意值。

請注意,lambda編譯器將負責內嵌簡單訪問器,如() => r.Address。它可以很容易地做到這一點,因爲訪問器本身就是表達式。

編輯:再次讀你的問題,我看你使用的是實體框架。我不確定EF的查詢提供程序是否足夠複雜以便嵌入訪問器。如果不是,這可能不起作用,在這種情況下,可能需要做一些手動轉換,正如理查德在他的回答中所做的那樣。我會有興趣聽到這是否適合您的情況。我會以任何方式離開這個答案,因爲它可能對不使用EF的人有用。

另外,正如@svick在註釋中指出的那樣,EF幾乎肯定不支持塊表達式。您可能必須按如下方式構建λ:

return E.Lambda<Func<bool>>(
    E.OrElse(
     E.Equal(E.Invoke(leftAccessor), E.Invoke(rightAccessor)), 
     E.AndAlso(
      E.Call(IsNullOrEmptyMethod, E.Invoke(leftAccessor)), 
      E.Call(IsNullOrEmptyMethod, E.Invoke(rightAccessor))))); 
+2

我懷疑EF支持'BlockExpression's。 – svick

+0

哦,好點! –

+0

EF在過去使用'Invoke'的表達式存在問題(http://stackoverflow.com/q/10524/124386),但我沒有檢查過它是否在最新版本中修復。 –

相關問題