System.LINQ.Dynamic: Select(“ new (…)”) into a List<T> (or any other enumerable collection of <T>)

本小妞迷上赌 提交于 2019-11-26 15:57:42

First, you'll access the current grouped value as Key in your Select clause:

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

That should make your query work. The harder question is how to turn the returned objects, which will have a dynamically generated type that inherits from DynamicClass, into a static type.

Option 1: Use reflection to access the dynamic object's Group and TotalValue properties.

Option 2: Use compiled expression trees for lightweight code generation to access the Group and TotalValue properties.

Option 3: Modify the Dynamic library to support a strongly-typed result. This turns out to be rather simple:

  1. In ExpressionParser.Parse(), capture the type argument in a private field:

    private Type newResultType;
    public Expression Parse(Type resultType)
    {
        newResultType = resultType;
        int exprPos = token.pos;
        // ...
    
  2. Near the end of ExpressionParser.ParseNew(), we'll try to use newResultType before defaulting to a dynamic type:

    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. Finally, we need a strongly typed version of 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)));
    }
    

    The only changes from the original Select() are places we reference TResult.

Now we just need a named type to return:

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

And your updated query will look like this:

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

I casted returned data to List<IExampleInterface> by the following way:

1 Extend Select method of dynamic linq to support generic types. See first answer here

2 Use the Select method (very similar to first line of dahlbyk's answer)

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

If you need multiple columns, you can use following:

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

3 Convert data to list.

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

4 Use Impromptu to convert List to List<IExampleInterface>

var result = new List<IExampleInterface>();

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

Another possibility is to extend query language of DLinq with the possibility to specify the type name in the new clause in query string.

I have described the changes need in Dynamic.cs in another stackoverflow answer.

It will allow you to pose queries like the following:

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

Another, easier, way to do that is to use Json serializing/deserializing using the Newtonsoft.Json like this:

Have a class with the wanted properties:

public class MyClass
{
    public string Group;

    public double Total;
}

After your query, you Json-serialize then deserialize to the data type you want:

string jsonData = JsonConvert.SerializeObject(q1);
List<MyClass> typedResult = JsonConvert.DeserializeObject<List<MyClass>>(jsonData);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!