Jak odczytać plik CSV do .NET Datatable

170

Jak mogę załadować plik CSV do System.Data.DataTable, tworząc datatable na podstawie pliku CSV?

Czy pozwala na to zwykła funkcjonalność ADO.net?

Ronnie Overby
źródło
21
Jak to może być „nie na temat”? To konkretne pytanie i 100 osób uważa je za przydatne
Ryan,
10
@Ryan: Zaprawdę, mówię wam ... Moderatorzy StackOverflow to wylęg żmij. Zostańcie za mną, moderatorzy StackOverflow!
Ronnie Overby,

Odpowiedzi:

89

Oto doskonała klasa, która skopiuje dane CSV do datatable przy użyciu struktury danych do utworzenia DataTable:

Przenośny i wydajny ogólny parser dla plików płaskich

Jest łatwy w konfiguracji i obsłudze. Zachęcam do obejrzenia.

Jay Riggs
źródło
Rzeczywiście doskonałe. U mnie zadziałało idealnie po wyjęciu z pudełka, nawet bez czytania dokumentacji.
smirkingman
Czy to zadziała w przypadku plików CSV, w których każdy wiersz może mieć inną strukturę? Mam plik dziennika z różnymi typami zarejestrowanych zdarzeń, które musiałyby zostać rozdzielone na wiele tabel.
gonzobrains
2
@gonzobrains - prawdopodobnie nie; podstawowym założeniem pliku CSV jest prostokątna struktura danych oparta na jednym zestawie nagłówków kolumn określonych w pierwszym wierszu. To, co masz, wydaje się być bardziej ogólnymi, oddzielonymi przecinkami danymi, wymagającymi bardziej wyrafinowanego „ETL” do przeanalizowania z pliku na instancje obiektów różnych typów (które mogą obejmować DataRows różnych DataTables).
KeithS
93

Korzystam z OleDbdostawcy. Jednak występują problemy, jeśli czytasz w wierszach, które mają wartości liczbowe, ale chcesz, aby były traktowane jako tekst. Możesz jednak obejść ten problem, tworząc schema.iniplik. Oto moja metoda, której użyłem:

// 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;
    }
}
Jim Scott
źródło
Dzięki stary. To mi pomogło. Miałem plik CSV, w którym przecinki były nie tylko separatorami, ale znajdowały się wszędzie w wartościach wielu kolumn, więc wymyślenie wyrażenia regularnego, które podzieliłoby linię, było dość trudne. OleDbProvider poprawnie wywnioskował schemat.
Galilyou,
Implementacja ma sens, ale jak radzimy sobie z komórkami zawierającymi mieszane typy danych. Na przykład 40C itp.?
GKED
GKED, jeśli dane, które czytasz, zawsze mają oczekiwany zestaw kolumn i typów, możesz umieścić w tym samym folderze plik shema.ini, który przekazuje dostawcy OleDb informacje o kolumnach. Oto łącze do artykułu firmy Microsoft, który zawiera szczegółowe informacje na temat struktury pliku. msdn.microsoft.com/en-us/library/…
Jim Scott
4
Chociaż ta odpowiedź zadziała, zdecydowanie odradzam. Wprowadzasz zależność zewnętrzną, która może kolidować z innymi instalacjami pakietu Office na tym samym komputerze (używać programu Excel w lokalnym środowisku deweloperskim?), W zależności od zainstalowanych wersji. Istnieją pakiety NuGet (ExcelDataReader, CsvHelper), które robią to w bardziej wydajny i przenośny sposób.
A. Murray,
1
@ A.Murray - Co dokładnie masz na myśli? Używa wbudowanego dostawcy OleDb w System.Data.dll. Nie musisz instalować żadnych dodatkowych „sterowników”. Byłbym zszokowany w dzisiejszych czasach, gdyby jakakolwiek instalacja systemu Windows nie miała zainstalowanego podstawowego sterownika Jet. To jest CSV z lat 90-tych ...
Wielkanoc Pawła
40

Zdecydowałem się użyć czytnika CSV Sebastiena Loriona .

Sugestia Jaya Riggsa jest również świetnym rozwiązaniem, ale po prostu nie potrzebowałem wszystkich funkcji, które zapewnia Generic Parser Andrew Rissinga .

AKTUALIZACJA 25.10.2010

Po prawie półtorarocznym używaniu czytnika Csv Sebastiena Loriona w moim projekcie odkryłem, że rzuca on wyjątki podczas analizowania niektórych plików csv, które uważam za dobrze uformowane.

Więc przeszedłem na Generic Parser Andrew Rissinga i wydaje się, że działa znacznie lepiej.

AKTUALIZACJA 22.09.2014

Obecnie używam głównie tej metody rozszerzenia do czytania tekstu rozdzielanego:

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

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

AKTUALIZACJA 20.02.2015

Przykład:

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());
}
Ronnie Overby
źródło
Zgadzam się, że czytnik CSV Sebastiena Loriena jest świetny. Używam go do ciężkiego przetwarzania CSV, ale używam również Rissingów Andrew's do małych zadań i dobrze mi to służyło. Baw się dobrze!
Jay Riggs
Jak mogę użyć tych klas, aby załadować CSV do DATATABLE?
Muflix,
Próbowałem tego, ale kolekcja it.Current.Keys zwraca „System.Linq.Enumerable + WhereSelectListIterator`2 [System.Int32, System.Char]” zamiast nazwy kolumny. Jakieś przemyślenia, dlaczego?
user3658298
Czy możesz używać ograniczników wieloznakowych?
rolki
Nie, ale myślałem o umożliwieniu tego.
Ronnie Overby
32

Hej, działa w 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;
   }

Obraz CSV wprowadź opis obrazu tutaj

Tabela danych zaimportowana wprowadź opis obrazu tutaj

Shivam Srivastava
źródło
7
Tylko wtedy, gdy 100% danych wejściowych to najprostsze pliki CSV (co może być prawdą w twoim przypadku).
Ronnie Overby
Masz rację. powinieneś użyć codeproject.com/Articles/9258/A-Fast-CSV-Reader (Lorion dll).
Shivam Srivastava
1
Zobacz moją odpowiedź z 2009 roku.
Ronnie Overby
1
@ShivamSrivastava Otrzymuję błąd w ostatnim wierszu, czy tam jesteś, a następnie podaj inne dane kontaktowe
Sunil Acharya
Chociaż nie korzystałem dokładnie z tej wersji, to na jej podstawie rozwiązałem swój problem. Dziękuję Ci. Działa bardzo dobrze.
nrod
13

Zawsze używaliśmy sterownika Jet.OLEDB, dopóki nie zaczęliśmy korzystać z aplikacji 64-bitowych. Firma Microsoft nie wydała i nie wyda 64-bitowego sterownika Jet. Oto proste rozwiązanie, które wymyśliliśmy, które wykorzystuje File.ReadAllLines i String.Split do odczytywania i analizowania pliku CSV oraz ręcznego ładowania DataTable. Jak wspomniano powyżej, NIE obsługuje sytuacji, w których jedna z wartości kolumny zawiera przecinek. Używamy tego głównie do czytania niestandardowych plików konfiguracyjnych - fajną częścią korzystania z plików CSV jest to, że możemy je edytować w programie 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);
}
Chuck Bevitt
źródło
8

to jest kod, którego używam, ale Twoje aplikacje muszą działać w sieci 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();
        }
Tomasz
źródło
Dokładnie to chciałem przedstawić.
Kapitan Kenpachi,
8

Możesz to osiągnąć za pomocą Microsoft.VisualBasic.FileIO.TextFieldParser dll w 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
źródło
Nie próbuj ponownie wymyślać koła z przetwarzaniem CSV. Jest tak wiele świetnych alternatyw open source, które są bardzo solidne.
Mike Cole
1
Dzięki Brad, przydatna wskazówka dotycząca TextFieldParser do obsługi osadzonych cytatów.
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
źródło
3

Natknąłem się na ten fragment kodu, który używa Linq i regex do analizowania pliku CSV. Artykuł odsyłający ma już ponad półtora roku, ale nie znalazłem lepszego sposobu analizowania pliku CSV przy użyciu Linq (i wyrażenia regularnego) niż ten. Zastrzeżenie polega na tym, że zastosowane tutaj wyrażenie regularne odnosi się do plików rozdzielanych przecinkami (wykryje przecinki w cudzysłowach!) I może nie pasować dobrze do nagłówków, ale istnieje sposób, aby to obejść). Weź szczyt:

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
Nepa
źródło
3

Najlepszą opcją, jaką znalazłem, i rozwiązuje problemy, w których możesz mieć zainstalowane różne wersje pakietu Office, a także problemy 32/64-bitowe, o których wspomniał Chuck Bevitt , to FileHelpers .

Można go dodać do odwołań do projektu przy użyciu narzędzia NuGet i zapewnia rozwiązanie jednowierszowe:

CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);
Neo
źródło
czy możesz powiedzieć, co to jest CommonEngine? NuGet jest taki sam jak NuGet.Core. Znalazłem tylko NuGet.Core w referencjach
sindhu jampani
To FileHelpers, którego potrzebujesz. Jeśli masz pakiet NuGet, dodaj go za pomocą NuGet. W przeciwnym razie po prostu dodaj go jako zespół do projektu. CommonEngine jest częścią FileHelpers.
Neo
3

Dla tych, którzy nie chcą używać zewnętrznej biblioteki i wolą nie używać OleDB, zobacz poniższy przykład. Wszystko, co znalazłem, to OleDB, zewnętrzna biblioteka lub po prostu podzielone na podstawie przecinka! W moim przypadku OleDB nie działał, więc chciałem czegoś innego.

Znalazłem artykuł MarkJ, który odwoływał się do metody Microsoft.VisualBasic.FileIO.TextFieldParser, jak widać tutaj . Artykuł jest napisany w VB i nie zwraca datatable, więc zobacz mój przykład poniżej.

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
źródło
3

Bardzo podstawowa odpowiedź: jeśli nie masz złożonego pliku csv, który może używać prostej funkcji podziału, będzie to dobrze działać przy importowaniu (zwróć uwagę, że importuje to jako ciągi, wykonuję później konwersje typów danych, jeśli zajdzie taka potrzeba)

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

Moja metoda, jeśli importuję tabelę z separatorem ciągów [] i rozwiązuje problem, w którym bieżący wiersz, który czytam, mógł przejść do następnego wiersza w pliku csv lub tekstowym <- W takim przypadku chcę zapętlić, dopóki nie otrzymam do całkowitej liczby wierszy w pierwszym wierszu (kolumnach)

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;
    }
Matt Farguson
źródło
Świetnie, po prostu jeszcze nie zadeklarowałeś wierszy jako string [].
Styl zwierzęcy
@AnimalStyle masz rację - zaktualizowano za pomocą bardziej niezawodnej metody i zadeklarowanych wierszy
Matt Farguson
3

Zmodyfikowano z Mr ChuckaBevitta

Rozwiązanie robocze:

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);
}
Balaji Selvarajan
źródło
Więc to rozwiązuje problem z pamięcią, prawda? To jest przetwarzanie wiersz po wierszu i nie utrzymuje się w pamięci, więc nie powinno być żadnych wyjątków? Podoba mi się sposób, w jaki jest to przetwarzane, ale czy File.ReadAllLines () nie zapisuje wszystkiego w pamięci? Myślę, że powinieneś użyć File.ReadLines (), aby uniknąć ogromnego bufora pamięci? To dobra odpowiedź na zadane pytanie, które chcę tylko wiedzieć o problemach z pamięcią.
DtechNet
2

Oto rozwiązanie wykorzystujące sterownik tekstowy 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

Po wypełnieniu można wycenić właściwości datatable, takie jak ColumnName, aby wykorzystać wszystkie możliwości obiektów danych ADO.Net.

W VS2008 możesz użyć Linq, aby osiągnąć ten sam efekt.

UWAGA: To może być duplikatem tego pytania SO.

Bob Mc
źródło
2

Nie mogę się oprzeć dodaniu do tego własnego spinu. To jest o wiele lepsze i bardziej kompaktowe niż to, czego używałem w przeszłości.

To rozwiązanie:

  • Nie zależy od sterownika bazy danych ani biblioteki innej firmy.
  • Nie zawiedzie przy zduplikowanych nazwach kolumn
  • Obsługuje przecinki w danych
  • Obsługuje dowolny separator, a nie tylko przecinki (chociaż jest to ustawienie domyślne)

Oto, co wymyśliłem:

  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

Zależy to od metody rozszerzenia ( Unique), która obsługuje zduplikowane nazwy kolumn, które mają być znalezione jako moja odpowiedź w Jak dołączyć unikalne liczby do listy ciągów

A oto BlankToNothingfunkcja pomocnicza:

  Public Function BlankToNothing(ByVal Value As String) As Object 
    If String.IsNullOrEmpty(Value) Then Return Nothing
    Return Value
  End Function
toddmo
źródło
2

Dzięki Cinchoo ETL - bibliotece open source, możesz łatwo przekonwertować plik CSV do DataTable za pomocą kilku wierszy kodu.

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

Aby uzyskać więcej informacji, odwiedź codeproject artykule .

Mam nadzieję, że to pomoże.

RajN
źródło
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;
    }

używając: https://joshclose.github.io/CsvHelper/

Youssef Bouha
źródło
Zauważ, że w wersji 13 Configuration nazwa została zmieniona na, CsvConfiguration aby uniknąć konfliktów przestrzeni nazw. Demo tej odpowiedzi działa: dotnetfiddle.net/sdwc6i
dbc
2

Używam biblioteki o nazwie ExcelDataReader, którą można znaleźć w NuGet. Pamiętaj, aby zainstalować zarówno rozszerzenie ExcelDataReader, jak i rozszerzenie ExcelDataReader.DataSet (to drugie zapewnia wymaganą metodę AsDataSet, o której mowa poniżej).

Wszystko zawarłem w jednej funkcji, możesz ją skopiować bezpośrednio w swoim kodzie. Podaj ścieżkę do pliku CSV, a otrzymasz zestaw danych z jedną tabelą.

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
źródło
Jest rok 2020 i jest to świetne rozwiązanie w porównaniu do niektórych starszych odpowiedzi tutaj. Jest ładnie zapakowany i używa popularnej i lekkiej biblioteki z NuGet. I jest elastyczny - jeśli twój MemoryStreamplik CSV jest w pamięci, po prostu przekaż go jako ścieżkę pliku zamiast. DataTable, o którą prosił OP, można łatwo wyodrębnić z DataSet w następujący sposób:result.Tables[0]
Tawab Wakil
1

Po prostu udostępniając te metody rozszerzenia, mam nadzieję, że może to komuś pomóc.

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
źródło
0

Użyj tego, jedna funkcja rozwiązuje wszystkie problemy z przecinkami i cudzysłowami:

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
źródło
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
Esat Kiziltepe
źródło
0

Niedawno napisałem parser CSV dla .NET, który, jak twierdzę, jest obecnie najszybszym dostępnym pakietem nuget : Sylvan.Data.Csv .

Używanie tej biblioteki do załadowania pliku DataTablejest niezwykle łatwe.

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

Zakładając, że twój plik to standardowe pliki oddzielone przecinkami z nagłówkami, to wszystko, czego potrzebujesz. Istnieją również opcje umożliwiające odczytywanie plików bez nagłówków i używanie alternatywnych ograniczników itp.

Możliwe jest również zapewnienie niestandardowego schematu dla pliku CSV, aby kolumny mogły być traktowane jako coś innego niż stringwartości. Umożliwi to DataTablezaładowanie kolumn wartościami, z którymi będzie łatwiej pracować, ponieważ nie będziesz musiał ich wymuszać, gdy uzyskasz do nich dostęp.

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

TypedCsvSchemato implementacja, ICsvSchemaProviderktóra zapewnia prosty sposób definiowania typów kolumn. Możliwe jest jednak również podanie niestandardowego, ICsvSchemaProvidergdy chcesz podać więcej metadanych, takich jak unikalność lub ograniczony rozmiar kolumny itp.

MarkPflug
źródło