web-dev-qa-db-ja.com

System.LINQ.Dynamic:Select( "new(...)")into List <T>(またはその他の列挙可能な<T>のコレクション)

Company(string)、Fund(string)、State(string)、Value(double)の4つの列を持つDataTableがあるとします。

    table1.Rows.Add("Company 1","Fund 1","NY",100));
    table1.Rows.Add("Company 2","Fund 1","CA",200));
    table1.Rows.Add("Company 3","Fund 1","FL",300));
    table1.Rows.Add("Company 4","Fund 2","CA",400));
    table1.Rows.Add("Company 5","Fund 1","NY",500));
    table1.Rows.Add("Company 6","Fund 2","CA",600));
    table1.Rows.Add("Company 7","Fund 3","FL",700));

System.LINQ.Dynamicを使用して、Company、Fund、またはStateのいずれかでグループ化する動的クエリを作成し、最初の列として基準によってグループを選択し、sum(value):

string groupbyvalue="Fund";
var q1= table1.AsEnumerable().AsQueryable()
              .GroupBy(groupbyvalue,"it")
              .Select("new ("+groupbyvalue+" as Group, Sum(Value) as TotalValue)");

上記のクエリでは、選択されたgroupbyvalue(Group)は常に文字列であり、合計は常にdoubleになるため、結果がプロパティGroup(string)を持つオブジェクトであるListのようなものにキャストできるようにしたい)およびTotalValue(double)。

私はこれで多くの問題を抱えています、誰かがいくつかの光を当てることができますか?

20
Brad

まず、Select句で現在のグループ化された値にKeyとしてアクセスします。

_.Select("new (Key as Group, Sum(Value) as TotalValue)");
_

これでクエリが機能するはずです。より難しい問題は、DynamicClassから継承する動的に生成された型を持つ返されたオブジェクトを静的型に変換する方法です。

オプション1:リフレクションを使用して動的オブジェクトのGroupおよびTotalValueプロパティにアクセスします。

オプション2:GroupおよびTotalValueプロパティにアクセスするための軽量コード生成に、コンパイルされた式ツリーを使用します。

オプション3:強く型付けされた結果をサポートするようにダイナミックライブラリを変更します。これはかなり単純であることがわかります。

  1. ExpressionParser.Parse()で、タイプ引数をプライベートフィールドにキャプチャします。

    _private Type newResultType;
    public Expression Parse(Type resultType)
    {
        newResultType = resultType;
        int exprPos = token.pos;
        // ...
    _
  2. ExpressionParser.ParseNew()の終わり近くで、動的タイプにデフォルト設定する前にnewResultTypeを使用しようとします。

    _Expression ParseNew()
    {
        // ...
        NextToken();
        Type type = newResultType ?? DynamicExpression.CreateClass(properties);
        MemberBinding[] bindings = new MemberBinding[properties.Count];
        for (int i = 0; i < bindings.Length; i++)
            bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
        return Expression.MemberInit(Expression.New(type), bindings);
    }
    _
  3. 最後に、強く型付けされたバージョンのSelect()が必要です。

    _public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (selector == null) throw new ArgumentNullException("selector");
        LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(TResult), selector, values);
        return source.Provider.CreateQuery<TResult>(
            Expression.Call(
                typeof(Queryable), "Select",
                new Type[] { source.ElementType, typeof(TResult) },
                source.Expression, Expression.Quote(lambda)));
    }
    _

    元のSelect()からの唯一の変更は、TResultを参照する場所です。

ここで、返す名前付き型が必要です。

_    public class Result
    {
        public string Group { get; set; }
        public double TotalValue { get; set; }
    }
_

更新されたクエリは次のようになります。

_    IQueryable<Result> res = table1.AsQueryable()
        .GroupBy(groupbyvalue, "it")
        .Select<Result>("new (Key as Group, Sum(Value) as TotalValue)");
_
42
dahlbyk

次の方法で、返されたデータをList <IExampleInterface>にキャストしました。

1ジェネリック型をサポートするために動的linqのSelectメソッドを拡張します。最初の答えを参照してください ここ

2 Selectメソッドを使用します(dahlbykの回答の最初の行と非常によく似ています)

Select<dynamic>("new (Key as Group, Sum(Value) as TotalValue)")

複数の列が必要な場合は、次を使用できます。

GroupBy(@"new (Fund, Column1, Column2)", "it").
Select<dynamic>("new (Key.Fund, Key.Column1, Key.Column2, Sum(Value) as TotalValue)")

3データをリストに変換します。

var data = ...
  GroupBy("...").Select<dynamic>("...").
  Cast<dynamic>().AsEnumerable().ToList();

4 Imprompt を使用して、ListをList <IExampleInterface>に変換します

var result = new List<IExampleInterface>();

foreach (var d in data)
{
  dynamic r = Impromptu.ActLike<IExampleInterface>(d);
  result.Add(r);
}
3
Roman O

もう1つの可能性は、DLinqのクエリ言語を拡張して、クエリ文字列のnew句でタイプ名を指定できるようにすることです。

別の stackoverflow answerDynamic.csで必要な変更について説明しました。

これにより、次のようなクエリを実行できます。

IQueryable<Result> res 
    = table1.AsQueryable()
    .GroupBy(groupbyvalue, "it")
    .Select("new Result(Key as Group, Sum(Value) as TotalValue)")
    as IQueryable<Result>;
1
Krizz

これを行うもう1つの簡単な方法は、次のようにNewtonsoft.Jsonを使用してJsonのシリアル化/逆シリアル化を使用することです。

必要なプロパティを持つクラスがあります。

public class MyClass
{
    public string Group;

    public double Total;
}

クエリの後、Jsonでシリアル化し、必要なデータ型に逆シリアル化します。

string jsonData = JsonConvert.SerializeObject(q1);
List<MyClass> typedResult = JsonConvert.DeserializeObject<List<MyClass>>(jsonData);
0
FyCyka LK