【问题标题】:System.LINQ.Dynamic: Select(" new (...)") into a List<T> (or any other enumerable collection of <T>)System.LINQ.Dynamic:将 Select("new (...)") 放入 List<T>(或任何其他可枚举的 <T> 集合)
【发布时间】:2010-11-30 17:51:18
【问题描述】:

假设我有一个四列的 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));

我想使用 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(组)总是一个字符串,总和总是一个double,所以我希望能够转换成类似List的东西,其中Result是一个有属性的对象组(字符串)和 TotalValue(双精度)。

我在这方面遇到了很多麻烦,有人能解释一下吗?

【问题讨论】:

    标签: linq dynamic-linq


    【解决方案1】:

    首先,您将在 Select 子句中以 Key 的形式访问当前分组值:

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

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

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

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

    选项 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)");
    

    【讨论】:

    • 代码可以升级以支持匿名对象(它当前在尝试调用例如Select&lt;object&gt;("new (Key as MyProp")时会抛出异常):在ParseNew中,将newResultType ??替换为newResultType != null &amp;&amp; newResultType != typeof(object) ?。这样就可以拨打Select&lt;object&gt;。参见示例here
    【解决方案2】:

    另一种可能性是扩展 DLinq 的查询语言,可以在查询字符串的new 子句中指定类型名称。

    我已经在另一个 stackoverflow answer 中描述了 Dynamic.cs 中的更改需求。

    它将允许您提出如下查询:

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

    【讨论】:

      【解决方案3】:

      我通过以下方式将返回的数据转换为 List

      1 扩展动态 linq 的 Select 方法以支持泛型类型。见第一个答案here

      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 将 List 转换为 List

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

      【讨论】:

        【解决方案4】:

        另一种更简单的方法是使用 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);
        

        【讨论】:

          猜你喜欢
          • 2012-07-28
          • 1970-01-01
          • 1970-01-01
          • 2014-01-27
          • 1970-01-01
          • 1970-01-01
          • 2019-11-08
          • 1970-01-01
          • 2023-04-09
          相关资源
          最近更新 更多