如何将CSV文件读入.NET Datatable?

205
如何将CSV文件加载到System.Data.DataTable中,使创建的dataTable基于CSV文件?常规的ADO.net功能是否允许这样做?

37
这怎么可能是“离题”的?这是一个具体问题,有100人认为它很有用。 - Ryan
18
@Ryan:我实话告诉你... StackOverflow的版主们就像一窝毒蛇。退后吧,StackOverflow的版主们! - Ronnie Overby
23个回答

108

我一直在使用 OleDb 提供程序。但是,如果您读入具有数字值但希望将它们视为文本的行,则会出现问题。不过,您可以通过创建一个 schema.ini 文件来解决这个问题。这是我使用的方法:

// using System.Data;
// using System.Data.OleDb;
// using System.Globalization;
// using System.IO;

static DataTable GetDataTableFromCsv(string path, bool isFirstRowHeader)
{
    string header = isFirstRowHeader ? "Yes" : "No";

    string pathOnly = Path.GetDirectoryName(path);
    string fileName = Path.GetFileName(path);

    string sql = @"SELECT * FROM [" + fileName + "]";

    using(OleDbConnection connection = new OleDbConnection(
              @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathOnly + 
              ";Extended Properties=\"Text;HDR=" + header + "\""))
    using(OleDbCommand command = new OleDbCommand(sql, connection))
    using(OleDbDataAdapter adapter = new OleDbDataAdapter(command))
    {
        DataTable dataTable = new DataTable();
        dataTable.Locale = CultureInfo.CurrentCulture;
        adapter.Fill(dataTable);
        return dataTable;
    }
}

如果您读取的数据始终具有预期的列和类型集,则可以在同一文件夹中放置一个 shema.ini 文件,告诉 OleDb 提供程序有关列的信息。以下是微软文章的链接,提供有关如何构建该文件的详细信息。https://msdn.microsoft.com/zh-cn/library/ms709353(v=vs.85).aspx - Jim Scott
4
虽然这个答案可以解决问题,但我强烈建议不要这样做。你引入了一个外部依赖,可能会与同一台机器上其他安装的Office产生冲突(在本地开发环境中使用Excel吗?),这取决于已安装版本。有一些NuGet包(如ExcelDataReader、CsvHelper)以更高效、更可移植的方式实现了这一点。 - A. Murray
3
@A.Murray - 你具体是什么意思?这个使用了 System.Data.dll 中内置的 OleDb 提供程序。你不需要安装任何额外的“驱动程序”。而且在今天这个时代,如果任何 Windows 安装没有基本的 Jet 驱动程序安装,我会感到震惊。这是20世纪90年代的 CSV... - Paul Easter
3
我使用的是64位目标,因此我需要将“Microsoft.Jet.OLEDB.4.0”替换为“Provider=Microsoft.ACE.OLEDB.12.0;Data”,并在循环中将所有CSV添加到一个数据集中。其余代码对我有效。+1从我的一方。 - Nagendra Upwanshi
2021年。OleDb不是跨平台的,因此只能在Windows上使用。 - bbsimonbb
显示剩余3条评论

96

1
确实很棒。它对我来说开箱即用,甚至不需要阅读文档就能完美运行。 - smirkingman
这个能用在每行结构不同的CSV文件上吗?我有一个日志文件,其中记录了不同类型的事件,需要将它们分离到多个表中。 - gonzobrains
2
@gonzobrains - 可能不是;CSV文件的基本假设是一个基于单个列标题集合的矩形数据结构,这些标题在第一行中指定。你所拥有的似乎更通用的逗号分隔、区分数据,需要更复杂的“ETL”来从文件中解析出不同类型的对象实例(这可能包括不同DataTable的DataRows)。 - KeithS
哇,这个对我来说也是开箱即用。我尝试了Microsoft.Office.Interop.Excel、IronXL和DocumentFormat.OpenXml,它们都运行得非常慢。处理3k行需要10秒以上的时间。而这个解析器在我松开鼠标时就完成了操作!我使用了NuGet,因为那是最简单的方式。太惊讶了! - blind Skwirl

59

嗨,它正在工作 100%

  public static DataTable ConvertCSVtoDataTable(string strFilePath)
  {
    DataTable dt = new DataTable();
    using (StreamReader sr = new StreamReader(strFilePath))
    {
        string[] headers = sr.ReadLine().Split(',');
        foreach (string header in headers)
        {
            dt.Columns.Add(header);
        }
        while (!sr.EndOfStream)
        {
            string[] rows = sr.ReadLine().Split(',');
            DataRow dr = dt.NewRow();
            for (int i = 0; i < headers.Length; i++)
            {
                dr[i] = rows[i];
            }
            dt.Rows.Add(dr);
        }

    }


    return dt;
   }

CSV 图像 enter image description here

导入的数据表格 enter image description here


8
只有当输入数据全部为最简单的 CSV 文件时才能满足条件(这在您的情况下可能是成立的)。 - Ronnie Overby
你是正确的。你应该使用http://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader(Lorion dll)。我尝试过,它的效果很好。 - Shivam Srivastava
1
请查看我在2009年的回答。 - Ronnie Overby
1
@ShivamSrivastava,我在最后一行遇到了错误,你在吗?如果在的话,请给我其他联系方式。 - Sunil Acharya
4
这对我有用,非常感谢。好的部分是没有第三方动态链接库。 :) - goofyui
显示剩余2条评论

41
我决定使用Sebastien Lorion的Csv Reader
Jay Riggs的建议也是一个很好的解决方案,但我只需要Andrew Rissing的Generic Parser所提供的部分功能。
更新10/25/2010:
在我的项目中使用Sebastien Lorion的Csv Reader将近一年半后,我发现它会在解析某些我认为格式良好的CSV文件时抛出异常。
因此,我转而使用Andrew Rissing的Generic Parser,它似乎做得更好。
更新9/22/2014:
这些天,我主要使用这个扩展方法来读取分隔文本:

https://github.com/Core-Techs/Common/blob/master/CoreTechs.Common/Text/DelimitedTextExtensions.cs#L22

https://www.nuget.org/packages/CoreTechs.Common/

更新于2015年2月20日

例子:

var csv = @"Name, Age
Ronnie, 30
Mark, 40
Ace, 50";

TextReader reader = new StringReader(csv);
var table = new DataTable();
using(var it = reader.ReadCsvWithHeader().GetEnumerator())
{

    if (!it.MoveNext()) return;

    foreach (var k in it.Current.Keys)
        table.Columns.Add(k);

    do
    {
        var row = table.NewRow();
        foreach (var k in it.Current.Keys)
            row[k] = it.Current[k];
    
        table.Rows.Add(row);
    
    } while (it.MoveNext());
}

我同意Sebastien Lorien的CSV阅读器非常棒。我用它来处理大量的CSV,但我也用过Andrew's Rissing的阅读器来处理小任务,效果也很好。祝你玩得开心! - Jay Riggs
我该如何使用这些类将CSV文件加载到DATATABLE中? - Muflix
我尝试了这个,但是 it.Current.Keys 集合返回的是 "System.Linq.Enumerable+WhereSelectListIterator`2[System.Int32,System.Char]" 而不是列名。你有什么想法吗? - user3658298
你可以使用多个字符的分隔符吗? - rollsch
@JohnF 没有看到实际文件,无法给出原因。 - Ronnie Overby
显示剩余2条评论

13

我们过去一直使用Jet.OLEDB驱动程序,直到我们开始使用64位应用程序。微软没有发布也不会发布64位的Jet驱动程序。这里是一个简单的解决方案,它使用File.ReadAllLines和String.Split来读取和解析CSV文件,并手动加载一个DataTable。正如上面所提到的,它无法处理其中一个列值包含逗号的情况。我们主要用于读取自定义配置文件 - 使用CSV文件的好处是可以在Excel中编辑它们。

string CSVFilePathName = @"C:\test.csv";
string[] Lines = File.ReadAllLines(CSVFilePathName);
string[] Fields;
Fields = Lines[0].Split(new char[] { ',' });
int Cols = Fields.GetLength(0);
DataTable dt = new DataTable();
//1st row must be column names; force lower case to ensure matching later on.
for (int i = 0; i < Cols; i++)
    dt.Columns.Add(Fields[i].ToLower(), typeof(string));
DataRow Row;
for (int i = 1; i < Lines.GetLength(0); i++)
{
    Fields = Lines[i].Split(new char[] { ',' });
    Row = dt.NewRow();
    for (int f = 0; f < Cols; f++)
        Row[f] = Fields[f];
    dt.Rows.Add(Row);
}

11

您可以使用C#中的Microsoft.VisualBasic.FileIO.TextFieldParser dll来实现它。

static void Main()
        {
            string csv_file_path=@"C:\Users\Administrator\Desktop\test.csv";

            DataTable csvData = GetDataTabletFromCSVFile(csv_file_path);

            Console.WriteLine("Rows count:" + csvData.Rows.Count);

            Console.ReadLine();
        }


private static DataTable GetDataTabletFromCSVFile(string csv_file_path)
        {
            DataTable csvData = new DataTable();

            try
            {

            using(TextFieldParser csvReader = new TextFieldParser(csv_file_path))
                {
                    csvReader.SetDelimiters(new string[] { "," });
                    csvReader.HasFieldsEnclosedInQuotes = true;
                    string[] colFields = csvReader.ReadFields();
                    foreach (string column in colFields)
                    {
                        DataColumn datecolumn = new DataColumn(column);
                        datecolumn.AllowDBNull = true;
                        csvData.Columns.Add(datecolumn);
                    }

                    while (!csvReader.EndOfData)
                    {
                        string[] fieldData = csvReader.ReadFields();
                        //Making empty value as null
                        for (int i = 0; i < fieldData.Length; i++)
                        {
                            if (fieldData[i] == "")
                            {
                                fieldData[i] = null;
                            }
                        }
                        csvData.Rows.Add(fieldData);
                    }
                }
            }
            catch (Exception ex)
            {
            }
            return csvData;
        }

请不要试图重新发明CSV处理的轮子。有许多非常强大的开源替代方案可供选择。 - Mike Cole
1
谢谢Brad,关于使用TextFieldParser处理嵌入引号的有用提示。 - mattpm
这是更恰当的答案,其中有空格和描述。+1 - Bharat

8

这是我使用的代码,但您的应用程序必须在 .NET 3.5 版本上运行。

private void txtRead_Click(object sender, EventArgs e)
        {
           // var filename = @"d:\shiptest.txt";

            openFileDialog1.InitialDirectory = "d:\\";
            openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            DialogResult result = openFileDialog1.ShowDialog();
            if (result == DialogResult.OK)
            {
                if (openFileDialog1.FileName != "")
                {
                    var reader = ReadAsLines(openFileDialog1.FileName);

                    var data = new DataTable();

                    //this assume the first record is filled with the column names
                    var headers = reader.First().Split(',');
                    foreach (var header in headers)
                    {
                        data.Columns.Add(header);
                    }

                    var records = reader.Skip(1);
                    foreach (var record in records)
                    {
                        data.Rows.Add(record.Split(','));
                    }

                    dgList.DataSource = data;
                }
            }
        }

        static IEnumerable<string> ReadAsLines(string filename)
        {
            using (StreamReader reader = new StreamReader(filename))
                while (!reader.EndOfStream)
                    yield return reader.ReadLine();
        }

这基本上就是我想要呈现的内容。 - Captain Kenpachi

4

改编自ChuckBevitt先生

解决方案如下:

string CSVFilePathName = APP_PATH + "Facilities.csv";
string[] Lines = File.ReadAllLines(CSVFilePathName);
string[] Fields;
Fields = Lines[0].Split(new char[] { ',' });
int Cols = Fields.GetLength(0);
DataTable dt = new DataTable();
//1st row must be column names; force lower case to ensure matching later on.
for (int i = 0; i < Cols-1; i++)
        dt.Columns.Add(Fields[i].ToLower(), typeof(string));
DataRow Row;
for (int i = 0; i < Lines.GetLength(0)-1; i++)
{
        Fields = Lines[i].Split(new char[] { ',' });
        Row = dt.NewRow();
        for (int f = 0; f < Cols-1; f++)
                Row[f] = Fields[f];
        dt.Rows.Add(Row);
}

这样解决了内存问题,对吧?这是逐行处理,不在内存中保留,所以不应该有异常?我喜欢这种处理方式,但是File.ReadAllLines()会将所有内容保存到内存中,对吗?我认为你应该使用File.ReadLines()来避免巨大的内存缓冲区。这是一个很好的回答,但我想知道内存方面的问题。 - DtechNet

4
我找到的最佳选择,能够解决安装了不同版本办公软件或者32/64位问题,正如Chuck Bevitt所提到的,那就是FileHelpers
它可以通过NuGet添加到您的项目引用中,并提供一行代码的解决方案:
CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);

你能告诉我什么是CommonEngine吗?NuGet和NuGet.Core是一样的吗?我在引用中只找到了NuGet.Core。 - sindhu jampani
你需要的是FileHelpers。如果你使用NuGet,可以通过NuGet添加它。否则,只需将它作为程序集添加到你的项目中。CommonEngine是FileHelpers的一部分。 - Neo
1
这绝对是我遇到的最好、最简单的选择。非常感谢! - baltermia

4
    private static DataTable LoadCsvData(string refPath)
    {
        var cfg = new Configuration() { Delimiter = ",", HasHeaderRecord = true };
        var result = new DataTable();
        using (var sr = new StreamReader(refPath, Encoding.UTF8, false, 16384 * 2))
        {
            using (var rdr = new CsvReader(sr, cfg))
            using (var dataRdr = new CsvDataReader(rdr))
            {
                result.Load(dataRdr);
            }
        }
        return result;
    }

using: https://joshclose.github.io/CsvHelper/


请注意,在13版中,“Configuration”已更名为“CsvConfiguration”,以避免命名空间冲突。此答案的演示:https://dotnetfiddle.net/sdwc6i - dbc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接