Как прочитать файл CSV в .NET Datatable

170

Как я могу загрузить CSV-файл в System.Data.DataTable, создавая таблицу данных на основе CSV-файла?

Позволяет ли обычная функциональность ADO.net это?

Ронни Оверби
источник
21
Как это возможно «не по теме»? Это конкретный вопрос, и 100 человек считают его полезным
Райан
10
@Ryan: Истинно говорю вам ... Модераторы StackOverflow - это плод гадюк. Отстань от меня, модераторы StackOverflow!
Ронни Оверби

Ответы:

89

Вот отличный класс, который будет копировать данные CSV в таблицу данных, используя структуру данных для создания DataTable:

Портативный и эффективный универсальный парсер для плоских файлов

Это легко настроить и легко использовать. Я призываю вас взглянуть.

Джей Риггс
источник
Отлично действительно. Он отлично работал для меня из коробки, даже не читая документацию.
ухмылка
Будет ли это работать с файлами CSV, где каждая строка может иметь разную структуру? У меня есть файл журнала с различными типами регистрируемых событий, которые необходимо разделить на несколько таблиц.
gonzobrains
2
@gonzobrains - Вероятно, нет; основное допущение файла CSV - это прямоугольная структура данных, основанная на одном наборе заголовков столбцов, указанном в первой строке. То, что у вас есть, является более общими, разделенными запятыми, различаемыми данными, требующими более сложного «ETL» для анализа из файла в экземпляры объектов различных типов (которые могут включать в себя DataRows различных DataTables).
KeithS
93

Я использовал 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 правильно вывел схему.
Галилу
Реализация имеет смысл, но как мы имеем дело с ячейками, содержащими смешанные типы данных. Например 40С и тд?
GKED
GKED, если данные, в которых вы читаете, всегда имеют ожидаемый набор столбцов и типов, вы можете поместить в одну папку файл shema.ini, который сообщает поставщику OleDb информацию о столбцах. Вот ссылка на статью Microsoft, в которой подробно описывается, как структурировать файл. msdn.microsoft.com/en-us/library/…
Джим Скотт
4
Пока этот ответ будет работать, я настоятельно рекомендую против этого. Вы вводите внешнюю зависимость, которая может конфликтовать с другими установками офиса на том же компьютере (использовать Excel в локальной среде разработчика?), В зависимости от установленных версий. Существуют пакеты NuGet (ExcelDataReader, CsvHelper), которые делают это более эффективным и более переносимым способом.
А. Мюррей
1
@ A.Murray - Что именно вы имеете в виду? При этом используется встроенный поставщик OleDb в System.Data.dll. Вам не нужно устанавливать никаких дополнительных «драйверов». И я был бы шокирован в наши дни, если бы в любой установке Windows не был установлен основной драйвер Jet. Это CSV 1990-х ....
Пол Пасха
40

Я решил использовать Csv Reader Себастьяна Лориона .

Предложение Джея Риггса также является отличным решением, но мне просто не нужны были все функции, которые предоставляет Generic Parser Эндрю Риссинга .

ОБНОВЛЕНИЕ 25.10.2010

После использования Csv Reader Себастьяна Лориона в моем проекте в течение почти полутора лет я обнаружил, что он генерирует исключения при синтаксическом анализе некоторых CSV-файлов, которые, по моему мнению, хорошо сформированы.

Итак, я переключился на Generic Parser Эндрю Риссинга, и, похоже, дела идут намного лучше.

ОБНОВЛЕНИЕ 22.09.2014

В настоящее время я в основном использую этот метод расширения для чтения текста с разделителями:

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

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

ОБНОВЛЕНИЕ 20.02.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());
}
Ронни Оверби
источник
Я согласен, что читатель CSV Себастьяна Лориена великолепен. Я использую его для тяжелой обработки CSV, но я также использовал Rissing от Эндрю для небольших работ, и это мне хорошо послужило. Радоваться, веселиться!
Джей Риггс
Как я могу использовать эти классы для загрузки CSV в DATATABLE?
Muflix
Я пробовал это, но коллекция it.Current.Keys возвращается с "System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]", а не именем столбца. Есть мысли, почему?
user3658298
Вы можете использовать многосимвольные разделители?
катится
Нет, но я думал о том, чтобы включить это.
Ронни Оверби
32

Эй, это работает на 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 Image введите описание изображения здесь

Таблица данных Импортировано введите описание изображения здесь

Шивам Шривастава
источник
7
Только когда 100% входных данных являются простейшими из файлов CSV (что может быть правдой в вашем случае).
Ронни Оверби
Ты прав. Вы должны использовать codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll) Я попробовал его работать хорошо.
Шивам Шривастава
1
Смотрите мой ответ с 2009 года.
Ронни Оверби
1
@ShivamSrivastava Я получаю сообщение об ошибке в последнем ряду. Вы там, затем предоставьте другую контактную информацию
Сунил Ачарья,
Хотя я не использовал эту версию точно, именно на этом я решил свою проблему. Спасибо. Работает очень хорошо.
nrod
13

Мы всегда использовали драйвер Jet.OLEDB, пока не начали переходить на 64-битные приложения. 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);
}
Чак Бевитт
источник
8

это код, который я использую, но ваши приложения должны работать с чистой версией 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();
        }
Томас
источник
Это в значительной степени то, что я хотел представить.
Капитан Кенпачи
8

Вы можете достичь этого с помощью Microsoft.VisualBasic.FileIO.TextFieldParser dll в C #

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;
        }
kombsh
источник
Пожалуйста, не пытайтесь заново изобрести колесо с обработкой CSV. Есть так много отличных альтернатив с открытым исходным кодом, которые очень надежны.
Майк Коул
1
Спасибо Брэду, полезный совет относительно TextFieldParser для обработки встроенных кавычек.
Mattpm
3
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;
    }
}
Nodir
источник
3

Я наткнулся на этот фрагмент кода, который использует Linq и регулярные выражения для анализа файла CSV. Ссылочной статье уже более полутора лет, но она не нашла более точного способа анализа CSV с использованием Linq (и регулярных выражений), чем эта. Предостережение - это регулярное выражение, применяемое здесь для файлов с разделителями-запятыми (будет обнаруживать запятые внутри кавычек!) И то, что оно может не очень хорошо восприниматься в заголовках, но есть способ преодолеть их). Возьми пик:

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
Непско
источник
3

Лучший вариант, который я нашел, и он решает проблемы, когда у вас могут быть установлены разные версии Office, а также 32/64-битные проблемы, такие как упомянутый Чак Бевитт , это FileHelpers. .

Он может быть добавлен к ссылкам на ваш проект с помощью NuGet и предоставляет однострочное решение:

CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);
Нео
источник
Можете ли вы сказать, что такое CommonEngine? Есть NuGet то же самое с NuGet.Core. Я нашел только NuGet.Core в ссылках
синдху Джампани
Вам нужен FileHelpers. Если у вас есть NuGet, добавьте его с помощью NuGet. В противном случае просто добавьте его как сборку в ваш проект. CommonEngine является частью FileHelpers.
Нео
3

Для тех из вас, кто не хочет использовать внешнюю библиотеку и предпочитает не использовать 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;
    }
Smeiff
источник
3

Очень простой ответ: если у вас нет сложного 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 [].
Стиль животных
@AnimalStyle Вы правы - обновлен более надежным методом и объявлены строки
Мэтт Фаргюсон
3

Модифицировано от мистера Чака Бевитта

Рабочий раствор:

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
2

Вот решение, которое использует текстовый драйвер ODBC ADO.Net:

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.

Боб Мак
источник
2

Не могу удержаться от добавления своего собственного спина к этому. Это намного лучше и компактнее, чем то, что я использовал в прошлом.

Это решение:

  • Не зависит от драйвера базы данных или сторонней библиотеки.
  • Не сбой при повторных именах столбцов
  • Обрабатывает запятые в данных
  • Обрабатывает любой разделитель, а не только запятые (хотя это по умолчанию)

Вот что я придумал:

  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

Это зависит от метода extension ( Unique) для обработки повторяющихся имен столбцов, которые можно найти в качестве моего ответа в разделе Как добавлять уникальные числа в список строк

А вот BlankToNothingвспомогательная функция:

  Public Function BlankToNothing(ByVal Value As String) As Object 
    If String.IsNullOrEmpty(Value) Then Return Nothing
    Return Value
  End Function
toddmo
источник
2

С Cinchoo ETL - библиотеку с открытым исходным кодом, вы можете легко конвертировать файл CSV в DataTable с несколькими строками кода.

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

Для получения дополнительной информации, пожалуйста, посетите codeproject статью .

Надеюсь, поможет.

Райн
источник
2
    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
2

Я использую библиотеку под названием 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();
   }
}
Dotnetsqlcoder
источник
Это 2020 год, и это отличное решение по сравнению с некоторыми из старых ответов здесь. Он красиво упакован и использует популярную и легкую библиотеку от NuGet. И это гибко - если ваш CSV находится в памяти, просто передайте его MemoryStreamвместо пути к файлу. DataTable, который запрашивал OP, легко извлекается из DataSet следующим образом:result.Tables[0]
Tawab Wakil
1

Просто поделившись этими методами расширения, я надеюсь, что это может кому-то помочь.

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;
}
Akaize
источник
0

Используйте это, одна функция решит все проблемы запятой и цитаты:

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;
    }
Teemo
источник
0
 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
Эсат Кызылтепе
источник
0

Недавно я написал анализатор CSV для .NET, который, как я утверждаю, в настоящее время является самым быстрым из доступных в виде пакета nuget : Sylvan.Data.Csv .

Использовать эту библиотеку для загрузки DataTableочень легко.

using var tr = File.OpenText("data.csv");
using var dr = CsvDataReader.Create(tr);
var dt = new DataTable();
dt.Load(dr);

Предполагая, что ваш файл - это стандартные файлы с разделителями-запятыми и заголовками, это все, что вам нужно. Существуют также опции, позволяющие читать файлы без заголовков, использовать альтернативные разделители и т. Д.

Также можно предоставить пользовательскую схему для файла CSV, чтобы столбцы можно было обрабатывать как stringзначения, отличные от значений. Это позволит DataTableзагружать столбцы со значениями, с которыми проще работать, так как вам не придется их приводить при доступе к ним.

var schema = new TypedCsvSchema();
schema.Add(0, typeof(int));
schema.Add(1, typeof(string));
schema.Add(2, typeof(double?));
schema.Add(3, typeof(DateTime));
schema.Add(4, typeof(DateTime?));

var options = new CsvDataReaderOptions { 
    Schema = schema 
};

using var tr = GetData();
using var dr = CsvDataReader.Create(tr, options);

TypedCsvSchemaявляется реализацией, ICsvSchemaProviderкоторая предоставляет простой способ определения типов столбцов. Тем не менее, также можно предоставить пользовательские настройки, ICsvSchemaProviderесли вы хотите предоставить больше метаданных, таких как уникальность или ограниченный размер столбца и т. Д.

MarkPflug
источник