如何在LINQ中的GroupBy之后选择列?

时间:2019-02-12 19:02:44

标签: c# linq

在一开始,我有一个包含两列的数据表,我需要使用“ 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”列的总和。

2 个答案:

答案 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>,还必须实现AddRemoveCount之类的功能。它们将非常容易,为此使用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文件,则只需很少的更改。