0

以下のコード ブロックは、「 linq 拡張メソッドを使用して左外部結合を実行するにはどうすればよいですか?」という質問に答えます。

var qry = Foo.GroupJoin(
      Bar, 
      foo => foo.Foo_Id,
      bar => bar.Foo_Id,
      (x,y) => new { Foo = x, Bars = y })
.SelectMany(
      x => x.Bars.DefaultIfEmpty(),
      (x,y) => new { Foo = x, Bar = y});

この GroupJoin と SelectMany を MethodCallExpressions としてどのように記述しますか? 私が見つけたすべての例は、文字列をラムダに変換する DynamicExpressions を使用して記述されています (別の例)。可能であれば、そのライブラリに依存することは避けたいと思っています。

上記のクエリは、式と関連するメソッドで記述できますか?

foo => foo.Foo_IdParameterExpressions MemberExpressions と Expression.Lambda() を使用するような基本的なラムダ式を構築する方法は知っていますが、どのように構築します(x,y) => new { Foo = x, Bars = y })か??? 両方の呼び出しを作成するために必要なパラメーターを構築できるようにするには?

MethodCallExpression groupJoinCall =
         Expression.Call(
           typeof(Queryable),
           "GroupJoin",
           new Type[] { 
                  typeof(Customers), 
                  typeof(Purchases), 
                  outerSelectorLambda.Body.Type, 
                  resultsSelectorLambda.Body.Type 
               },
                           c.Expression,
                           p.Expression,
                           Expression.Quote(outerSelectorLambda),
                           Expression.Quote(innerSelectorLambda),
                           Expression.Quote(resultsSelectorLambda)
                        );
MethodCallExpression selectManyCall =
   Expression.Call(typeof(Queryable), 
    "SelectMany", new Type[] { 
        groupJoinCall.ElementType, 
        resultType, 
        resultsSelectorLambda.Body.Type 
    }, groupJoinCall.Expression, Expression.Quote(lambda), 
    Expression.Quote(resultsSelectorLambda))); 

最終的に、n Bars を Foo に結合する繰り返し可能なプロセスを作成する必要があります。垂直方向のデータ構造があるため、ユーザーが Foo をソートできるように、バーとして表されるものを返すには、左結合クエリが必要です。要件は、ユーザーが 10 本のバーで並べ替えできるようにすることですが、3 本を超えるバーを使用することはないと思います。上記の最初のブロックのコードを最大 10 回チェーンするプロセスを作成しようとしましたが、5 回を超えると Visual Studio 2012 が遅くなり、7 回前後でロックアップしました。

したがって、selectManyCall を返し、ユーザーが要求した回数だけ再帰的に呼び出すメソッドを作成しようとしています。

LinqPad で機能する以下のクエリに基づいて、繰り返す必要があるプロセスは、Expression オブジェクトの透過的な識別子を手動で処理することだけです。クエリ sorts は、Bars (この場合は 3 Bars) でソートされた Foos を返します。

補足です。このプロセスは、OrderBy デリゲートで結合を行う方がはるかに簡単ですが、生成されるクエリには T-SQL "OUTER APPLY" が含まれています。これは、必要な Oracle でサポートされていません。

プロジェクションを匿名型に書き込む方法や、機能する可能性のあるその他の独創的なアイデアに感謝します。ありがとうございました。

var q = Foos
        .GroupJoin (
            Bars, 
            g => g.FooID, 
            sv => sv.FooID, 
            (g, v) => 
                new  
                {
                    g = g, 
                    v = v
                }
        )
        .SelectMany (
            s => s.v.DefaultIfEmpty (), 
            (s, v) => 
                new  
                {
                    s = s, 
                    v = v
                }
        )
        .GroupJoin (
            Bars, 
            g => g.s.g.FooID, 
            sv => sv.FooID, 
            (g, v) => 
                new  
                {
                    g = g, 
                    v = v
                }
        )
        .SelectMany (
            s => s.v.DefaultIfEmpty (), 
            (s, v) => 
                new  
                {
                    s = s, 
                    v = v
                }
        )
        .GroupJoin (
            Bars,  
            g => g.s.g.s.g.FooID, 
            sv => sv.FooID, 
            (g, v) => 
                new  
                {
                    g = g, 
                    v = v
                }
        )
        .SelectMany (
            s => s.v.DefaultIfEmpty (),  
            (s, v) => 
                new  
                {
                    s = s, 
                    v = v
                }
        )
        .OrderBy (a => a.s.g.s.g.v.Text)
        .ThenBy (a => a.s.g.v.Text)
        .ThenByDescending (a => a.v.Date)
        .Select (a => a.s.g.s.g.s.g);
4

1 に答える 1

1

式を生成する方法がわからない場合は、いつでもコンパイラから支援を受けることができます。あなたができることは、クエリするタイプでラムダ式を宣言し、ラムダを書くことです。コンパイラが式を生成するので、それを調べて、どの式が式ツリーを構成しているかを確認できます。

たとえば、式はクエリ構文を使用した場合と同等です (または、必要に応じてメソッド呼び出し構文を使用することもできます)。

Expression<Func<IQueryable<Foo>, IQueryable<Bar>, IQueryable>> expr =
    (Foo, Bar) =>
        from foo in Foo
        join bar in Bar on foo.Foo_Id equals bar.Foo_Id into bars
        from bar in bars.DefaultIfEmpty()
        select new
        {
            Foo = foo,
            Bar = bar,
        };

あなたの質問に答えるために、匿名オブジェクトを作成する式を実際に生成することはできません。実際の型はコンパイル時にわかりません。ダミー オブジェクトを作成しGetType()てその型を取得し、それを使用して適切な新しい式を作成することで、ちょっとごまかすことができますが、それはより汚いハックであり、これを行うことはお勧めしません。そうすることで、匿名型の型がわからないため、厳密に型指定された式を生成できなくなります。

例えば、

var dummyType = new
{
    foo = default(Foo),
    bars = default(IQueryable<Bar>),
}.GetType();

var fooExpr = Expression.Parameter(typeof(Foo), "foo");
var barsExpr = Expression.Parameter(typeof(IQueryable<Bar>), "bars");
var fooProp = dummyType.GetProperty("foo");
var barsProp = dummyType.GetProperty("bars");
var ctor = dummyType.GetConstructor(new Type[]
{
    fooProp.PropertyType,
    barsProp.PropertyType,
});
var newExpr = Expression.New(
    ctor,
    new Expression[] { fooExpr, barsExpr },
    new MemberInfo[] { fooProp, barsProp }
);
// the expression type is unknown, just some lambda
var lambda = Expression.Lambda(newExpr, fooExpr, barsExpr);

匿名オブジェクトを含む式を生成する必要がある場合は常に、既知の型を作成し、それを匿名型の代わりに使用することが適切です。はい、使用は制限されますが、そのような状況を処理するためのよりクリーンな方法です。そうすれば、少なくともコンパイル時に型を取得できます。

// use this type instead of the anonymous one
public class Dummy
{
    public Foo foo { get; set; }
    public IQueryable<Bar> bars { get; set; }
}
var dummyType = typeof(Dummy);

var fooExpr = Expression.Parameter(typeof(Foo), "foo");
var barsExpr = Expression.Parameter(typeof(IQueryable<Bar>), "bars");
var fooProp = dummyType.GetProperty("foo");
var barsProp = dummyType.GetProperty("bars");
var ctor = dummyType.GetConstructor(Type.EmptyTypes);

var newExpr = Expression.MemberInit(
    Expression.New(ctor),
    Expression.Bind(fooProp, fooExpr),
    Expression.Bind(barsProp, barsExpr)
);
// lambda's type is known at compile time now
var lambda = Expression.Lambda<Func<Foo, IQueryable<Bar>, Dummy>>(
    newExpr,
    fooExpr,
    barsExpr);

または、ダミーの型を作成して使用する代わりに、代わりに式でタプルを使用できる場合があります。

static Expression<Func<T1, T2, Tuple<T1, T2>>> GetExpression<T1, T2>()
{
    var type1 = typeof(T1);
    var type2 = typeof(T2);
    var tupleType = typeof(Tuple<T1, T2>);

    var arg1Expr = Expression.Parameter(type1, "arg1");
    var arg2Expr = Expression.Parameter(type2, "arg2");
    var arg1Prop = tupleType.GetProperty("Item1");
    var arg2Prop = tupleType.GetProperty("Item2");
    var ctor = tupleType.GetConstructor(new Type[]
    {
        arg1Prop.PropertyType,
        arg2Prop.PropertyType,
    });

    var newExpr = Expression.New(
        ctor,
        new Expression[] { arg1Expr, arg2Expr },
        new MemberInfo[] { arg1Prop, arg2Prop }
    );
    // lambda's type is known at compile time now
    var lambda = Expression.Lambda<Func<T1, T2, Tuple<T1, T2>>>(
        newExpr,
        arg1Expr,
        arg2Expr);
    return lambda;
}

それを使用するには:

var expr = GetExpression<Foo, IQueryable<Bar>>();
于 2013-02-24T05:53:05.130 に答える