如何将CSV文件加载到System.Data.DataTable中,从而基于CSV文件创建数据表?

常规的ADO.net功能是否允许这样做?

评论

这怎么可能是“主题外”?这是一个特定的问题,有100人发现它有用

@Ryan:我真的对你说... StackOverflow主持人是毒蛇。跟我来,StackOverflow主持人!

#1 楼

这是一个优秀的类,可以使用数据的结构将CSV数据复制到数据表中以创建DataTable:

用于平面文件的便携式高效通用解析器

这很容易配置和易于使用。我恳请你看看。

评论


确实很棒。它即使不阅读文档也对我非常有效。

–傻笑的人
13年5月29日在10:24

这对每行可能具有不同结构的CSV文件有效吗?我有一个日志文件,其中包含不同类型的已记录事件,需要将其分为多个表。

–冈比亚人
2013年12月27日在18:18

@gonzobrains-可能不是; CSV文件的基本假设是基于第一行中指定的一组列标题的矩形数据结构。您所看到的似乎是更通用的,用逗号分隔的,可区分的数据,需要更复杂的“ ETL”才能从文件中解析为不同类型的对象实例(其中可能包括不同DataTables的DataRows)。

– KeithS
15年4月13日在21:39

#2 楼

我一直在使用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;
    }
}


评论


谢谢哥们。那对我有帮助。我有一个CSV文件,其中逗号不仅是分隔符,而且它们在许多列值中都无处不在,因此想出一个将行分隔开的正则表达式有点挑战。 OleDbProvider正确推断了架构。

–加利尤
2012年1月14日上午10:41

该实现很有意义,但是我们如何处理包含混合数据类型的单元格。例如40C等?

– GKED
2012-2-16在2:12

GKED,如果您正在读取的数据始终具有一组预期的列和类型,则可以将shema.ini文件放在同一文件夹中,该文件告诉OleDb提供程序有关列的信息。这是指向Microsoft文章的链接,该文章提供了有关如何构造文件的详细信息。 msdn.microsoft.com/zh-CN/library/…

–吉姆·斯科特(Jim Scott)
2015年10月2日在20:51

尽管此答案有效,但我强烈建议您不要这样做。您引入了一个外部依赖关系,该依赖关系可能与同一台计算机上的其他Office安装(在本地开发环境中使用Excel?)冲突,具体取决于安装的版本。那里有NuGet软件包(ExcelDataReader,CsvHelper),它们以更高效,更可移植的方式执行此操作。

– A. Murray
16年11月25日在12:00

@ A.Murray-你到底是什么意思?这将使用System.Data.dll中的内置OleDb提供程序。您不需要安装任何其他“驱动程序”。如果任何Windows安装都没有安装基本的Jet驱动程序,我将在这一时代感到震惊。这是1990年代的CSV ...

– Paul Easter
17-09-28在0:02



#3 楼

我决定使用Sebastien Lorion的Csv Reader。
Jay Riggs的建议也是一个不错的解决方案,但是我并不需要Andrew Rissing的Generic Parser提供的所有功能。
UPDATE 10/25 / 2010
在我的项目中使用Sebastien Lorion的Csv Reader一年半后,我发现在解析一些我认为格式正确的csv文件时,它会引发异常。
确实改用了安德鲁·瑞辛(Andrew Rissing)的Generic Parser,而且性能似乎要好得多。
2014年9月22日更新
这些天,我主要使用这种扩展方法来读取定界文本:
https: //github.com/Core-Techs/Common/blob/master/CoreTechs.Common/Text/DelimitedTextExtensions.cs#L22
https://www.nuget.org/packages/CoreTechs.Common/
UPDATE 2/20/2015
示例:
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's用于小型工作,因此效果很好。玩得开心!

–杰伊·里格斯(Jay Riggs)
09年6月26日在17:36

我如何使用这些类将CSV加载到DATATABLE中?

– Muflix
2015年2月19日在16:02



我试过了,但是it.Current.Keys集合返回的是“ System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32,System.Char]”,而不是列的名称。有什么想法吗?

–user3658298
2015年2月22日在13:27



可以使用多字符定界符吗?

–卷
17年1月23日在3:08

不,但是我考虑过启用它。

–罗尼·奥弗比(Ronnie Overby)
17年1月23日在19:37

#4 楼

嘿,它能正常工作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图像


数据表已导入


评论


仅当100%的输入是最简单的CSV文件时(在您的情况下可能如此)。

–罗尼·奥弗比(Ronnie Overby)
2015年1月1日,下午3:28

你是对的。您应该使用codeproject.com/Articles/9258/A-Fast-CSV-Reader(Lorion dll)我试过了。

– Shivam Srivastava
2015年1月1日在6:39



请参阅我2009年的回答。

–罗尼·奥弗比(Ronnie Overby)
2015年1月1日14:24

@ShivamSrivastava我在最后一行收到错误信息,然后再给您其他联系信息

– Sunil Acharya
16年2月13日在12:30

尽管我没有完全使用此版本,但正是基于此版本,我才解决了问题。谢谢。效果很好。

–nrod
18年11月23日在17:11

#5 楼

在开始使用64位应用程序之前,我们一直使用Jet.OLEDB驱动程序。 Microsoft还没有,也不会发布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);
}


#6 楼

这是我使用的代码,但是您的应用必须在网络版本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();
        }


评论


这几乎是我想要介绍的。

– Kenpachi上尉
13年2月12日在18:25

#7 楼

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

评论


请不要尝试通过CSV处理重新发明轮子。有很多强大的开源替代方案。

–迈克·科尔(Mike Cole)
2014年1月14日23:38

感谢Brad,这是与TextFieldParser有关处理嵌入式引号的有用技巧。

–mattpm
2014年3月6日在11:37

#8 楼

我发现的最佳选择是FileHelpers。它可以解决您可能安装了不同版本Office的问题,还提到了诸如Chuck Bevitt这样的32/64位问题。

可以将其添加到您的项目使用NuGet进行引用,它提供了一种单线解决方案:

CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);


评论


您能告诉我什么是CommonEngine吗? NuGet与NuGet.Core是否相同。我在引用中只找到NuGet.Core

–印度信德省
14年5月14日在11:51

这是您需要的FileHelpers。如果您有NuGet,请与NuGet一起添加。否则,只需将其添加为项目中的程序集即可。 CommonEngine是FileHelpers的一部分。

– Neo
2014年5月14日15:28



到目前为止,这是我遇到的最好,最简单的选择。非常感谢!

–speyck
12月15日12:16

#9 楼

由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
19年1月16日在22:52

#10 楼

public class Csv
{
    public static DataTable DataSetGet(string filename, string separatorChar, out List<string> errors)
    {
        errors = new List<string>();
        var table = new DataTable("StringLocalization");
        using (var sr = new StreamReader(filename, Encoding.Default))
        {
            string line;
            var i = 0;
            while (sr.Peek() >= 0)
            {
                try
                {
                    line = sr.ReadLine();
                    if (string.IsNullOrEmpty(line)) continue;
                    var values = line.Split(new[] {separatorChar}, StringSplitOptions.None);
                    var row = table.NewRow();
                    for (var colNum = 0; colNum < values.Length; colNum++)
                    {
                        var value = values[colNum];
                        if (i == 0)
                        {
                            table.Columns.Add(value, typeof (String));
                        }
                        else
                        {
                            row[table.Columns[colNum]] = value;
                        }
                    }
                    if (i != 0) table.Rows.Add(row);
                }
                catch(Exception ex)
                {
                    errors.Add(ex.Message);
                }
                i++;
            }
        }
        return table;
    }
}


#11 楼

我遇到了使用Linq和regex解析CSV文件的这段代码。参考文章现在已经有一年半的历史了,但是没有比这更巧妙的方法使用Linq(和regex)解析CSV了。需要注意的是,此处使用的正则表达式适用于以逗号分隔的文件(将检测引号内的逗号!),并且它可能不适用于标头,但是有一种方法可以克服这些问题)。达到顶峰:

Dim lines As String() = System.IO.File.ReadAllLines(strCustomerFile)
Dim pattern As String = ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
Dim r As System.Text.RegularExpressions.Regex = New System.Text.RegularExpressions.Regex(pattern)
Dim custs = From line In lines _
            Let data = r.Split(line) _
                Select New With {.custnmbr = data(0), _
                                 .custname = data(1)}
For Each cust In custs
    strCUSTNMBR = Replace(cust.custnmbr, Chr(34), "")
    strCUSTNAME = Replace(cust.custname, Chr(34), "")
Next


#12 楼

对于那些不想使用外部库,而又不想使用OleDB的人,请参见下面的示例。我发现的所有内容要么是OleDB,外部库,要么仅仅是基于逗号拆分!对于我的情况,OleDB无法正常工作,因此我希望有所不同。

我发现MarkJ撰写了一篇文章,其中引用了Microsoft.VisualBasic.FileIO.TextFieldParser方法,如下所示。本文是用VB编写的,并且不返回数据表,因此请参见下面的示例。

public static DataTable LoadCSV(string path, bool hasHeader)
    {
        DataTable dt = new DataTable();

        using (var MyReader = new Microsoft.VisualBasic.FileIO.TextFieldParser(path))
        {
            MyReader.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
            MyReader.Delimiters = new String[] { "," };

            string[] currentRow;

            //'Loop through all of the fields in the file.  
            //'If any lines are corrupt, report an error and continue parsing.  
            bool firstRow = true;
            while (!MyReader.EndOfData)
            {
                try
                {
                    currentRow = MyReader.ReadFields();

                    //Add the header columns
                    if (hasHeader && firstRow)
                    {
                        foreach (string c in currentRow)
                        {
                            dt.Columns.Add(c, typeof(string));
                        }

                        firstRow = false;
                        continue;
                    }

                    //Create a new row
                    DataRow dr = dt.NewRow();
                    dt.Rows.Add(dr);

                    //Loop thru the current line and fill the data out
                    for(int c = 0; c < currentRow.Count(); c++)
                    {
                        dr[c] = currentRow[c];
                    }
                }
                catch (Microsoft.VisualBasic.FileIO.MalformedLineException ex)
                {
                    //Handle the exception here
                }
            }
        }

        return dt;
    }


#13 楼

非常基本的答案:如果您没有可以使用简单拆分功能的复杂csv,则可以很好地进行导入(请注意,此导入为字符串,如果需要的话,我会在以后进行数据类型转换)

 private DataTable csvToDataTable(string fileName, char splitCharacter)
    {                
        StreamReader sr = new StreamReader(fileName);
        string myStringRow = sr.ReadLine();
        var rows = myStringRow.Split(splitCharacter);
        DataTable CsvData = new DataTable();
        foreach (string column in rows)
        {
            //creates the columns of new datatable based on first row of csv
            CsvData.Columns.Add(column);
        }
        myStringRow = sr.ReadLine();
        while (myStringRow != null)
        {
            //runs until string reader returns null and adds rows to dt 
            rows = myStringRow.Split(splitCharacter);
            CsvData.Rows.Add(rows);
            myStringRow = sr.ReadLine();
        }
        sr.Close();
        sr.Dispose();
        return CsvData;
    }


我的方法(如果我要导入带有字符串[]分隔符的表)并处理我正在读取的当前行可能已移至csv或文本文件中的下一行的问题<-在这种情况下,我想循环直到获得第一行(列)的总行数

public static DataTable ImportCSV(string fullPath, string[] sepString)
    {
        DataTable dt = new DataTable();
        using (StreamReader sr = new StreamReader(fullPath))
        {
           //stream uses using statement because it implements iDisposable
            string firstLine = sr.ReadLine();
            var headers = firstLine.Split(sepString, StringSplitOptions.None);
            foreach (var header in headers)
            {
               //create column headers
                dt.Columns.Add(header);
            }
            int columnInterval = headers.Count();
            string newLine = sr.ReadLine();
            while (newLine != null)
            {
                //loop adds each row to the datatable
                var fields = newLine.Split(sepString, StringSplitOptions.None); // csv delimiter    
                var currentLength = fields.Count();
                if (currentLength < columnInterval)
                {
                    while (currentLength < columnInterval)
                    {
                       //if the count of items in the row is less than the column row go to next line until count matches column number total
                        newLine += sr.ReadLine();
                        currentLength = newLine.Split(sepString, StringSplitOptions.None).Count();
                    }
                    fields = newLine.Split(sepString, StringSplitOptions.None);
                }
                if (currentLength > columnInterval)
                {  
                    //ideally never executes - but if csv row has too many separators, line is skipped
                    newLine = sr.ReadLine();
                    continue;
                }
                dt.Rows.Add(fields);
                newLine = sr.ReadLine();
            }
            sr.Close();
        }

        return dt;
    }


评论


很好,您只是还没有将行声明为string []。

–动物风格
15年5月8日在22:36

@AnimalStyle您说对了-使用更可靠的方法和声明的行进行了更新

–马特·法格森(Matt Farguson)
15年5月13日在19:35



#14 楼

这是一个使用ADO.Net的ODBC文本驱动程序的解决方案:

Dim csvFileFolder As String = "C:\YourFileFolder"
Dim csvFileName As String = "YourFile.csv"

'Note that the folder is specified in the connection string,
'not the file. That's specified in the SELECT query, later.
Dim connString As String = "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" _
    & csvFileFolder & ";Extended Properties=""Text;HDR=No;FMT=Delimited"""
Dim conn As New Odbc.OdbcConnection(connString)

'Open a data adapter, specifying the file name to load
Dim da As New Odbc.OdbcDataAdapter("SELECT * FROM [" & csvFileName & "]", conn)
'Then fill a data table, which can be bound to a grid
Dim dt As New DataTableda.Fill(dt)

grdCSVData.DataSource = dt


填充后,您可以对数据表的属性(如ColumnName)进行赋值,以利用其所有功能。 ADO.Net数据对象。

在VS2008中,您可以使用Linq达到相同的效果。

注意:这可能是此SO问题的重复。 >

#15 楼

无法抗拒地添加我自己的旋转。这比我过去使用的更好,更紧凑。

此解决方案:


不依赖数据库驱动程序或第三个政党图书馆。
不会在重复的列名称上失败
处理数据中的逗号
处理任何定界符,而不仅仅是逗号(尽管这是默认值)

与:

   Public Function ToDataTable(FileName As String, Optional Delimiter As String = ",") As DataTable
    ToDataTable = New DataTable
    Using TextFieldParser As New Microsoft.VisualBasic.FileIO.TextFieldParser(FileName) With
      {.HasFieldsEnclosedInQuotes = True, .TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited, .TrimWhiteSpace = True}
      With TextFieldParser
        .SetDelimiters({Delimiter})
        .ReadFields.ToList.Unique.ForEach(Sub(x) ToDataTable.Columns.Add(x))
        ToDataTable.Columns.Cast(Of DataColumn).ToList.ForEach(Sub(x) x.AllowDBNull = True)
        Do Until .EndOfData
          ToDataTable.Rows.Add(.ReadFields.Select(Function(x) Text.BlankToNothing(x)).ToArray)
        Loop
      End With
    End Using
  End Function
 


它依赖于扩展方法(Unique)处理重复列在如何将唯一数字附加到字符串列表中时,我会找到一个名字作为答案

这是BlankToNothing帮助函数:

   Public Function BlankToNothing(ByVal Value As String) As Object 
    If String.IsNullOrEmpty(Value) Then Return Nothing
    Return Value
  End Function
 


#16 楼

使用Cinchoo ETL-一个开源库,您可以使用几行代码轻松地将CSV文件转换为DataTable。

using (var p = new ChoCSVReader(** YOUR CSV FILE **)
     .WithFirstLineHeader()
    )
{
    var dt = p.AsDataTable();
}


有关更多信息,请访问codeproject文章。

希望有帮助。

#17 楼

    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;
    }


使用:https://joshclose.github.io/CsvHelper/

评论


请注意,在版本13中,Configuration被重命名为CsvConfiguration以避免名称空间冲突。这个答案的演示工作:dotnetfiddle.net/sdwc6i

–dbc
7月8日15:00

#18 楼

我使用一个名为ExcelDataReader的库,您可以在NuGet上找到它。确保同时安装ExcelDataReader和ExcelDataReader.DataSet扩展(后者提供了下面引用的必需的AsDataSet方法)。

我将所有内容封装在一个函数中,您可以将其直接复制到代码中。
给它一个CSV文件的路径,它为您提供一个表的数据集。

public static DataSet GetDataSet(string filepath)
{
   var stream = File.OpenRead(filepath);

   try
   {
       var reader = ExcelReaderFactory.CreateCsvReader(stream, new ExcelReaderConfiguration()
       {
           LeaveOpen = false
       });

       var result = reader.AsDataSet(new ExcelDataSetConfiguration()
       {
           // Gets or sets a value indicating whether to set the DataColumn.DataType 
           // property in a second pass.
           UseColumnDataType = true,

           // Gets or sets a callback to determine whether to include the current sheet
           // in the DataSet. Called once per sheet before ConfigureDataTable.
           FilterSheet = (tableReader, sheetIndex) => true,

           // Gets or sets a callback to obtain configuration options for a DataTable. 
           ConfigureDataTable = (tableReader) => new ExcelDataTableConfiguration()
           {
               // Gets or sets a value indicating the prefix of generated column names.
               EmptyColumnNamePrefix = "Column",

               // Gets or sets a value indicating whether to use a row from the 
               // data as column names.
               UseHeaderRow = true,

               // Gets or sets a callback to determine which row is the header row. 
               // Only called when UseHeaderRow = true.
               ReadHeaderRow = (rowReader) =>
               {
                   // F.ex skip the first row and use the 2nd row as column headers:
                   //rowReader.Read();
               },

               // Gets or sets a callback to determine whether to include the 
               // current row in the DataTable.
               FilterRow = (rowReader) =>
               {
                   return true;
               },

               // Gets or sets a callback to determine whether to include the specific
               // column in the DataTable. Called once per column after reading the 
               // headers.
               FilterColumn = (rowReader, columnIndex) =>
               {
                   return true;
               }
           }
       });

       return result;
   }
   catch (Exception ex)
   {
       return null;
   }
   finally
   {
       stream.Close();
       stream.Dispose();
   }
}


评论


现在是2020年,与此处的一些旧答案相比,这是一个不错的解决方案。它包装精美,并使用了NuGet中流行的轻量级库。而且它很灵活-如果您的CSV位于内存中,只需将其作为MemoryStream(而不是文件路径)传递即可。 OP要求的DataTable可以像这样从DataSet中轻松提取:result.Tables [0]

– Tawab Wakil
3月20日15:04



#19 楼

只是分享此扩展方法,希望对您有所帮助。

public static List<string> ToCSV(this DataSet ds, char separator = '|')
{
    List<string> lResult = new List<string>();

    foreach (DataTable dt in ds.Tables)
    {
        StringBuilder sb = new StringBuilder();
        IEnumerable<string> columnNames = dt.Columns.Cast<DataColumn>().
                                          Select(column => column.ColumnName);
        sb.AppendLine(string.Join(separator.ToString(), columnNames));

        foreach (DataRow row in dt.Rows)
        {
            IEnumerable<string> fields = row.ItemArray.Select(field =>
              string.Concat("\"", field.ToString().Replace("\"", "\"\""), "\""));
            sb.AppendLine(string.Join(separator.ToString(), fields));
        }

        lResult.Add(sb.ToString());
    }
    return lResult;
}

public static DataSet CSVtoDataSet(this List<string> collectionCSV, char separator = '|')
{
    var ds = new DataSet();

    foreach (var csv in collectionCSV)
    {
        var dt = new DataTable();

        var readHeader = false;
        foreach (var line in csv.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
        {
            if (!readHeader)
            {
                foreach (var c in line.Split(separator))
                    dt.Columns.Add(c);
            }
            else
            {
                dt.Rows.Add(line.Split(separator));
            }
        }

        ds.Tables.Add(dt);
    }

    return ds;
}


#20 楼

使用此功能,一个函数可以解决所有逗号问题和引号:

public static DataTable CsvToDataTable(string strFilePath)
    {

        if (File.Exists(strFilePath))
        {

            string[] Lines;
            string CSVFilePathName = strFilePath;

            Lines = File.ReadAllLines(CSVFilePathName);
            while (Lines[0].EndsWith(","))
            {
                Lines[0] = Lines[0].Remove(Lines[0].Length - 1);
            }
            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], typeof(string));
            DataRow Row;
            int rowcount = 0;
            try
            {
                string[] ToBeContinued = new string[]{};
                bool lineToBeContinued = false;
                for (int i = 1; i < Lines.GetLength(0); i++)
                {
                    if (!Lines[i].Equals(""))
                    {
                        Fields = Lines[i].Split(new char[] { ',' });
                        string temp0 = string.Join("", Fields).Replace("\"\"", "");
                        int quaotCount0 = temp0.Count(c => c == '"');
                        if (Fields.GetLength(0) < Cols || lineToBeContinued || quaotCount0 % 2 != 0)
                        {
                            if (ToBeContinued.GetLength(0) > 0)
                            {
                                ToBeContinued[ToBeContinued.Length - 1] += "\n" + Fields[0];
                                Fields = Fields.Skip(1).ToArray();
                            }
                            string[] newArray = new string[ToBeContinued.Length + Fields.Length];
                            Array.Copy(ToBeContinued, newArray, ToBeContinued.Length);
                            Array.Copy(Fields, 0, newArray, ToBeContinued.Length, Fields.Length);
                            ToBeContinued = newArray;
                            string temp = string.Join("", ToBeContinued).Replace("\"\"", "");
                            int quaotCount = temp.Count(c => c == '"');
                            if (ToBeContinued.GetLength(0) >= Cols && quaotCount % 2 == 0 )
                            {
                                Fields = ToBeContinued;
                                ToBeContinued = new string[] { };
                                lineToBeContinued = false;
                            }
                            else
                            {
                                lineToBeContinued = true;
                                continue;
                            }
                        }

                        //modified by Teemo @2016 09 13
                        //handle ',' and '"'
                        //Deserialize CSV following Excel's rule:
                        // 1: If there is commas in a field, quote the field.
                        // 2: Two consecutive quotes indicate a user's quote.

                        List<int> singleLeftquota = new List<int>();
                        List<int> singleRightquota = new List<int>();

                        //combine fileds if number of commas match
                        if (Fields.GetLength(0) > Cols) 
                        {
                            bool lastSingleQuoteIsLeft = true;
                            for (int j = 0; j < Fields.GetLength(0); j++)
                            {
                                bool leftOddquota = false;
                                bool rightOddquota = false;
                                if (Fields[j].StartsWith("\"")) 
                                {
                                    int numberOfConsecutiveQuotes = 0;
                                    foreach (char c in Fields[j]) //start with how many "
                                    {
                                        if (c == '"')
                                        {
                                            numberOfConsecutiveQuotes++;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }
                                    if (numberOfConsecutiveQuotes % 2 == 1)//start with odd number of quotes indicate system quote
                                    {
                                        leftOddquota = true;
                                    }
                                }

                                if (Fields[j].EndsWith("\""))
                                {
                                    int numberOfConsecutiveQuotes = 0;
                                    for (int jj = Fields[j].Length - 1; jj >= 0; jj--)
                                    {
                                        if (Fields[j].Substring(jj,1) == "\"") // end with how many "
                                        {
                                            numberOfConsecutiveQuotes++;
                                        }
                                        else
                                        {
                                            break;
                                        }
                                    }

                                    if (numberOfConsecutiveQuotes % 2 == 1)//end with odd number of quotes indicate system quote
                                    {
                                        rightOddquota = true;
                                    }
                                }
                                if (leftOddquota && !rightOddquota)
                                {
                                    singleLeftquota.Add(j);
                                    lastSingleQuoteIsLeft = true;
                                }
                                else if (!leftOddquota && rightOddquota)
                                {
                                    singleRightquota.Add(j);
                                    lastSingleQuoteIsLeft = false;
                                }
                                else if (Fields[j] == "\"") //only one quota in a field
                                {
                                    if (lastSingleQuoteIsLeft)
                                    {
                                        singleRightquota.Add(j);
                                    }
                                    else
                                    {
                                        singleLeftquota.Add(j);
                                    }
                                }
                            }
                            if (singleLeftquota.Count == singleRightquota.Count)
                            {
                                int insideCommas = 0;
                                for (int indexN = 0; indexN < singleLeftquota.Count; indexN++)
                                {
                                    insideCommas += singleRightquota[indexN] - singleLeftquota[indexN];
                                }
                                if (Fields.GetLength(0) - Cols >= insideCommas) //probabaly matched
                                {
                                    int validFildsCount = insideCommas + Cols; //(Fields.GetLength(0) - insideCommas) may be exceed the Cols
                                    String[] temp = new String[validFildsCount];
                                    int totalOffSet = 0;
                                    for (int iii = 0; iii < validFildsCount - totalOffSet; iii++)
                                    {
                                        bool combine = false;
                                        int storedIndex = 0;
                                        for (int iInLeft = 0; iInLeft < singleLeftquota.Count; iInLeft++)
                                        {
                                            if (iii + totalOffSet == singleLeftquota[iInLeft])
                                            {
                                                combine = true;
                                                storedIndex = iInLeft;
                                                break;
                                            }
                                        }
                                        if (combine)
                                        {
                                            int offset = singleRightquota[storedIndex] - singleLeftquota[storedIndex];
                                            for (int combineI = 0; combineI <= offset; combineI++)
                                            {
                                                temp[iii] += Fields[iii + totalOffSet + combineI] + ",";
                                            }
                                            temp[iii] = temp[iii].Remove(temp[iii].Length - 1, 1);
                                            totalOffSet += offset;
                                        }
                                        else
                                        {
                                            temp[iii] = Fields[iii + totalOffSet];
                                        }
                                    }
                                    Fields = temp;
                                }
                            }
                        }
                        Row = dt.NewRow();
                        for (int f = 0; f < Cols; f++)
                        {
                            Fields[f] = Fields[f].Replace("\"\"", "\""); //Two consecutive quotes indicate a user's quote
                            if (Fields[f].StartsWith("\""))
                            {
                                if (Fields[f].EndsWith("\""))
                                {
                                    Fields[f] = Fields[f].Remove(0, 1);
                                    if (Fields[f].Length > 0)
                                    {
                                        Fields[f] = Fields[f].Remove(Fields[f].Length - 1, 1);
                                    }
                                }
                            }
                            Row[f] = Fields[f];
                        }
                        dt.Rows.Add(Row);
                        rowcount++;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception( "row: " + (rowcount+2) + ", " + ex.Message);
            }
            //OleDbConnection connection = new OleDbConnection(string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}; Extended Properties=""text;HDR=Yes;FMT=Delimited"";", FilePath + FileName));
            //OleDbCommand command = new OleDbCommand("SELECT * FROM " + FileName, connection);
            //OleDbDataAdapter adapter = new OleDbDataAdapter(command);
            //DataTable dt = new DataTable();
            //adapter.Fill(dt);
            //adapter.Dispose();
            return dt;
        }
        else
            return null;

        //OleDbConnection connection = new OleDbConnection(string.Format(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}; Extended Properties=""text;HDR=Yes;FMT=Delimited"";", strFilePath));
        //OleDbCommand command = new OleDbCommand("SELECT * FROM " + strFileName, connection);
        //OleDbDataAdapter adapter = new OleDbDataAdapter(command);
        //DataTable dt = new DataTable();
        //adapter.Fill(dt);
        //return dt;
    }


#21 楼

 Public Function ReadCsvFileToDataTable(strFilePath As String) As DataTable
    Dim dtCsv As DataTable = New DataTable()
    Dim Fulltext As String
    Using sr As StreamReader = New StreamReader(strFilePath)
        While Not sr.EndOfStream
            Fulltext = sr.ReadToEnd().ToString()
            Dim rows As String() = Fulltext.Split(vbLf)
            For i As Integer = 0 To rows.Count() - 1 - 1
                Dim rowValues As String() = rows(i).Split(","c)
                If True Then
                    If i = 0 Then
                        For j As Integer = 0 To rowValues.Count() - 1
                            dtCsv.Columns.Add(rowValues(j))
                        Next
                    Else
                        Dim dr As DataRow = dtCsv.NewRow()
                        For k As Integer = 0 To rowValues.Count() - 1
                            dr(k) = rowValues(k).ToString()
                        Next
                        dtCsv.Rows.Add(dr)
                    End If
                End If
            Next
        End While
    End Using
    Return dtCsv
End Function


#22 楼

我最近写了一个.NET的CSV解析器,我声称它是目前最快的nuget包:Sylvan.Data.Csv。
使用此库加载DataTable非常容易。 >
using var dr = CsvDataReader.Create("data.csv");
var dt = new DataTable();
dt.Load(dr);


假设您的文件是带有标题的标准逗号分隔文件,这就是您所需要的。还有一些选项允许读取不带标题的文件,并使用其他定界符等。
还可以为CSV文件提供自定义架构,以便将列视为string值以外的值。这将允许DataTable列加载更易于使用的值,因为在访问它们时不必强制它们。
这可以通过提供ICsvSchemaProvider实现来实现,该实现公开了单一方法。 DbColumn? GetColumn(string? name, int ordinal)类型是在DbColumn中定义的抽象类型,这意味着如果您实现自己的模式提供程序,则也必须提供该实现。 DbColumn类型公开有关列的各种元数据,并且您可以选择根据需要公开尽可能多的元数据。最重要的元数据是System.Data.Common,它用于
公开类型信息的非常简单的实现,如下所示:
class TypedCsvColumn : DbColumn
{
    public TypedCsvColumn(Type type, bool allowNull)
    {
        // if you assign ColumnName here, it will override whatever is in the csv header
        this.DataType = type;
        this.AllowDBNull = allowNull;
    }
}
    
class TypedCsvSchema : ICsvSchemaProvider
{
    List<TypedCsvColumn> columns;

    public TypedCsvSchema()
    {
        this.columns = new List<TypedCsvColumn>();
    }

    public TypedCsvSchema Add(Type type, bool allowNull = false)
    {
        this.columns.Add(new TypedCsvColumn(type, allowNull));
        return this;
    }

    DbColumn? ICsvSchemaProvider.GetColumn(string? name, int ordinal)
    {
        return ordinal < columns.Count ? columns[ordinal] : null;
    }
}

要使用此实现,请执行以下操作:

var schema = new TypedCsvSchema()
    .Add(typeof(int))
    .Add(typeof(string))
    .Add(typeof(double), true)
    .Add(typeof(DateTime))
    .Add(typeof(DateTime), true);
var options = new CsvDataReaderOptions
{
    Schema = schema
};


using var dr = CsvDataReader.Create("data.csv", options);
...


评论


我无法使用您的任何代码,我想它已经过时了。您能否更新您的帖子?

–speyck
12月10日13:25

@speyck确实,它已经过时了。我在这里更新了示例。如果您有任何疑问或问题,请随时在github.com/MarkPflug/Sylvan中打开问题

– MarkPflug
12月10日下午16:31

会做的,谢谢!

–speyck
12月15日7:35