如何按未知列名对数据表进行分组并计算一个字段的总和?
How to group datatable by unknown column names and calculate sum of one field?
我有一个 table 这样的:
Name Age Gender
Sasha 12 W
Sasha 20 W
Sasha 21 M
Bob 21 M
我想按多个字段分组,例如 [Name]
和 [Gender]
,然后按字段 [Age]
求和。这些列在编译时是未知的,因为用户可以 select 它们。
所以,在这个例子中我想要这个:
Name Age Gender
Sasha 32 W
Sasha 21 M
Bob 21 M
但是我不能通过 LINQ 来完成,因为我不知道编译时的列。
感谢解答!
如果要按多列分组,可以使用匿名类型。
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);
根据您的评论,您似乎实际上并不知道这些列,因为它们是用户指定的。然后它更加困难和容易出错。
一种方法是为多个字段提供自定义 IEqualityComparer<T>
。这应该有效:
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"
硬编码为总和列。它必须是数字列,因此您必须确保这一点。您也可以让用户提供它。但同样,它必须可以解析为十进制,否则它不起作用。
我有一个 table 这样的:
Name Age Gender
Sasha 12 W
Sasha 20 W
Sasha 21 M
Bob 21 M
我想按多个字段分组,例如 [Name]
和 [Gender]
,然后按字段 [Age]
求和。这些列在编译时是未知的,因为用户可以 select 它们。
所以,在这个例子中我想要这个:
Name Age Gender
Sasha 32 W
Sasha 21 M
Bob 21 M
但是我不能通过 LINQ 来完成,因为我不知道编译时的列。
感谢解答!
如果要按多列分组,可以使用匿名类型。
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);
根据您的评论,您似乎实际上并不知道这些列,因为它们是用户指定的。然后它更加困难和容易出错。
一种方法是为多个字段提供自定义 IEqualityComparer<T>
。这应该有效:
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"
硬编码为总和列。它必须是数字列,因此您必须确保这一点。您也可以让用户提供它。但同样,它必须可以解析为十进制,否则它不起作用。