【问题标题】:How do I remove rows from a DataTable up to a certain date?如何从 DataTable 中删除直到某个日期的行?
【发布时间】:2020-08-23 12:13:45
【问题描述】:

我在过滤 DataTable 时遇到问题,比如 DtFromExcel。 DataTable 没有标题行,它以实际数据行开头,如下所示。

1 | 05/01/2020 Fri | ABC | XYZ | ...
2 | 05/01/2020 Fri | AAA | WKV | ...
3 | 05/02/2020 Sat | BCD | OPQ | ...
4 | 05/03/2020 Sun | CDE | RST | ...
5 | 05/03/2020 Sun | EFA | FAY | ...
6 | 05/03/2020 Sun | AXG | EAS | ...
7 | 05/04/2020 Mon | DEF | LMN | ...
8 | 05/04/2020 Mon | SXA | YTR | ...
9 | 05/05/2020 Tue | DAF | AAG | ...

第二列包含某个日期和一些额外的字符串(星期几),这些行按此日期列排序。可以有多个具有相同日期的行。

现在,我想删除日期列包含某个日期的行以及之前的任何行。例如,如果某个日期是05/04/2020,那么我需要删除直到第 8 行的所有行,以便剩余的 DataTable 看起来像

9 | 05/05/2020 Tue | DAF | AAG | ...

我的问题是,首先我不知道如何过滤没有列名的 DataTable。我考虑过在不覆盖第一个实际数据行的情况下分配标题行,但似乎这是很多工作,只是为了过滤。其次,我不确定如何使用这些条件((a)第二列包含某个日期,AND(b)任何包含该日期之前日期的行)。

private void DeleteRows(DateTime certainDate){
    DataRow[] targetRowsToDelete = dtFromExcel.Select(/* Not sure what to put in here */);
    foreach (DataRow row in targetRowsToDelete)
    {
        if (Convert.ToDateTime(row[1].ToString().Split(c" ")[0]) <= certainDate)
        DtFromExcel.Rows.Remove(row);
    }
}

我不想循环遍历整个 DataTable,因为这个过程经常发生在我的程序中。

【问题讨论】:

    标签: c# .net select datatable filtering


    【解决方案1】:

    如果您使用empty constructor 创建一个没有名称的DataColumn,文档说明...

    DataColumn 对象在创建时没有默认的 ColumnNameCaption。将其添加到DataColumnCollection 时,如果尚未为ColumnName 分配名称,则会生成默认名称("Column1""Column2" 等)。

    ...所以像这样创建和加载DataTable...

    const string Input = @"1 | 05/01/2020 Fri | ABC | XYZ | ...
    2 | 05/01/2020 Fri | AAA | WKV | ...
    3 | 05/02/2020 Sat | BCD | OPQ | ...
    4 | 05/03/2020 Sun | CDE | RST | ...
    5 | 05/03/2020 Sun | EFA | FAY | ...
    6 | 05/03/2020 Sun | AXG | EAS | ...
    7 | 05/04/2020 Mon | DEF | LMN | ...
    8 | 05/04/2020 Mon | SXA | YTR | ...
    9 | 05/05/2020 Tue | DAF | AAG | ...";
    DtFromExcel = new DataTable();
    
    for (int i = 0; i < 5; i++)
    {
        DataColumn column = new DataColumn();
        Console.WriteLine($"Column {i} has ColumnName \"{column.ColumnName}\"");
    
        DtFromExcel.Columns.Add(column);
        Console.WriteLine($"Column {i} has ColumnName \"{column.ColumnName}\"");
    }
    
    foreach (string line in Input.Split("\r\n"))
    {
        string[] fields = line.Split(" | ");
    
        DtFromExcel.Rows.Add(fields);
    }
    

    ...产生这个输出...

    第 0 列有 ColumnName ""
    第 0 列的 ColumnName 为“Column1”
    第 1 列有 ColumnName ""
    第 1 列的 ColumnName 为“Column2”
    第 2 列有 ColumnName ""
    第 2 列的 ColumnName 为“Column3”
    第 3 列有 ColumnName ""
    第 3 列的 ColumnName 为“Column4”
    第 4 列有 ColumnName ""
    第 4 列的 ColumnName "Column5"

    ...所以您始终可以使用这些默认名称。此外,仅仅因为您的输入数据未指定列/字段名称并不意味着您在将其加载到DataTable...之后就不能这样做。

    DtFromExcel.Columns[1].ColumnName = "MyDateColumn";
    

    无论哪种方式,您都会有一个已知名称,您可以通过该名称引用该列。

    至于您关于不想“循环遍历整个 DataTable”的评论,不清楚您的意思是因为附加代码还是性能影响,但即使您没有明确循环,也可能是后一点通过并测试每个DataRowSelect()will。请注意,由于您说行是按日期排序的,因此您可以利用 using LINQ 在找到搜索范围之外的日期后立即停止扫描行...

    private static DateTime GetRowDate(DataRow row) => DateTime.ParseExact(
        (string) row["MyDateColumn"], "MM/dd/yyyy ddd", null
    );
    
    private void DeleteRows(DateTime maxDate)
    {
        DataRow[] rowsToRemove = DtFromExcel.AsEnumerable()
            .TakeWhile(row => GetRowDate(row) <= maxDate)
            .ToArray();// Required to prevent "Collection was modified" exception in foreach below
    
        foreach (DataRow row in rowsToRemove)
            DtFromExcel.Rows.Remove(row);
    }
    

    如果您的行不能保证按日期排序,那么您可以将Where() 替换为TakeWhile(),它的工作原理是一样的。

    至于您最初使用DateTable.Select() 的请求,我不确定这是否可行,因为您的日期似乎存储为string,而不是DateTime,在您的DataColumn 中。我看到expression syntax 支持CONVERT() function 可以在StringDateTime 之间转换,但我无法想象它会比LINQ 更具性能或可读性,所以我不会追求除非你绝对必须这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-07
      • 2011-10-24
      • 2015-06-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多