7

次のテーブル構造を想像してください

---------
TableA
ID
Name

---------
TableB
ID
TableAID

---------
TableC
ID
TableBID

Expression<Func<TableA, TableB, TableC, T>>これら 3 つのテーブルを結合し、 をセレクタとして受け入れる関数を定義したいと考えています。

だから私は次のようなものが欲しいです:

public IQueryable<T> GetJoinedView<T>(Expression<Func<TableA, TableB, TableC, T>> selector)
{
    return from a in DbContext.Set<TableA>()
           join b on DbContext.Set<TableB>() a.ID equals b.TableAID
           join c on DbContext.Set<TableC>() b.ID equals c.TableBID
           select selector;
}

さて、明らかに、上記は私がやりたいことをしていません。これIQueryableにより、式タイプが得られます。メソッド チェーン構文を使用することもできますが、メソッド チェーンの呼び出しごとに 1 つずつ、複数のセレクターが必要になります。次の不完全な関数のように、セレクターを取得して匿名型に適用する方法はありますか:

public IQueryable<T> GetJoinedView<T>(Expression<Func<TableA, TableB, TableC, T>> selector)
{
    var query = from a in DbContext.Set<TableA>()
                join b on DbContext.Set<TableB>() a.ID equals b.TableAID
                join c on DbContext.Set<TableC>() b.ID equals c.TableBID
                select new
                {
                    A = a, B = b, C = c
                };

    // I need the input selector to be modified to be able to operate on
    // the above anonymous type
    var resultSelector = ModifyInputSelectorToOperatorOnAnonymousType(selector);

    return query.Select(resultSelector);
}

これをどのように行うことができるかについてのアイデアはありますか?

4

3 に答える 3

14

匿名型を使用する代わりに、選択する使い捨ての中間オブジェクトを定義できます。

public class JoinedItem
{
    public TableA TableA { get; set; }
    public TableB TableB { get; set; }
    public TableC TableC { get; set; }
}

新しい方法:

public IQueryable<T> GetJoinedView<T>(Expression<Func<JoinedItem, T>> selector)
{
    return DbContext.Set<TableA>()
                    .Join(DbContext.Set<TableB>(),
                          a => a.ID,
                          b => b.TableAID,
                          (a, b) => new { A = a, B = b})
                    .Join(DbContext.Set<TableC>(),
                          ab => ab.B.ID,
                          c => c.TableBID
                          (ab, c) => new JoinedItem
                              {
                                  TableA = ab.A,
                                  TableB = ab.B,
                                  TableC = c
                              })
                     .Select(selector);
}

LINQ で直接やりたいことを表現するよりも、この方法をより明確に使用できるように、これら 3 つのテーブルを実際に結合しますか? 毎回このクエリを作成するために必要な余分な行は、この方法を使用するよりも明確であると私は主張します。

于 2013-10-28T21:48:44.133 に答える
2

したがって、私たちができることは、データを匿名オブジェクトに結合するための正確な方法から始めることです。

最初に、この単純なヘルパー クラスとメソッドから始めて、ある式のすべてのインスタンスを特定の式の別の式に置き換えることができるようにします。

public class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

次に、実際の方法について説明します。これらの匿名オブジェクトのシーケンスを 3 つのパラメーター コンストラクターを使用してマップするには、入力シーケンスを最初のパラメーターにマッピングすることを表す式と、他の 2 つのパラメーターのセレクターをメソッドに受け入れさせることができます。次に、「実際の」セレクターの本体の最初のパラメーターが最初のパラメーターのセレクターの本体である場合、すべてのインスタンスを置き換えることができます。

匿名型の型推論を可能にするために、最初にパラメーターを追加する必要があることに注意してください。

public static Expression<Func<TInput, TOutput>>
    ModifyInputSelectorToOperatorOnAnonymousType
    <TInput, TOutput, TParam1, TParam2, TParam3>(
    //this first param won't be used; 
    //it's here to allow type inference
    IQueryable<TInput> exampleParam,
    Expression<Func<TInput, TParam1>> firstSelector,
    Expression<Func<TInput, TParam2>> secondSelector,
    Expression<Func<TInput, TParam3>> thirdSelector,
    Expression<Func<TParam1, TParam2, TParam3, TOutput>> finalSelector)
{
    var parameter = Expression.Parameter(typeof(TInput), "param");

    var first = firstSelector.Body.Replace(firstSelector.Parameters.First(),
        parameter);
    var second = secondSelector.Body.Replace(secondSelector.Parameters.First(),
        parameter);
    var third = thirdSelector.Body.Replace(thirdSelector.Parameters.First(),
        parameter);

    var body = finalSelector.Body.Replace(finalSelector.Parameters[0], first)
        .Replace(finalSelector.Parameters[1], second)
        .Replace(finalSelector.Parameters[2], third);

    return Expression.Lambda<Func<TInput, TOutput>>(body, parameter);
}

これを呼び出すために、型推論を満たすためにクエリを渡すことができます。次に、匿名オブジェクトの 1 番目、2 番目、3 番目のパラメーターのセレクター、および最終的なセレクターを渡します。

var resultSelector = ModifyInputSelectorToOperatorOnAnonymousType(
    query, x => x.A, x => x.B, x => x.C, selector);

そして残りはあなたがすでに持っているものです。

于 2013-10-28T22:46:57.767 に答える
0

たぶんそれはあなたが探している解決策ではないかもしれませんが、私はそれを投稿します:

次のように、データベースに対して実行する「選択」ごとに DataModel をお勧めします。

 public class JoinedDataModel
 {
     public TableA DataA { get; set; }
     public TableB DataB { get; set; }
     public TableC DataC { get; set; }
 }

あなたの「選択」は、あなたがすでに行っているのと同じことをします

public IQueryable<JoinedDataModel> GetJoinedView( )
{
    return from a in DbContext.Set<TableA>()
           join b on DbContext.Set<TableB>() a.ID equals b.TableAID
           join c on DbContext.Set<TableC>() b.ID equals c.TableBID
           select new JoinedDataModel( )
           {
                DataA = a,
                DataB = b,
                DataC = c
           };
}

そして、あなたの「セレクター」を表す何らかの「マッパー」、または少なくともセレクターの意味を表す「マッパー」が必要です。

public static Mapper( )
{
    private static Dictionary<MapTuple, object> _maps = new Dictionary<MapTuple, object>();

    public static void AddMap<TFrom, TTo>(Action<TFrom, TTo, DateTime> map)
    {
        Mapper._maps.Add(MapTuple.Create(typeof(TFrom), typeof(TTo)), map);
    }

    public static TTo Map<TFrom, TTo>( TFrom srcObj )
    {
        var typeFrom = typeof(TFrom);
        var typeTo = typeof(TTo);
        var key = MapTuple.Create(typeFrom, typeTo);
        var map = (Action<TFrom, TTo, DateTime>) Mapper._maps[key];

        TTo targetObj = new TTo( );

        map( srcObj, targetObj );

        return targetObj;
    }

次に、少なくとも 1 つのマッピング メソッドを定義する必要があります。

AddMap<JoinedDataModel, YourResultModel>( 
    ( src, trg ) =>
    {
        trg.SomePropertyA = src.DataA.SomeProperty;
        trg.SomePropertyB = src.DataB.SomeProperty;
    }
);

次に、単に呼び出すことができます:

 public IList<YourResultModel> CallDb( )
 {
      return ( from item in GetJoinedView( )
               select Mapper.MapTo<JoinedDataModel, YourResultModel>( item ) 
             ).ToList( );
 }

メソッドに何らかのものを渡したいと思っていることは知っていますが、Expressionこれはうまくいかないと思いますが、誰かが解決策を思いついたかもしれません。

于 2013-10-28T21:48:34.603 に答える