【问题标题】:How dynamically group by and compute aggregate sums/maximums data with LINQ in c#如何在 c# 中使用 LINQ 动态分组和计算聚合总和/最大值数据
【发布时间】:2020-07-25 23:07:35
【问题描述】:

我们有一个 API 可以处理来自不同来源(数据库、Web 服务、纯文本等)的数据,因此我们将其存储在 List 结构中。 我们有这段代码来选择其中的一些列并应用函数来获取新数据(将两列相加,...)

Func<dynamic[], object>[] leftFunctions = new Func<dynamic[], object>[this.Columns.Count];
IEnumerable<dynamic[]> returnValue = null
j = 0;
foreach (JoinDataColumn dataColum in this.Columns.OrderBy(x => x.Index))
{
     //Obtenemos para cada columna la funcion
     leftFunctions[j++] = dataColum.getLeftFunction(LeftDataQuery);
}
returnValue = (await LeftDataQuery.ExecuteAsync(parameters))
    .Select(x => leftFunctions.Select(f => f.Invoke(x)).ToArray());

在上面的代码中:LeftDataquery 是一个类,用于获取执行此操作的数据列表,并返回List&lt;object[]&gt;

await LeftDataQuery.ExecuteAsync(parameters)

LeftFuncions 是从源数据创建新数据的公式,“lefdataquery[1]+lefdataquery[2]”或简单地“lefdataquery[0]”来获取第一列的数据。

在 JoinDataColumn 类中,我对结果列表的每一列都有描述:名称、类型和聚合函数。

最后,用户可以选择要分组的列和其他列的聚合公式(sum、max、min、count distinct,...)。所以现在我有了返回值,其中包含原始源的列的子集和包含要分组的列的索引列表以及带有聚合公式的索引列表(例如,索引 1 是 SUM 等等)

public enum DataColumnAggregation { NONE = 0, SUM = 1, MAX = 2, MIN = 3 }

关于如何按此数据分组并获得 SUM/MAX/MIN 的任何想法,..

做一些测试我已经写了这段代码,但问题是 Groupby 不能使用对象数组作为参数。让 GroupBy 使用这种方法有什么想法吗?¿

    public enum DataColumnAggregation { NONE = 0, SUM = 1, MAX = 2, MIN = 3 }

    static void Main(string[] args)
    {
        List<object[]> returnValue = new List<object[]> { new object[] { "C1", 1, 2 }, new object[] { "C2", 3, 4 }, new object[] { "C1", 1, 3 } };


        DataColumnAggregation[] config = new DataColumnAggregation[] { DataColumnAggregation.NONE, DataColumnAggregation.SUM, DataColumnAggregation.MAX };
        //TODO agregar por la columna 0 , sumar la columna 1 y maximo de la columna 2 de returnvalue

        var resultado2 = returnValue.GroupBy(x => getGroupByColumns(x,config)).Select(x=>agregar(x, config));

    }

    private static object[] agregar(IGrouping<string[], object[]> x, DataColumnAggregation[] config)
    {
        List<object> result = new List<object>();
        for (int i = 0; i < config.Length; i++)
        {
            if (config[i] == DataColumnAggregation.NONE)
            {
                result.Add(x.Select(xy => xy[i]).FirstOrDefault());
            }
            if (config[i] == DataColumnAggregation.SUM)
            {
                result.Add(x.Sum(xy => Convert.ToInt32(xy[i]))); 
            }
            if (config[i] == DataColumnAggregation.MAX)
            {
                result.Add(x.Max(xy => xy[i])); 
            }
            if (config[i] == DataColumnAggregation.MIN)
            {
                result.Add(x.Min(xy => xy[i])); 
            }
        }
        return result.ToArray();
    }


    private static string[] getGroupByColumns(object[] x, DataColumnAggregation[] config)
    {
        List<string> group = new List<string>();
        for (int i = 0; i< config.Length; i++)
        {
            if (config[i] == DataColumnAggregation.NONE) group.Add(x[i].ToString());
        }
        return group.ToArray();
    }

谢谢

【问题讨论】:

  • 我认为您遗漏了很多细节。您要查询的List 在哪里? getLeftFunction 返回什么? LeftDataQuery 是什么?按列分组的参数和聚合公式在哪里? “索引列表”是什么意思?
  • 已编辑。索引列表,是一个数组,结果列表中对应索引的聚合公式
  • 您能否提供这些列表的 C# 数据类型?

标签: c# linq dynamic


【解决方案1】:

由于您尝试按string[](或更好的List&lt;string&gt;)进行分组,因此您需要一个IEqualityComparer 来传递给GroupBy 以比较元素。

public class IEnumerableSequenceEqualityComparer<T> : IEqualityComparer<IEnumerable<T>> {
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y) =>
        Object.ReferenceEquals(x, y) || (x != null && y != null && (x.SequenceEqual(y)));

    public int GetHashCode(IEnumerable<T> src) {
        // Will not throw an OverflowException
        //unchecked {
        //    return src.Where(e => e != null).Select(e => e.GetHashCode()).Aggregate(17, (a, b) => 23 * a + b);
        //}
        var hc = new HashCode();
        foreach (var v in src)
            hc.Add(v);
        return hc.ToHashCode();
    }
}

名为Make 的工厂类有助于创建比较器:

public static class Make {
    public static IEqualityComparer<IEnumerable<T>> SequenceEqualityComparer<T>() => new IEnumerableSequenceEqualityComparer<T>();
    public static IEqualityComparer<IEnumerable<T>> SequenceEqualityComparer<T>(T _) => new IEnumerableSequenceEqualityComparer<T>();
}

现在您可以使用序列相等比较器调用GroupBy

var resultado2 = returnValue.GroupBy(x => getGroupByColumns(x,config), Make.SequenceEqualityComparer<string>()).Select(x=>agregar(x, config));

注意:这会导致组 Key 成为 IEnumerable&lt;string&gt;。如果您更喜欢IList&lt;string&gt;,只需将相等比较器类和因子类中的IEnumerable 替换为IList。或者添加一个额外的 IList 版本并进行适当的名称更改。

【讨论】:

  • 似乎可行,但 HashCode 类在哪里?¿ 谢谢
  • @koes 它是 .Net Core 2.1 及更高版本的一部分。如果您不使用它,您可以使用注释掉的代码并删除 HashCode 代码。
猜你喜欢
  • 2013-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-06
  • 1970-01-01
  • 2014-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多