2012-12-19 32 views
2

我正在使用實體框架版本4.我需要比較一個大型(〜一百萬條記錄)SQL Server表與從Web返回的較長(〜2000)複雜對象數組服務。需要比較五個不同的屬性,以確定複雜對象的實例是否已經在數據庫中。涉及複雜對象的實體框架的Linq表達式構建

我創建了一個函數,它返回在.Where和.Any方法中使用的表達式。它看起來像這樣(其中A是複雜對象,而tblA是EF類):

function Expression<tblA, bool> GetSearchPredicate(A a) 
{ 
    return ta => ta.Field1.Equals(a.Field1) 
     && ta.Field2.Equals(a.Field2) 
     && ta.Field3.Equals(a.Field3) 
     && ta.Field4.Equals(a.Field4) 
     && ta.Field5.Equals(a.Field5); 
} 

這可行。我可以通過這樣比較所有2000個A的實例:

IEnumerable<A> objects = [web service call]; 
var result = objects.Select(a => !db.tblA.Any(GetSearchPredicate(a))); 

這也適用。但速度很慢。所以我研究了構建一種實用方法,該方法可以構建一個可以通過EF直接傳輸到數據庫的表達式。

我使用this question中的代碼作爲構建該實用方法的基礎。該問題中的示例顯示將單個屬性與一系列常量進行比較,而我的版本將不得不將多個屬性與多個常量進行比較。我盡力而爲低於:

public static IQueryable<TEntity> WhereIn<TEntity> 
     (
     this ObjectQuery<TEntity> query, 
     IEnumerable<Expression<Func<TEntity, bool>>> predicates 
     ) 
    { 
     if (predicates == null) throw new ArgumentNullException("predicates"); 

     IEnumerable<ParameterExpression> p = predicates.Select(pred => pred.Parameters.Single()).ToArray(); 

     IEnumerable<Expression> equals = predicates.Select(value => 
      (Expression)value.Body); 

     Expression bigEqual = equals.Aggregate((accumulate, equal) => 
      Expression.Or(accumulate, equal)); 

     var result1 = Expression.Lambda<Func<TEntity, bool>>(bigEqual, p.First()); 
     var result = query.Where(result1); 
     return result; 
    } 

這將這樣被調用:

IEnumerable<A> objects = [web service call]; 
var result = db.tblA.WhereIn(objects.Select(a => GetSearchPredicate(a))); 

我得到的是一個消息說,「TA」(即針對TEntity對象佔位符)未綁定。我認爲這是因爲我有多個表達式(變量predicates)被合併,也許這個消息被拋出,因爲我只是從IEnumerable的第一個predicates傳遞參數。但即使predicates只有一個表達式,也會發生這種情況。

我有理由相信,基於該方法我聯繫,我可以建立這五個特性(通過A.Field5A.Field1值)比較恆定的一種表現,而不是傳遞的參數predicates已經讓他們組合成一系列的表達。但我寧願不要,因爲這需要我的方法知道它的類型爲AtblA,這與泛型和通用目的相反。 (它也會很複雜和凌亂。)

我希望我展示的例子能解釋我想要做的事情。它能以通用的方式完成嗎?

+0

在什麼時候,你得到的消息? –

+0

@GregBair當我嘗試執行該函數的結果。對不起,我把那部分拿出來了! –

+0

不要試圖無益,但使用可以導入到EF模型中的存儲過程來完成(並且速度非常快)會非常簡單。你有沒有特別的理由不這樣做? –

回答

3

您將需要用單個參數替換謂詞主體中的參數。像這樣的東西應該工作:然後

public static Expression<Func<T, bool>> BuildOr<T>(
    IEnumerable<Expression<Func<T, bool>>> predicates) 
{ 
    Expression body = null; 
    ParameterExpression p = null; 
    Expression<Func<T, bool>> first = null; 

    foreach (Expression<Func<T, bool>> item in predicates) 
    { 
     if (first == null) 
     { 
      first = item; 
     } 
     else 
     { 
      if (body == null) 
      { 
       body = first.Body; 
       p = first.Parameters[0]; 
      } 

      var toReplace = item.Parameters[0]; 
      var itemBody = ReplacementVisitor.Transform(item, toReplace, p); 
      body = Expression.OrElse(body, itemBody); 
     } 
    } 

    if (first == null) 
    { 
     throw new ArgumentException("Sequence contains no elements.", "predicates"); 
    } 

    return (body == null) ? first : Expression.Lambda<Func<T, bool>>(body, p); 
} 

private sealed class ReplacementVisitor : ExpressionVisitor 
{ 
    private IList<ParameterExpression> SourceParameters { get; set; } 
    private Expression ToFind { get; set; } 
    private Expression ReplaceWith { get; set; } 

    public static Expression Transform(
     LambdaExpression source, 
     Expression toFind, 
     Expression replaceWith) 
    { 
     var visitor = new ReplacementVisitor 
     { 
      SourceParameters = source.Parameters, 
      ToFind = toFind, 
      ReplaceWith = replaceWith, 
     }; 

     return visitor.Visit(source.Body); 
    } 

    private Expression ReplaceNode(Expression node) 
    { 
     return (node == ToFind) ? ReplaceWith : 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; 
    } 
} 

WhereIn方法變爲:

public static IQueryable<TEntity> WhereIn<TEntity>(
    this ObjectQuery<TEntity> query, 
    IEnumerable<Expression<Func<TEntity, bool>>> predicates) 
{ 
    if (predicates == null) throw new ArgumentNullException("predicates"); 

    var predicate = BuildOr(predicates); 
    return query.Where(predicate); 
} 
+0

WOW。真棒!我會立即嘗試這個! –

+0

事實證明,按照我嘗試的方式進行操作會導致RPC調用的許多參數,可能(我猜)會從所有的A'實例中傳遞屬性值。但是這解決了我想解決的問題。非常感謝你! –

+0

看來我們應該能夠使用LINQ Aggregate將表達式聚合成單個表達式。對此有何想法,理查德? – pomeroy