如何在c#中通过上传文件转换csv数据?



我正在使用c#的windows窗体制作桌面应用程序,我想使应用程序能够在用户导入文件时转换csv数据(使列变为行),如何为它编写代码?这是我申请的截图上传CSV文件

我已经在导入按钮

里面写了一个代码
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;
}
}

与其编写一些代码来进行转位,我认为详细介绍如何得到答案会更有用,因此给出了相当长的答案。

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包添加到单元测试项目中:

  • FluentAssertions
  • FluentAssertions。分析
  • xunit.analyzers

(最后两个不是必需的,但我发现分析程序对于提供关于我是否遵循良好编码实践的反馈非常有用)。现在我们可以开始为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包中的一个扩展方法,它有许多这样的扩展方法来极大地简化编写关于复杂对象的断言。

这个还不能构建,因为它引用了一个我们还没有创建的名为TransposeTestData的属性。该属性将为我们的单元测试方法提供参数化的测试数据,以便该方法可以使用多对输入和预期输出来运行。有关XUnit中参数化测试的更多信息,请参阅Andrew Lock的博客。

现在我们可以添加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的样子。

我们还需要添加这个小的辅助方法,它简化了在测试数据中创建DataTables。

/// <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添加到DataSetds中,我们可以将dt转置并将转置的DataTable添加到ds中,例如

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

我希望这个答案能帮助你学习如何在你的代码中分离关注点,如何使它更容易测试,以及如何测试它。