在一开始,我有一个包含两列的数据表,我需要使用“ TasksId”列进行GroupBy并获取具有相同“ TaskId”的列“ Quantity”的总和。我已经通过这篇文章(Use LINQ to group data from DataTable)成功实现了这一目标,但是现在,DataTable包含更多列,我需要在groupBy之后检索所有这些列,我该如何实现呢?
我试图在LINQ中使用GroupBy多列,但我不了解它的确切工作方式以及如何与“ Quantity”列的总和一起使用。
var groupedData = from b in table.AsEnumerable()
group b by b.Field<string>("TasksId") into g
select new
{
TaskId= g.Key,
Count= g.Count(),
Sum= g.Sum(x => x.Field<int>("Quantity"))
};
现在我正在获取具有相同TaskId的TaskId和“ Quantity”字段的总和,我现在要检索的是多个列值,但仍获取“ Quantity”列的总和。
答案 0 :(得分:0)
使用多个字段更新您的论坛。以下内容应关闭:
var groupedData = from b in table.AsEnumerable()
group b by new
{
b.Field<string>("TasksId"),
b.Field<string>("Another Column")
} into g
select new
{
TaskId= g.Key,
Count= g.Count(),
Sum= g.Sum(x => x.Field<int>("Quantity"))
};
答案 1 :(得分:0)
我认为最好将您的关注点分开(请参见Separation of concerns demystified)
您应该将表与表的转换方式分开。这样,您将拥有两个表类和一个函数,以将一种类型或表行的序列转换为另一种类型的表行。
这样做的好处是,如果以后您决定以其他方式存储数据(例如,存储在数据库或CSV文件中),则转换或其他表都不必更改。如果您决定将表格的可视化方式更改为操作员,例如从表格转换为图形,也会出现这种情况。
我不知道您有什么桌子。看来您有一个包含Tasks
的表和另一个表,我们称它为具有Destinations
的表
要表示表中的行,您将需要两个类:
class Task
{
public int TaskId {get; set;}
public int Quantity {get; set;}
...
}
class Destination
{
public int TaskId {get; set;}
public int Count {get; set;}
...
}
此外,您将需要两个代表表的类。如果您想隐藏该版本实际上是一个DataTable,则您的类应聚合一个DataTable:它具有一个DataTable ,而不是它是一个DataTable 。这样,您就可以在WinForms应用程序中自由地将DataTable更改为数据库表,CSV文件或DataGridView。
无论如何,您的表需要实现IEnumerable:
// Class Tasks represents a "table" of Tasks
class Tasks : IEnumerable<Task>, IEnumerable
{
// create at construction. It has the columns of a Task:
private readonly DataTable dataTable;
public Tasks()
{
// create an empty DataTable
}
public Tasks(IEnumerable<Task> tasks)
{
// creates a DataTable filled with the tasks
}
// only if you sometimes still need the fact that this is internally still a DataTable
public DataTable DataTable {get;} => this.DataTable;
}
您比我更了解DataTables,并且知道您想对数据表做什么。您会比我更清楚是否需要添加/删除/查找等功能。考虑实施ICollection<Task>
或IList<Task>
而不是IEnumerable<Task>
为帮助您实现IEnumerable:
public IEnumerator<Task> GetEnumerator()
{
return this.GetTasks().GetEnumerator();
}
IEnumerator IEnumerator.GetEnumerator()
{
return this.GetEnumerator();
}
private IEnumerable<Task> GetTasks()
{
return this.dataTable.AsEnumerable()
.Select(row => new Task
{
TaskId = row.Field<int>("TaskId"),
Quantity = row.Field<int>("Quantity"),
...
});
}
不要做ToList()
!如果您的呼叫者只想要几行,那么转换所有行将很浪费。让您的呼叫者执行Take(4).ToList();
或他想要的任意数量的行。
如果要实现ICollection<Task>
,还必须实现Add
,Remove
,Count
之类的功能。它们将非常容易,为此使用DataTable.Rows
。该类已经实现了ICollection
,因此您只需要将项目与Task
进行相互转换
public void Add(Task task)
{
DataRow rowToAdd = new DataRow();
// fill the datarow with the values from Task
this.dataTable.Rows.add(rowToAdd);
}
现在您已经拥有Tasks
类,您的Destinations
类将非常相似。
现在,您唯一需要做的就是将Tasks
的序列转换为Destinations
的序列。
为使它看起来与LINQ非常相似,我将其编写为扩展方法。参见extension methods demystified
public static IEnumerable<Destination> ToDestinations(this IEnumerable<Task> tasks)
{
// make groups of tasks with same TaskId
return tasks.GroupBy(task => task.TaskId,
// from every found taskId, with all found Tasks with this taskId,
// make one new Destination object
(taskId, tasksWithThisTaskId) => new Destination
{
TaskId = taskId,
Count = tasksWithThisTaskId.Count(), // for Count: count all Tasks in this group
TotalQuantity = tasksWithThisTaskId // for TotalQuanty:
.Select(task => task.Quantity) // extract the quantities of all tasks
.Sum(), // and Sum them
... // other destination properties, use either taskId, or the
// tasks with this taskId
}
用法:
// suppose you have a sequence of tasks:
IEnumerable<Task> tasks = ...
// put it in a Tasks object, so you have a DataTable:
Tasks tasks = new Tasks(tasks);
// if desired we can add a new Task (=Row) to the table:
Task taskToAdd = new Task {...};
tasks.Add(taskToAdd);
// convert to Destinations:
Destinations destinations = tasks.ToDestinations(); // yes, only one line!
// do you need the DataTable?
DataTable destinationsTable = destinations.DataTable; // yes, also only one line!
由于您将关注点分开了,如果您决定在“任务”中添加或删除列,或者您确定“目的地”在内部是CSV文件,则只需很少的更改。