如何基于.net中的字符拆分将数据表行拆分为多行

时间:2019-06-08 18:47:25

标签: c# .net console-application

我有一个如下所示的数据表

Dosage  Drug            Patient
-----------------------------------
  25    Indocin         David
  50    Enebrel,Crozine Sam
  10    Hydralazine     Christoff
  21    Combivent       Janet
 100    Dilantin        Melanie

应根据逗号分割转换为如下所示的

Dosage  Drug            Patient
------------------------------
  25    Indocin         David
  50    Enebrel         Sam
  50    Crozine         Sam
  10    Hydralazine     Christoff
  21    Combivent       Janet
 100    Dilantin        Melanie

我做了以下代码,没有得到预期的结果。有人可以提出答案吗?

private static void ProcessDatatable(DataTable dt)
{
    DataTable dtnew = new DataTable();

    IEnumerable<string[]> allRowValues = dt.AsEnumerable()
                                           .Select(r => r.Field<string>(1).Split(','));

    dtnew = allRowValues.ToDataTable();
}

扩展方法:

public static DataTable ToDataTable<T>(this IEnumerable<T> collection, string tableName)
{
    DataTable tbl = ToDataTable(collection);
    tbl.TableName = tableName;

    return tbl;
}

public static DataTable ToDataTable<T>(this IEnumerable<T> collection)
{
    DataTable dt = new DataTable();

    Type t = typeof(T);

    PropertyInfo[] pia = t.GetProperties();

    // Create the columns in the DataTable
    foreach (PropertyInfo pi in pia)
    {
        dt.Columns.Add(pi.Name, pi.PropertyType);
    }

    // Populate the table
    foreach (T item in collection)
    {
        DataRow dr = dt.NewRow();

        dr.BeginEdit();

        foreach (PropertyInfo pi in pia)
        {
             dr[pi.Name] = pi.GetValue(item, null);
        }

        dr.EndEdit();

        dt.Rows.Add(dr);
    }

    return dt;
}

2 个答案:

答案 0 :(得分:2)

您认为您想要的是这个

IEnumerable<object[]> allRowValues = dataTable.AsEnumerable()
    .SelectMany(dataRow =>
        dataRow.Field<string>(1).Split(',').Select(drug => new[] { dataRow[0], drug, dataRow[2] }));

但是您真正想要的是:

IEnumerable<Record> allRowValues = dataTable.AsEnumerable()
    .Select(dataRow => new Record(dataRow))
    .SelectMany(record => record.SplitDrugs());

// ...

public class Record
{
    public int Dosage { get; }
    public string Drug { get; }
    public string Patient { get; }

    public Record(int dosage, string drug, string patient)
    {
        Dosage = dosage;
        Drug = drug;
        Patient = patient;
    }

    public Record(DataRow dataRow)
        : this((int)dataRow["Dosage"], (string)dataRow["Drug"], (string)dataRow["Patient"])
    {
    }

    public IEnumerable<Record> SplitDrugs()
    {
        return Drug.Split(',').Select(drug => new Record(Dosage, drug, Patient));
    }
}

简短的说明:您想用花哨的LINQ来解决太多的问题,比如从数据表中提取信息,逐行处理行,应用业务逻辑并将结果合并到新的数据表中。这是编写容易出错,无法读取,无法测试,不稳定和无法维护的代码的好方法。

最终将感谢您选择第二个选项的人员的不完整列表:

  • 您未来的自我
  • 您的队友
  • 您的代码审核人
  • 单元测试作者
  • 最终用户
  • 您的老师(如果有任务)
  • SO社区

在此期间,我将为您节省一些时间来调试将allRowValues(在您的情况下为IEnumerable<string[]>类型)转换回DataTable的过程。如果您认为它将包含3列,那么您错了。相反,它将包含诸如LengthLongLengthRank等列。...查看properties Array class以找出原因。

编辑

OP在另一个答案下的注释中完善了原始意图。

  

...,但是我只是发布了数据表的原型,实际上实际上有180列。我需要在逗号分隔的值分开的情况下在newRow.ItemArray中手动添加所有180列吗?更简单的方法?

是的,有一种更简单的方法。涉及泛型,您可以将使用范围扩展到此有限的用例之外:

// extension method
public static DataTable ExpandColumn<T>(this DataTable dataTable, string columnName,
    Func<T, IEnumerable<T>> expandField)
{
    var clonedDataTable = dataTable.Clone();
    var columnIndex = dataTable.Columns.IndexOf(columnName);
    var column = dataTable.Columns[columnIndex];
    foreach (var dataRow in dataTable.AsEnumerable())
    {
        var valueToExpand = dataRow.Field<T>(column);
        var expandedValues = expandField(valueToExpand);
        var originalValues = dataRow.ItemArray;
        foreach (var value in expandedValues)
        {
            originalValues[columnIndex] = value;
            clonedDataTable.Rows.Add(originalValues);
        }
    }
    return clonedDataTable;
}

// usage
var dataTableNew = dataTable.ExpandColumn<string>("Drug", drug => drug.Split(','));

上述扩展方法通过复制原始行来克隆DataTable实例,并通过为每个值应用expandField函数来扩展指定列中的值。

我仍然希望您从编辑中得到的教训中吸取教训,并对设计进行三思。

答案 1 :(得分:1)

我对C#几乎不满意,因此我不得不以老式的方式进行操作,但它确实有效。

public partial class Form1 : Form
{
    private DataTable dt;
    private DataTable dtExpanded;

    public Form1()
    {
        InitializeComponent();
        LoadTable();
        LoadExpandedTable();
    }
    //Dosage Drug            Patient
   private void LoadTable()
    {
        dt = new DataTable();
        using (SqlConnection cn = new SqlConnection("Your connection string"))
        {
            using (SqlCommand cmd = new SqlCommand("Select * From DrugDoses", cn))
            {
                cn.Open();
                dt.Load(cmd.ExecuteReader());
            }
        }
        dataGridView1.DataSource = dt;
    }
    private void LoadExpandedTable()
    {
        dtExpanded = new DataTable();
        dtExpanded.Columns.Add("Dose");
        dtExpanded.Columns.Add("Drug");
        dtExpanded.Columns.Add("Patient");
        foreach (DataRow r in dt.Rows)
        {
            string s = (string)r["Drug"];
            if(s.Contains(","))
            {
                string[] splitName = s.Split(',');
                foreach (string drug in splitName)
                {
                    DataRow newRow = dtExpanded.NewRow();
                    newRow.ItemArray = new Object[] { r["Dosage"], drug , r["Patient"]};
                    dtExpanded.Rows.Add(newRow);
                }
            }
            else
            {
                dtExpanded.Rows.Add(r.ItemArray);
            }

        }
        dataGridView2.DataSource = dtExpanded;
    }
}