首页 > 解决方案 > 通过使用 DataTable 创建单独的组将数据清理到 DataTable 中

问题描述

第一栏 | 日期栏 | 数据列 | 组ID | 无效数据Fg
  中文 | 2019 年 10 月 28 日 | 页眉 | 1 | 错误的
  中文 | 2019 年 10 月 28 日 | 123-10-234-3333 | 1 | 错误的
  中文 | 2019 年 10 月 28 日 | 页眉 | 2 | 错误的
  中文 | 2019 年 10 月 28 日 | 133-11-334-4444 | 2 | 错误的
  中文 | 2019 年 10 月 28 日 | 345-12-332-2233 | 2 | 错误的
  中文 | 2019 年 10 月 28 日 | 页眉 | 3 | 错误的
  中文 | 2019 年 10 月 28 日 | 123---2222 | 3 | 真的
  中文 | 2019 年 10 月 28 日 | ---2334 | 3 | 真的

我有一个包含上述字段值的表。为了创建组,我使用了 foreach 并创建了两个单独的数据表

  1. 有效数据
  2. 无效数据。

我正在使用 C# 来编写代码:

List<DataRow>  ValidData = below
第一栏 | 日期栏 | 数据列
  中文 | 2019 年 10 月 28 日 | 标题
  中文 | 2019 年 10 月 28 日 | 123-10-234-3333
  中文 | 2019 年 10 月 28 日 | 标题
  中文 | 2019 年 10 月 28 日 | 133-11-334-4444
  中文 | 2019 年 10 月 28 日 | 345-12-332-2233
List<DataRow> InvalidData  below
第一栏 | 日期栏 | 数据列
  中文 | 2019 年 10 月 28 日 | 标题
  中文 | 2019 年 10 月 28 日 | 123---2222
  中文 | 2019 年 10 月 28 日 | ---2334

由于 Details3 缺少信息。详细信息是不完整的信息,并非完全为空。

由于我的数据没有 rowid,我添加了它们可能有助于分组。

以下是代码,请您查看并帮助更好地编写此代码。我无法 dataLines.Add(group.AsEnumerable().ToList()); 错误:谢谢。

    DataTable dt = new DataTable();
    var dtable = dt.AsEnumerable().GroupBy(r => r.Field<string>("GroupId"));
                                                               
    foreach (var group in dtable)
    {
    var InvalidData = group.Where(r => r.Field<string>("InvalidDataFg") == "True").FirstOrDefault();
       if (InvalidData != null)
          invalidDataLines.Add(group.AsEnumerable().ToList());
       else
          dataLines.Add(group.AsEnumerable().ToList());
    }


标签: c#linq

解决方案


我无法 dataLines.Add(group.AsEnumerable().ToList());

您的问题的直接答案是:

List<DataRow> datalines = ...
dataLines.AddRange(group);

您要求查看代码。如果您打算在更大的项目中使用此代码更长的时间,那么我看到了几个问题。

你应该致力于你的关注点分离:你的类就像意大利面条一样交织在一起。这使得在没有其他类的情况下很难使用它们,因此很难在类似情况下重用它们。很难测试它们,因此很难更改它们,因为您无法测试更改的效果。

将您的问题分解为更小的问题:分离关注点:

  • 看起来你有三个序列:原始数据、有效数据和无效数据
  • 显然你想把这些排序到数据表中,也许你想从数据表中提取它们
  • 您想从原始数据中提取所有有效数据,您想从原始数据中提取无效数据。

因此,好的设计是将 DataTable 部分与序列中数据的提取分开。

class OriginalData  // TODO: invent a proper name
{
    public string FirstColumn {get; set;} // TODO: invent a proper name, it is not a column!
    public DateTime Date {get; set;}
    public string Data {get; set;}
    public int GroupId {get; set;}
    public bool IsValid {get; set;}
}

class ValidData // TODO: invent a proper name
{
    public string FirstColumn {get; set;}
    public DateTime Date {get; set;}
    public string Data {get; set;}
}

class InvalidData {...}

您需要表格来存储数据:

class OriginalDataTable : DataTable
{
    // Define the columns
    private DataColumn columnDate = new DataColumn(...);
    private DataColumn columnIsValid = new DataColumn(...);
    ...

    public OriginalDataTable() : base()
    {
        base.Columns.Add(columnDate);
        base.Columns.add(columnIsValid);
        ...
    }

    public OriginalDataTable(IEnumerable<OriginalData> initialData) : this()
    {
         this.AddRange(initialData);
    }

    public void Add(OriginalData data)
    {
        DataRow row = this.CreateFilledRow(data);
        base.Rows.Add(row);
    }

    public void AddRange(IEnumerable<OriginalData> data)
    {
         foreach (OriginalData item in data)
             this.Add(item);
    }

    private DataRow CreateFilledRow(OriginalData data)
    {
          DataRow row = base.NewRow();
          row[columnDate] = data.Date;
          row[columnIsValid] = data.IsValid;
          ...
          return row;
    }

    ... // Other methods
}

访问数据有两种方式:你认为这个DataTable代表一个OriginalData序列,还是给你一个OriginalData序列?换句话说:它实现IEnumerable<OriginalData>了,还是有方法IEnumerable<OriginalData> GetData()?无论您使用哪种都是口味问题:

IEnumerable<OriginalData> initialData = ...
OriginalDataTable table = new OriginalDataTable(initialData);
IEnumerable<OriginalData> dataToAdd = ...
table.AddRange(dataToAdd);

// If you implement IEnumreable<OriginalData> you can do:
foreach (OriginalData data in table) {...}

// othwerwise you do:
foreach (OriginalData data in table.GetData()) {...}

代码类似:

public IEnumerable<OriginalData> GetOriginalData()
{
    foreach (DataRow row in base.Rows)
    {
         OriginalData data = CreateFromDataRow(row);
         yield return data;
    }
}

public IEnumerator<OriginalData> GetEnumerator()
{
    return this.GetOriginalData.GetEnumerator();
}

public OriginalData CreateFromDataRow(row)
{
    return new OriginalData
    {
        IsValid = (bool)row[this.columnIsValid];
        ...
    }
}

您还需要将 OriginalData 转换为 ValidData 或 InvalidData 的过程:

public static ValidData ToValidData(this OriginalData data)
{
    return new ValidData()
    {
        ...
    }
}

当然还有一种从原始数据中提取 ValidData 的方法:

public static IEnumerable<ValidData> ToValidData(this IEnumerable<OriginalData> originalData)
{
    return originaldata.GroupBy(originaItem => originalItem.GroupId)
                       .Select(... => ToValidData(...));
}

对你的其他类和表做类似的事情。这看起来工作量很大,但你可以在 30 分钟内完成。

现在从有效和无效数据中提取很容易。将结果放在另一个表中也很容易。

IEnumerable<OriginalData> initialData = ...

// Put it in a table and add some data. Two statements only!
OriginalDataTable originalTable = new OriginalDataTable(initialData);
IEnumerable<OriginalData> dataToAdd = ...
originalTable.AddRange(dataToAdd);

// Easy to extract the data, and to convert into ValidData: One statement!
IEnumerable<ValidData> validData = originalTable.GetData().ToValidData();

// easy to put the result in a table
DataTable validDataTable = new ValidDataTable(validData);

// and easy to get the valid data items back:
foreach (ValidData validItem in ValidDataTable.GetData())
{
     ProcessValidData(validItem);
}

结论

如果您将长期使用这些表,如果您期望更改,如果您需要测试代码,最好将表与数据分开,并将数据与处理分开。

  • 小程序更容易理解
  • 这些程序更容易单独测试
  • 在其他情况下更容易重用它们。您在创建 ToInvalidData 方法时已经看到了这一点:可以重用从原始表中提取数据的代码
  • 更改数据更容易:例如,如果您想将 Boolean IsValid 的显示从 0/1 更改为测试 True/False 或 TRUE/FALSE,您所要做的就是更改表类。所有其他类和转换方法不必更改。

所有这一切都在大约 30 分钟内完成!


推荐阅读