System.LINQ.Dynamic:选择(“new (...)”)到列表中(或的任何其他可枚举集合)

发布于 2024-08-05 11:19:36 字数 977 浏览 8 评论 0原文

假设我有一个包含四列的 DataTable,Company(字符串)、Fund(字符串)、State(字符串)、Value(双精度):

    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,然后选择我的 group by criteria 作为第一列,和 sum(value):

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

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

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

Say I have a DataTable with four columns, 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));

I want to use System.LINQ.Dynamic to build a dynamic query which groups on either Company, Fund, or State, and then selects my group by criteria as the first column, and sum(value):

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

In the above query, the selected groupbyvalue (Group) will always be a string, and the sum will always be a double, so I want to be able to cast into something like a List, where Result is an object with properties Group (string) and TotalValue (double).

I'm having a lot of trouble with this, can anyone shed some light?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

平生欢 2024-08-12 11:19:36

首先,您将在 Select 子句中将当前分组的值作为 Key 进行访问:

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

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

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

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

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

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

    私有类型 newResultType;
    公共表达式解析(类型结果类型)
    {
        新结果类型=结果类型;
        int exprPos = token.pos;
        // ...
    
  2. 靠近 ExpressionParser.ParseNew() 的末尾,我们将在默认为动态类型之前尝试使用 newResultType

    表达式 ParseNew()
    {
        // ...
        NextToken();
        类型类型 = newResultType ?? DynamicExpression.CreateClass(属性);
        MemberBinding[] 绑定 = new MemberBinding[properties.Count];
        for (int i = 0; i < 绑定.Length; i++)
            绑定[i] = Expression.Bind(type.GetProperty(properties[i].Name), 表达式[i]);
        返回 Expression.MemberInit(Expression.New(type), 绑定);
    }
    
  3. Finally ,我们需要 Select() 的强类型版本:

    public static IQueryable;选择(此 IQueryable 源、字符串选择器、params object[] 值)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (选择器== null) 抛出新的ArgumentNullException(“选择器”);
        LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(TResult), 选择器, 值);
        返回source.Provider.CreateQuery(
            表达式.调用(
                typeof(可查询), "选择",
                new Type[] { source.ElementType, typeof(TResult) },
                源.表达式,表达式.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)");

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)");
束缚m 2024-08-12 11:19:36

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

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

var result = new List<IExampleInterface>();

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

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);
}
滴情不沾 2024-08-12 11:19:36

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

我在另一个 stackoverflow 答案 中描述了 Dynamic.cs 中需要的更改。

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

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

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>;
谜泪 2024-08-12 11:19:36

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

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文