System.LINQ.Dynamic:选择("new(...)")到List <T>(或任何其他可枚举的<T>集合)

20 linq dynamic-linq

假设我有一个包含四列的DataTable,Company(字符串),Fund(字符串),State(字符串),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));
Run Code Online (Sandbox Code Playgroud)

我想使用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)");
Run Code Online (Sandbox Code Playgroud)

在上面的查询中,所选的groupbyvalue(Group)将始终是一个字符串,并且总和将始终为double,因此我希望能够转换为类似List的内容,其中Result是具有属性Group的对象(字符串) )和TotalValue(双).

我遇到很多麻烦,有人可以解决这个问题吗?

dah*_*byk 42

首先,您将像Key在Select子句中一样访问当前的分组值:

.Select("new (Key as Group, Sum(Value) as TotalValue)");
Run Code Online (Sandbox Code Playgroud)

这应该使您的查询工作.更难的问题是如何将返回的对象(具有继承的动态生成类型)DynamicClass转换为静态类型.

选项1:使用反射来访问动态对象GroupTotalValue属性.

选项2:使用编译的表达式树来生成轻量级代码以访问GroupTotalValue属性.

选项3:修改动态库以支持强类型结果.事实证明这很简单:

  1. ExpressionParser.Parse(),捕获私有字段中的类型参数:

    private Type newResultType;
    public Expression Parse(Type resultType)
    {
        newResultType = resultType;
        int exprPos = token.pos;
        // ...
    
    Run Code Online (Sandbox Code Playgroud)
  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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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)));
    }
    
    Run Code Online (Sandbox Code Playgroud)

    原始的唯一变化Select()是我们参考的地方TResult.

现在我们只需要一个命名类型返回:

    public class Result
    {
        public string Group { get; set; }
        public double TotalValue { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

您更新的查询将如下所示:

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