2016-11-28 104 views
1

我正在尋找一種方法來合併多個表達式樹以構建實體框架查詢的選擇器。查詢根據用戶提供的參數知道要選擇哪些列。例如,基本查詢返回實體的ID/Name列。如果顯式設置參數以檢索Description列,則查詢將返回ID/Name/Description。合併實體框架表達式樹

因此,我需要的代碼爲MergeExpressions方法在下面的代碼。

Expression<Func<T, TDto>> selector1 = x => new TDto 
{ 
    Id = x.Id, 
    Name = x.Name 
} 

Expression<Func<T, TDto>> selector2 = x => new TDto 
{ 
    Description = x.Description 
} 

var selector = selector1; 
if (includeDescription) 
    selector = MergeExpressions(selector1, selector2); 

var results = repo.All().Select(selector).ToList(); 

謝謝。

回答

1

對於一般情況不確定,但合併MemberInitExpression體型lambda類似於您的示例相對容易。所有你需要的是創建另一個MemberInitExpression聯合Bindings

static Expression<Func<TInput, TOutput>> MergeExpressions<TInput, TOutput>(Expression<Func<TInput, TOutput>> first, Expression<Func<TInput, TOutput>> second) 
{ 
    Debug.Assert(first != null && first.Body.NodeType == ExpressionType.MemberInit); 
    Debug.Assert(second != null && second.Body.NodeType == ExpressionType.MemberInit); 
    var firstBody = (MemberInitExpression)first.Body; 
    var secondBody = (MemberInitExpression)second.Body.ReplaceParameter(second.Parameters[0], first.Parameters[0]); 
    var body = firstBody.Update(firstBody.NewExpression, firstBody.Bindings.Union(secondBody.Bindings)); 
    return first.Update(body, first.Parameters); 
} 

注意lambda表達式必須綁定到同一個參數,所以上面的代碼中使用下列參數替代品幫手重新綁定第二拉姆達體第一個lambda參數:

public static partial class ExpressionUtils 
{ 
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target) 
    { 
     return new ParameterReplacer { Source = source, Target = target }.Visit(expression); 
    } 

    class ParameterReplacer : ExpressionVisitor 
    { 
     public ParameterExpression Source; 
     public Expression Target; 
     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      return node == Source ? Target : base.VisitParameter(node); 
     } 
    } 
} 
+1

簡單而乾淨的解決方案。謝謝伊萬。 – Joe

0

結賬PredicateBuilder

例子:

Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A"); 
Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.Contains("B"); 

var expr3 = PredicateBuilder.And(expr1, expr2); 
var query = context.Customers.Where(expr3); 

var expr3 = expr1.And(expr2); 
var query = context.Customers.Where(expr3); 
+0

PredicateBuilder只適用於謂詞,在我的情況下TDto不是布爾型。 – Joe

+0

@Joe您必須重構一下才能使用PredicateBuilder。根據我的經驗,爲EF查詢組合表達式最簡單的方法。 –

+0

問題是一個非常複雜的案例的過度簡化版本。我無法重構。不管怎樣,謝謝你。 – Joe

0

我做這種與擴展方法的東西。它的語法比在任何地方使用表達樹好一點。我稱之爲composable repositories

我還寫了一個工具(LinqExpander)將不同擴展方法的表達式樹組合起來,這對於從數據庫中進行投影(選擇)特別有用。當你對子實體做事情時,這只是一種詭計。 (見我的帖子在這裏:Composable Repositories - Nesting extensions

用法是沿着線的東西:

var dtos = context.Table 
       .ThingsIWant() //filter the set 
       .ToDtos() //project from database model to something else (your Selector) 
       .ToArray();//enumerate the set 

ToDtos可能看起來像:

public static IQueryable<DtoType> ToDtos(this IQueryable<DatabaseType> things) 
{ 
    return things.Select(x=> new DtoType{ Thing = x.Thing ... }); 
} 

你要合併兩個選擇togeather(IM假設避免underfetch,但這似乎有點奇怪)。我會用這樣的投影做到這一點:

context.Table 
       .AsExpandable() 
       .Select(x=>new { 
       Dto1 = x.ToDto1(), 
       Dto2 = x.ToDto2() 
       }) 
       .ToArray(); 

如果你真的希望它返回一個單獨的實體,這樣你或許可以這樣做:

context.Table 
       .AsExpandable() 
       .Select(x=> ToDto1(x).ToDto2(x)); 

,但我還沒有吃過這個。

由於這使用了子投影,因此您將需要.AsExpandable擴展。