2016-11-28 8 views
1

複数の式ツリーをマージして、Entity Frameworkクエリのセレクタを構築する方法を探しています。クエリは、ユーザー提供のパラメータに基づいて選択する列を認識します。たとえば、基本クエリはエンティティのID /名前列を返します。パラメータが明示的に設定されて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あなたのサンプルのようなボディラムダは比較的簡単です。必要なのは組み合わせBindingsで別のMemberInitExpressionを作成することです:上記のコードはに二ラムダ本体を再バインドするには、以下のパラメータ代替ヘルパーを使用していますので、ラムダ式は、一つの同じパラメータにバインドされなければならないことを

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); 
} 

注意最初のラムダパラメータ:

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

私の場合は、述語でのみ動作します。私の場合、TDtoはブールではありません。 – Joe

+0

@Joe PredicateBuilderを使用するには少しリファクタリングする必要があります。私の経験から、EFクエリの式を組み合わせる最も簡単な方法です。 –

+0

質問は非常に複雑なケースの過度に単純化されたバージョンです。私はリファクタリングできません。とにかくありがとうございました。 – Joe

0

私は拡張メソッドを持つこの種のものを行います。その構文的には、どこでも表現木を使用するよりも少し良いです。私はこれをcomposable repositoriesと呼ぶ。

また、さまざまな拡張メソッドの式ツリーを結合するためのツール(LinqExpander)もあります。これは、データベースからの投影(選択)に特に便利です。あなたがサブエンティティで物事をしているときは、これは唯一不可欠です。 (ここでは私のポストを参照してください。Composable Repositories - Nesting extensions

使用量はの線に沿って何かになります:あなたは(イムtogeather 2つの選択をマージしたい

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

:ToDtosは次のようになります

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

アンダーフェッチを避けることを前提としていますが、これは少し厄介なようです)。私はこのような投影を使用してこれを行うだろう:

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

あなたは本当にそれがこのような単一のエンティティを返すしたい場合は、おそらくのような何かを行うことができます:

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

を私が今までこれを試してみましたhavent 。

これはサブプロジェクションを使用するため、.AsExpandable拡張が必要です。

関連する問題