使用 Epplus 删除 Excel 中的重复行

Deleting duplicate rows in Excel using Epplus

我有一个包含多行和多列的工作表。我想删除此工作表中的所有 duplicate 行。换句话说,应该删除此屏幕截图中 highlighted 行,下面的行应上移:

结果应如下:

我正在使用以下代码片段:

List<int> rowsToDelete = new List<int>();
for (int row = 1; row <= worksheet.Dimension.End.Row; row++)
{
    string a = worksheet.Cells[row,1].Value.ToString();
    string b = worksheet.Cells[row,2].Value.ToString();
    string c = worksheet.Cells[row,3].Value.ToString();

    int i = row + 1;
    while (worksheet.Cells[i,1].Value.ToString().Equals(a) &&
           worksheet.Cells[i,2].Value.ToString().Equals(b) &&
           worksheet.Cells[i,3].Value.ToString().Equals(c))
    {
        rowsToDelete.add(i);
        i++;
    }
}
foreach (var row in rowsToDelete)
{
    worksheet.Delete(row);
}

它没有删除正确的行。我该如何解决这个问题?

这是使用 Epplus 4.5.3.3 和 .NET Framework 4.6.1

我已经用另一种方式解决了您的问题:我创建了两个额外的列,“CONCAT”和“COUNT”:

  • “CONCAT”包含公式=A2+B2+C2(直到数组末尾)
  • "COUNT" 包含公式 ==COUNTIF(D:D,D2)(也直到数组末尾)

从那时起,只需编写一个 VBA 宏,将值“E9”检查回“E2”,如果值大于 1,则删除整行。

我只能假设你误解了我对发布的 while 声明的评论…

while (worksheet.Cells[i,1].Value.ToString().Equals(a) &&
       worksheet.Cells[i,2].Value.ToString().Equals(b) &&
       worksheet.Cells[i,3].Value.ToString().Equals(c)) { …

只有当重复的行是连续的时,这才有效。例如,使用第一张发布的图片,假设有第九 (9) 行,并且在这一行中我们有“重复”单元格值“a”、“b”和“c”。因此,当 while 循环开始时,第 2 行的计算结果为 true,因为该行是第 1 行的副本。因此,行索引 2 被添加到列表中。在 while 循环的下一次迭代中,我们将添加第 3 行作为副本。但是,当我们到达第 4 行时,while 条件将计算为 false,因为第 4 行不是第 1 行的副本。因此,while 循环将“退出”并且代码将循环回到初始 for 循环以检查下一行是否重复。此时,永远不会检查第 9 行的重复项,因此将其保留为重复行。

要点是,如果其中一行不是重复行,您不想停止检查重复行。您需要继续遍历所有行,因为重复行可能在任何行上。

还应注意,避免“检查”已标记为重复的重复行可能会有所帮助。例如,使用相同的第一张图片,在第一次遍历“第一”行的行时,会将第 2 行和第 3 行添加为“重复”行。因此,当 while 循环退出并且我们循环回到下一行以检查它将是第 2 行时。但是第 2 行已经被标记为重复项,因此实际上没有必要检查该行是否有重复项.在下面的解决方案中,将检查我们正在检查的行是否已标记为重复。如果该行被标记为重复,那么我们将跳过该行。

接下来,实际删除行的最后一个 foreach 循环可能有一些问题。例如,假设要删除的行列表包含第 2、3 和 7 行。因此在 foreach 循环内...代码删除第 2 行。删除此行后,第 3 行现在是第 2 行并且第 4 行现在是第 3 行等等......因此在循环的下一次迭代中它将删除第 3 行,现在是第 2 (2) 行。我希望很清楚,以自上而下的方式删除行将行不通,因为一旦删除第一行,该行下方的所有行索引都会更改。

因此,如果我们想删除行索引列表中的适当行,那么,我们可以通过自下而上的方式删除行来完成此操作。如果我们自下而上删除行,那么我们就不必像自上而下删除行时那样担心混淆索引。

鉴于所有这些,我建议您将此问题分为两个步骤。第一步只是填充重复行的列表。请记住,由于我们将以自上而下的方式检查重复行,因此行索引列表可能不一定是有序的。例如,如果我们按照之前的建议添加重复的第 9 行,那么要删除的行索引列表将为 { 2, 3, 9, 7 }。 9 在 7 之前,因为第 9 行被发现与第 1 行重复,第 7 行被发现与第 6 行重复。这里的要点是列表可能不一定是有序的,这将创建如上所述的问题。

因此,在我们得到要删除的行索引列表后,我们将对列表进行排序。这会将列表设置为 { 2, 3, 7, 9 }。此时我们可以简单地从列表底部开始删除行,或者在下面的示例中我们将简单地反转列表,使其变为 { 9, 7, 3, 2 }。然后我们将有一个 ints 的有序列表,从高到低排列。现在 for 循环遍历列表应该不会混淆行索引。

为了提供帮助,我建议您创建一个方法,该方法接受一个打开的工作表和 return我们要删除的“未排序”行索引列表。为了简化事情,所有代码所做的就是添加重复行的行索引。遍历下面的代码,我们首先遍历工作表中的所有行。如果我们到达已被标记为重复的行,那么我们将跳过该行。

如果该行未标记为重复,则代码将启动另一个 for 循环,从下一行开始到最后一行结束。同样,如果我们到达已经标记为重复的行,那么我们将跳过该行。一旦代码遍历了所有行,我们只需 return 要删除的行索引列表。

private List<int> GetDuplicateRowsToDelete(ExcelWorksheet worksheet) {
  List<int> rowsToDelete = new List<int>();
  string a, b, c;
  for (int i = 1; i <= worksheet.Dimension.End.Row; i++) {
    if (!rowsToDelete.Contains(i)) {
      a = worksheet.Cells[i, 1].Value.ToString();
      b = worksheet.Cells[i, 2].Value.ToString();
      c = worksheet.Cells[i, 3].Value.ToString();
      for (int j = i + 1; j <= worksheet.Dimension.End.Row; j++) {
        if (!rowsToDelete.Contains(j)) {
          if (worksheet.Cells[j, 1].Value.ToString().Equals(a) &&
                 worksheet.Cells[j, 2].Value.ToString().Equals(b) &&
                 worksheet.Cells[j, 3].Value.ToString().Equals(c)) {
            rowsToDelete.Add(j);
          }
        }
      }
    }
  }
  return rowsToDelete;
}

最后我们可以利用这个方法获取要删除的索引,然后我们将列表排序和反转,然后从下往上删除行。像……

private void button1_Click(object sender, EventArgs e) {
  FileInfo newFile = new FileInfo(@"D:\Test\Excel_Test\RemoveDup1.xlsx");
  using (ExcelPackage pck = new ExcelPackage(newFile)) {
    using (ExcelWorksheet worksheet = pck.Workbook.Worksheets[0]) {
      List<int> rowsToDel = GetDuplicateRowsToDelete(worksheet);
      rowsToDel.Sort();
      rowsToDel.Reverse();
      foreach (int rowIndex in rowsToDel) {
        worksheet.DeleteRow(rowIndex);
      }
      pck.Save();
    }
  }
  MessageBox.Show("Removed duplicates complete");
}

我希望这是有道理的并且有所帮助。