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?
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?
Odpowiedzi:
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.
Korzystam z OleDb
dostawcy. 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.ini
plik. 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;
}
}
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 .
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.
Obecnie używam głównie tej metody rozszerzenia do czytania tekstu rozdzielanego:
https://www.nuget.org/packages/CoreTechs.Common/
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());
}
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
Tabela danych zaimportowana
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);
}
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();
}
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;
}
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;
}
}
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
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);
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;
}
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;
}
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);
}
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.
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:
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 BlankToNothing
funkcja pomocnicza:
Public Function BlankToNothing(ByVal Value As String) As Object
If String.IsNullOrEmpty(Value) Then Return Nothing
Return Value
End Function
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.
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/
Configuration
nazwa została zmieniona na, CsvConfiguration
aby uniknąć konfliktów przestrzeni nazw. Demo tej odpowiedzi działa: dotnetfiddle.net/sdwc6i
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();
}
}
MemoryStream
plik 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]
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;
}
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;
}
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
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 DataTable
jest 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ż string
wartości. Umożliwi to DataTable
zał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);
TypedCsvSchema
to implementacja, ICsvSchemaProvider
która zapewnia prosty sposób definiowania typów kolumn. Możliwe jest jednak również podanie niestandardowego, ICsvSchemaProvider
gdy chcesz podać więcej metadanych, takich jak unikalność lub ograniczony rozmiar kolumny itp.