Як прочитати файл CSV у .NET Datatable


170

Як я можу завантажити файл CSV в System.Data.DataTable , створюючи таблицю даних на основі файлу CSV?

Чи дозволяє звичайний функціонал ADO.net це?


21
Як це можливо "поза темою"? Це конкретне запитання, і 100 людей вважають його корисним
Ryan

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, які, на мою думку, добре сформовані.

Отже, я перейшов до «Загального аналізатора» Ендрю Ріссінга і, здається, це набагато краще.

ОНОВЛЕННЯ 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, але я також використовував Ендрюс Риссінг для невеликих робочих місць, і він мені добре служив. Веселіться!
Jay Riggs

Як я можу використовувати ці класи для завантаження CSV у DATATABLE?
Муфлікс

Я спробував це, але колекція it.Current.Keys повертається з "System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]", а не з назвою стовпця. Будь-які думки, чому?
користувач3658298

Чи можете ви використовувати роздільники з кількома символами?
котиться

Ні, але я думав про те, щоб це зробити.
Ronnie Overby

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 введіть тут опис зображення

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


7
Тільки тоді, коли 100% входів є найпростішими файлами CSV (що може бути правдою у вашому випадку).
Ронні Овербі

Ви праві. ви повинні використовувати codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll), я спробував його добре працювати.
Шивам Срівастава

1
Дивіться мою відповідь від 2009 р.
Ронні Овербі

1
@ShivamSrivastava Я отримую помилку в останньому рядку, чи ти там, то дай іншу контактну інформацію
Sunil Acharya

Хоча я не користувався цією версією точно, саме на цій основі я вирішив свою проблему. Дякую. Працює дуже добре.
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;
        }

Будь ласка, не намагайтеся знову винаходити колесо з обробкою CSV. Є так багато чудових альтернатив з відкритим кодом, які є дуже надійними.
Майк Коул

1
Дякую Бред, корисна порада стосовно TextFieldParser для обробки вбудованих цитат.
матчпм

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

3

Я натрапив на цей фрагмент коду, який використовує Linq та regex для розбору файлу 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 у посиланнях
sindhu jampani

Це вам потрібні 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;
    }

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

Приємно, що ви просто ще не оголосили рядки рядком [].
Тваринний стиль

@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 для досягнення того ж ефекту.

ПРИМІТКА. Це може бути дублікат цього запитання про так.


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

Це залежить від способу розширення ( Unique) для обробки дублікатів імен стовпців, які можна знайти як моя відповідь у розділі Як додати унікальні номери до списку рядків

І ось BlankToNothingфункція помічника:

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

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

Настає 2020 рік, і це чудове рішення порівняно з деякими старими відповідями тут. Він чудово упакований і використовує популярну та легку бібліотеку від NuGet. І це гнучко - якщо ваш CSV є в пам'яті, просто передайте його як MemoryStreamшлях до файлу. Таблиця даних, яку запитувала ОП, легко витягується із набору даних так: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;
}

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

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коли ви хочете надати більше метаданих, таких як унікальність або обмежений розмір стовпця тощо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.