System.LINQ.Dynamic: выберите ( "new (...)" ) в List <T> (или любую другую перечислимую коллекцию <T>)

Скажем, у меня есть DataTable с четырьмя столбцами: Company (string), Fund (string), State (string), Value (double):

    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 для создания динамического запроса, который группируется в любой компании, фонде или государстве, а затем выбирает мою группу по критериям в качестве первого столбца и sum (value):

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

В вышеприведенном запросе выбранное groupbyvalue (Group) всегда будет строкой, и сумма всегда будет двойной, поэтому я хочу, чтобы иметь возможность делать что-то вроде List, где Result - объект со свойствами Группа (строка) и TotalValue (double).

У меня много проблем с этим, может ли кто-нибудь пролить свет?

Ответы

Ответ 1

Во-первых, вы получите доступ к текущему сгруппированному значению как Key в предложении Select:

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

Это должно заставить ваш запрос работать. Более сложный вопрос заключается в том, как повернуть возвращенные объекты, которые будут иметь динамически сгенерированный тип, который наследует от DynamicClass, в статический тип.

Вариант 1: используйте отражение для доступа к свойствам динамического объекта Group и TotalValue.

Вариант 2: Используйте скомпилированные деревья выражений для генерации легкого кода для доступа к свойствам Group и TotalValue.

Вариант 3. Изменение динамической библиотеки для поддержки строго типизированного результата. Это оказывается довольно простым:

  • В ExpressionParser.Parse() запишите аргумент типа в частном поле:

    private Type newResultType;
    public Expression Parse(Type resultType)
    {
        newResultType = resultType;
        int exprPos = token.pos;
        // ...
    
  • В конце 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);
    }
    
  • Наконец, нам нужна строго типизированная версия 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)");

Ответ 2

Я отправил возвращенные данные в List <IExampleInterface> следующим образом:

1 Extend Выберите метод динамического linq для поддержки общих типов. См. Первый ответ здесь

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 Используйте Impromptu для преобразования списка в список <IExampleInterface>

var result = new List<IExampleInterface>();

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

Ответ 3

Другая возможность - расширить язык запросов DLinq с возможностью указать имя типа в предложении new в строке запроса.

Я описал необходимые изменения в Dynamic.cs в другом fooobar.com/info/267009/....

Это позволит вам задавать такие запросы, как:

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