winforms 如何在C#中通过上传文件转置csv数据?

rggaifut  于 2022-12-14  发布在  C#
关注(0)|答案(1)|浏览(164)

我正在做一个桌面应用程序使用的窗口形式与C#,我想使应用程序能够转置csv数据(使列成行)当用户导入文件,如何编写代码呢?这里是我的应用程序user when upload a CSV File的屏幕截图
我已经在导入按钮中编写了代码

private void btnImport_Click(object sender, System.EventArgs e)
        {
            try
            {
                if (txtCSVFolderPath.Text == "")
                {
                    MessageBox.Show("The Folder Path TextBox cannot be empty.", "Warning");
                    return;
                }
                else if (txtCSVFilePath.Text == "")
                {
                    MessageBox.Show("The File Path TextBox cannot be empty.", "Warning");
                    return;
                }

                else
                {
                    ConnectCSV();
                    btnUpload.Enabled = true;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            { }
        }

和connectcsv()类

public DataSet ConnectCSV()
        {
            DataSet ds = new DataSet();

            string fileName = openFileDialogCSVFilePath.FileName;

            CsvReader reader = new CsvReader(fileName);

            ds = reader.RowEnumerator;
            dGridCSVdata.DataSource = ds;

            dGridCSVdata.DataMember = "TheData";
            return ds;
        }

最新消息:
所以我试着使用'StringBuilder',但没有任何效果,它有什么问题吗?或者有没有其他方法的想法?

public DataSet RowEnumerator
{
    get
    {
        if (null == __reader)
            throw new System.ApplicationException("I can't start reading without CSV input.");

        __rowno = 0;
        string sLine;
        string sNextLine;
        DataSet ds = new DataSet();
        DataTable dt = ds.Tables.Add("TheData");

        while (null != (sLine = __reader.ReadLine()))
        {

            while (rexRunOnLine.IsMatch(sLine) && null != (sNextLine = __reader.ReadLine()))
                sLine += "\n" + sNextLine;

            __rowno++;
            DataRow dr = dt.NewRow();
            string[] values = rexCsvSplitter.Split(sLine);
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = Csv.Unescape(values[i]);
                if (__rowno == 1)
                {
                    dt.Columns.Add(values[i].Trim());
                }
                else
                {
                    if (Csv.CharNotAllowes(values[i]))
                    {
                        dr[i] = values[i].Trim();
                    }

                }
            }
            if (__rowno != 1)
            {
                dt.Rows.Add(dr);
            }
        }

        StringBuilder sb = new StringBuilder();// code I add for transpose the data table

        for (int u = 0; u < dt.Columns.Count; u++)
        {
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                sb.Append(dt.Rows[i][u].ToString());

                if (i < dt.Rows.Count - 1)
                {
                    sb.Append(';');
                }
            }
            sb.AppendLine();
        }

        File.WriteAllText("C:\\Users\\Desktop\\Output.csv", sb.ToString()); 
        __reader.Close();
        return ds;
    }
}
wydwbb8l

wydwbb8l1#

与其仅仅写一些代码来做转置,我认为更有用的是走一走我是如何得到答案的,因此相当长的答案。
RowEnumerator属性中有很多内容,这使得它很难测试。因为问题主要是询问如何将一个DataTable转置到另一个DataTable,所以让我们将转置功能拉到它自己的方法中,或者更好的是它自己的类中,在那里可以隔离地测试它。注意,该类不依赖于任何特定的UI框架,这使得它更易于测试并且更易于重用。

namespace StackOverflow74612723
{
    using System.Data;

    public static class Transposer
    {
        public static DataTable Transpose(DataTable input)
        {
            throw new NotImplementedException();
        }
    }
}

写一个只抛出异常的方法有什么意义呢?答案:现在我们已经有了方法签名,我们可以为这个方法编写一些单元测试,这样我们就可以定义我们期望它如何表现。如果你想在编写被测试的代码之前了解更多关于编写单元测试的方法,你需要的搜索词是“测试驱动开发”。
接下来,在你的解决方案中添加一个XUnit单元测试项目。你不一定要使用XUnit,还有其他的单元测试框架,比如NUnit和MSTest,它们都可以做这类事情。XUnit只是我个人的喜好。如果你以前没有用过它,可以看看它的文档,特别是入门指南。
将项目指涉加入至公寓测试项目,让它指涉包含Transposer类别的项目。将下列NuGet套件加入至公寓测试项目:

  • FluentAssert
  • FluentAssertions.Analyzers
  • xunit.analyzers

(the最后两个不是必需的,但是我发现分析器非常有用,可以提供关于我是否遵循良好编码实践的反馈)。现在我们可以开始为Transposer类编写单元测试了。

namespace StackOverflow74612723.Test
{
    using System.Data;
    using FluentAssertions;
    using Xunit;

    public class TransposerTest
    {
        /// <summary>
        /// Unit test method for the Transpose method.
        /// </summary>
        /// <param name="input">DataTable to transpose.</param>
        /// <param name="expectedOutput">
        /// Expected result of transposing the input DataTable.
        /// </param>
        [Theory]
        [MemberData(nameof(TransposeTestData))]
        public void TransposeTest(DataTable input, DataTable expectedOutput)
        {
            // nothing to arrange

            // Act
            var actualOutput = Transposer.Transpose(input);

            // Assert
            actualOutput.Should().BeEquivalentTo(expectedOutput);
        }
    }
}

值得指出的是,在Assert步骤中,actualOutput(即DataTable)似乎有一个Should()方法,这实际上是FluentAssertions包中的一个扩展方法,它有许多这样的扩展方法,可以极大地简化编写复杂对象的Assert。
这还不会生成,因为它引用了一个名为TransposeTestData的属性,我们还没有创建它。此属性将为我们的单元测试方法提供参数化测试数据,以便该方法可以使用多对输入和预期输出来运行。有关XUnit中参数化测试的更多信息,请参见Andrew Lock's blog
现在我们可以将TransposeTestData属性添加到TransposerTest类中:

/// <summary>
/// Gets a list of test cases for a Theory test for the Transpose method.
/// Each element of the list is an array of two objects, the first of
/// which is the input DataTable and the second of which is the expected
/// output of transposing the input DataTable.
/// </summary>
public static IEnumerable<object[]> TransposeTestData =>
    new List<object[]>
    {
        // First test case
        new object[]
        {
            // input
            CreateDataTable(2)
                .Rows.Add("A1", "B1").Table
                .Rows.Add("A2", "B2").Table,

            // expected output
            CreateDataTable(2)
                .Rows.Add("A1", "A2").Table
                .Rows.Add("B1", "B2").Table,
        },

        // Second test case
        new object[]
        {
            // input
            CreateDataTable(3)
                .Rows.Add("A1", "B1", "C1").Table
                .Rows.Add("A2", "B2", "C2").Table,

            // expected output
            CreateDataTable(2)
                .Rows.Add("A1", "A2").Table
                .Rows.Add("B1", "B2").Table
                .Rows.Add("C1", "C2").Table,
        },

        // Third test case
        new object[]
        {
            // input
            CreateDataTable(4)
                .Rows.Add("A1", "B1", "C1", "D1").Table
                .Rows.Add("A2", "B2", "C2", "D2").Table
                .Rows.Add("A3", "B3", "C3", "D3").Table
                .Rows.Add("A4", "B4", "C4", "D4").Table
                .Rows.Add("A5", "B5", "C5", "D5").Table
                .Rows.Add("A6", "B6", "C6", "D6").Table
                .Rows.Add("A7", "B7", "C7", "D7").Table
                .Rows.Add("A8", "B8", "C8", "D8").Table,

            // expected output
            CreateDataTable(8)
            .Rows.Add("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8").Table
            .Rows.Add("B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8").Table
            .Rows.Add("C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8").Table
            .Rows.Add("D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8").Table
        },
    };

这给了我们一个测试用例列表,每个测试用例都是一个对象数组,其中数组的元素对应于TransposeTest方法的每个参数,所以对于这个测试,每个对象数组都需要两个元素,第一个是我们想要转置的DataTable,第二个是我们期望转置后的DataTable的样子。
我们还需要添加这个小的helper方法,它简化了在测试数据中创建DataTable的过程。

/// <summary>
/// Creates a new DataTable with the supplied number of columns
/// and no rows.
/// </summary>
/// <param name="numberOfColumns">Number of columns.</param>
/// <returns>The DataTable.</returns>
private static DataTable CreateDataTable(int numberOfColumns)
{
    var table = new DataTable();
    for (var i = 0; i < numberOfColumns; i++)
    {
        table.Columns.Add();
    }

    return table;
}

我们现在可以构建并运行单元测试了,当然,它们会以NotImplementedException失败,因为我们还没有实现Transpose方法。
这可能看起来需要做很多工作,但这是值得花的时间,因为我们现在有了一个单元测试,它不仅定义了我们期望Transpose方法如何工作,而且还告诉我们它是否工作正确。我们在Transpose方法中需要的逻辑很容易出错,我不介意承认我花了几次尝试才把它弄正确。实际上如果没有单元测试我可能已经放弃了。
现在我们可以在Transposer类中实现Transpose方法:

public static DataTable Transpose(DataTable input)
{
    var inputRowCount = input.Rows.Count;
    var inputColumnCount = input.Columns.Count;
    var outputRowCount = inputColumnCount;
    var outputColumnCount = inputRowCount;
    var output = new DataTable();
    for (var outputX = 0; outputX < outputColumnCount; outputX++)
    {
        output.Columns.Add();
    }

    for (var outputY = 0; outputY < outputRowCount; outputY++)
    {
        var outputRowValues = new object[outputColumnCount];
        for (var outputX = 0; outputX < outputColumnCount; outputX++)
        {
            var cellValue = input.Rows[outputX][outputY];
            outputRowValues[outputX] = cellValue;
        }

        output.Rows.Add(outputRowValues);
    }

    return output;
}

因为我们有了单元测试,我们可以运行它来检查方法是否正确运行。一旦我们对它的正确运行感到满意,我们就可以从RowEnumerator属性调用它。我们可以转置dt并将转置后的DataTable添加到ds,而不是将dt添加到DataSetds。例如,

ds.Tables.Add(Transposer.Transpose(dt));

我希望这个答案能帮助您了解如何分离代码中的关注点,如何使代码更易于测试,以及如何测试代码。

相关问题