【问题标题】:How to group datatable by unknown column names and calculate sum of one field?如何按未知列名对数据表进行分组并计算一个字段的总和?
【发布时间】:2015-05-14 13:51:26
【问题描述】:

我有一张这样的桌子:

 Name    Age  Gender  
 Sasha   12      W  
 Sasha   20      W  
 Sasha   21      M  
 Bob     21      M

我想按多个字段分组,例如[Name][Gender],然后按字段[Age] 求和。这些列在编译时是未知的,因为用户可以选择它们。

所以,在这个例子中,我想要这个:

 Name    Age  Gender  
 Sasha   32      W  
 Sasha   21      M  
 Bob     21      M

但是我不能通过 LINQ 来做,因为我不知道编译时的列。

感谢您的回答!

【问题讨论】:

标签: c# linq datatable


【解决方案1】:

如果要按多列分组,可以使用匿名类型。

var ageSumsPerNameAndGender = table.AsEnumerable()
    .GroupBy(row => new { Name = row.Field<string>("Name"), Gender = row.Field<string>("Gender") })
    .Select(group => new
    {
        Name = group.Key.Name,
        Gender = group.Key.Gender,
        SumOfAge = group.Sum(row => row.Field<int>("Age"))
    });

如果你想输出这个,你可以使用foreach-loop:

Console.WriteLine("Name Age Gender");
foreach(var x in ageSumPerNamegenders)
    Console.WriteLine("{0} {1} {2}", x.Name, x.SumOfAge, x.Gender);

根据您的 cmets,您似乎实际上不知道这些列,因为用户指定了它们。那么它就更加困难和容易出错。

一种方法是为多个字段提供自定义IEqualityComparer&lt;T&gt;。这应该有效:

public class MultiFieldComparer : IEqualityComparer<IEnumerable<object>>
{
    public bool Equals(IEnumerable<object> x, IEnumerable<object> y)
    {
        if(x == null || y == null) return false;
        return x.SequenceEqual(y);
    }

    public int GetHashCode(IEnumerable<object> objects)
    {
        if(objects == null) return 0;
        unchecked  
        {
            int hash = 17;
            foreach(object obj in objects)
                hash = hash * 23 + (obj == null ? 0 : obj.GetHashCode());
            return hash;
        }
    }
}

现在您可以将此比较器的实例用于Enumerable.GroupBy(以及许多其他 LINQ 方法)。这是一个工作示例:

List<string> columnNames = new List<string> { "Name", "Gender" };

var columnsToGroupBy = table.Columns.Cast<DataColumn>()
    .Where(c => columnNames.Contains(c.ColumnName, StringComparer.InvariantCultureIgnoreCase))
    .ToArray();
var comparer = new MultiFieldComparer();
var summed = table.AsEnumerable()
    .GroupBy(row => columnsToGroupBy.Select(c => row[c]), comparer)
    .Select(group => new
    {
        AllFields = group.Key,
        Sum = group.Sum(row => row.IsNull("Age") ? 0 : decimal.Parse(row["Age"].ToString()))
    });
foreach (var x in summed)
{
    Console.WriteLine("{0} Sum: {1}", string.Join(" ", x.AllFields), x.Sum);
}

如您所见,我已将 "Age" 硬编码为 sum-column。它必须是数字列,因此您必须确保这一点。您也可以让用户提供它。但同样,它必须可以解析为十进制,否则它不起作用。

【讨论】:

  • 我不能,因为我不能在运行时创建匿名类型 - 我不知道我需要分组多少列
  • @gado:那你的实际需求是什么?你想要“这个”(姓名年龄性别)但你不知道你有没有?
  • 这是一个例子,我的真实表可以容纳不同的列。我从用户那里得到的分组列
  • @gado:这是有问题的,因为您想对字段求和,但您甚至不知道这些字段是否存在,或者您是否可以对它们进行求和。如果您求和的列是一个字符串,您希望它做什么?所以问题是:为什么允许用户提供你无法控制的列?向他提供可用列的列表。
  • 我的情况我总是可以总结这个字段,并且所有需要的字段都存在
猜你喜欢
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 2020-08-21
  • 1970-01-01
  • 2020-07-14
  • 1970-01-01
  • 1970-01-01
  • 2012-12-31
相关资源
最近更新 更多