Как прочитать файл CSV в.NET Datatable
Как я могу загрузить файл CSV в System.Data.DataTable
создание данных на основе файла CSV?
Позволяет ли обычная функциональность ADO.net это?
24 ответа
Вот отличный класс, который будет копировать данные CSV в таблицу данных, используя структуру данных для создания DataTable:
Портативный и эффективный универсальный парсер для плоских файлов
Это легко настроить и легко использовать. Я призываю вас взглянуть.
Я использую 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;
}
}
Эй, это работает на 100%
public static DataTable ConvertCSVtoDataTable(string strFilePath)
{
DataTable dt = new DataTable();
using (StreamReader sr = new StreamReader(strFilePath))
{
string[] headers = sr.ReadLine().Split(',');
foreach (string header in headers)
{
dt.Columns.Add(header);
}
while (!sr.EndOfStream)
{
string[] rows = sr.ReadLine().Split(',');
DataRow dr = dt.NewRow();
for (int i = 0; i < headers.Length; i++)
{
dr[i] = rows[i];
}
dt.Rows.Add(dr);
}
}
return dt;
}
CSV Image
Таблица данных Импортировано
Я решил использовать Csv Reader Себастьяна Лориона.
Предложение Джея Риггса также является отличным решением, но мне просто не нужны были все функции, которые предоставляет Generic Parser Эндрю Риссинга.
ОБНОВЛЕНИЕ 25.10.2010
После использования Csv Reader Себастьяна Лориона в моем проекте в течение почти полутора лет я обнаружил, что он генерирует исключения при синтаксическом анализе некоторых CSV-файлов, которые я считаю хорошо сформированными.
Итак, я переключился на Generic Parser Эндрю Риссинга, и, похоже, дела идут намного лучше.
ОБНОВЛЕНИЕ 22.09.2014
В настоящее время я в основном использую этот метод расширения для чтения текста с разделителями:
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());
}
Мы всегда использовали драйвер 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);
}
Вы можете достичь этого с помощью 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;
}
Это код, который я использую, но ваши приложения должны работать с чистой версией 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();
}
public DataTable CsvFileToDatatable(string path, bool IsFirstRowHeader)//here Path is root of file and IsFirstRowHeader is header is there or not
{
string header = "No";
string sql = string.Empty;
DataTable dataTable = null;
string pathOnly = string.Empty;
string fileName = string.Empty;
try
{
pathOnly = Path.GetDirectoryName(path);
fileName = Path.GetFileName(path);
sql = @"SELECT * FROM [" + fileName + "]";
if (IsFirstRowHeader)
{
header = "Yes";
}
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 = new DataTable();
dataTable.Locale = CultureInfo.CurrentCulture;
adapter.Fill(dataTable);
}
}
}
}
finally
{
}
return dataTable;
}
Модифицировано от мистера Чака Бевитта
Рабочий раствор:
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);
}
Лучший вариант, который я нашел, и он решает проблемы, когда у вас могут быть установлены разные версии Office, а также 32/64-битные проблемы, такие как упомянутый Чак Бевитт, это FileHelpers.
Он может быть добавлен к ссылкам на ваш проект с помощью NuGet и предоставляет однострочное решение:
CommonEngine.CsvToDataTable(path, "ImportRecord", ',', true);
Я наткнулся на этот фрагмент кода, который использует Linq и регулярные выражения для анализа файла CSV. Ссылочной статье уже более полутора лет, но она не нашла более точного способа анализа CSV с использованием Linq (и регулярного выражения), чем эта. Предостережение - это регулярное выражение, применяемое здесь для файлов с разделителями-запятыми (будет обнаруживать запятые внутри кавычек!) И то, что оно может не очень хорошо восприниматься в заголовках, но есть способ преодолеть их). Возьми пик:
Dim lines As String() = System.IO.File.ReadAllLines(strCustomerFile)
Dim pattern As String = ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
Dim r As System.Text.RegularExpressions.Regex = New System.Text.RegularExpressions.Regex(pattern)
Dim custs = From line In lines _
Let data = r.Split(line) _
Select New With {.custnmbr = data(0), _
.custname = data(1)}
For Each cust In custs
strCUSTNMBR = Replace(cust.custnmbr, Chr(34), "")
strCUSTNAME = Replace(cust.custname, Chr(34), "")
Next
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/
Cinchoo ETL - библиотека с открытым исходным кодом, вы можете легко конвертировать CSV-файл в DataTable с помощью нескольких строк кода.
using (var p = new ChoCSVReader(** YOUR CSV FILE **)
.WithFirstLineHeader()
)
{
var dt = p.AsDataTable();
}
Для получения дополнительной информации, пожалуйста, посетите статью codeproject.
Надеюсь, поможет.
Недавно я написал парсер 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
когда вы хотите предоставить больше метаданных, таких как уникальность или ограниченный размер столбца и т. д.
Очень простой ответ: если у вас нет сложного 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;
}
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;
}
}
Для тех из вас, кто не хочет использовать внешнюю библиотеку и предпочитает не использовать 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;
}
Не могу удержаться от добавления собственного спина к этому. Это намного лучше и компактнее, чем то, что я использовал в прошлом.
Это решение:
- Не зависит от драйвера базы данных или сторонней библиотеки.
- Не подведет к повторяющимся именам столбцов
- Обрабатывает запятые в данных
- Обрабатывает любой разделитель, а не только запятые (хотя это по умолчанию)
Вот что я придумал:
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
Я использую библиотеку под названием ExcelDataReader, вы можете найти ее на Nuget
Я инкапсулировал каждую вещь в одну функцию, вы можете скопировать ее в свой код напрямую. Указать путь к 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();
}
}
Вот решение, которое использует драйвер текста ODBC ADO.Net:
Dim csvFileFolder As String = "C:\YourFileFolder"
Dim csvFileName As String = "YourFile.csv"
'Note that the folder is specified in the connection string,
'not the file. That's specified in the SELECT query, later.
Dim connString As String = "Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=" _
& csvFileFolder & ";Extended Properties=""Text;HDR=No;FMT=Delimited"""
Dim conn As New Odbc.OdbcConnection(connString)
'Open a data adapter, specifying the file name to load
Dim da As New Odbc.OdbcDataAdapter("SELECT * FROM [" & csvFileName & "]", conn)
'Then fill a data table, which can be bound to a grid
Dim dt As New DataTableda.Fill(dt)
grdCSVData.DataSource = dt
После заполнения вы можете оценить свойства таблицы данных, например ColumnName, чтобы использовать все возможности объектов данных ADO.Net.
В VS2008 вы можете использовать Linq для достижения того же эффекта.
ПРИМЕЧАНИЕ: это может быть дубликат этого вопроса SO.
Просто поделившись этими методами расширения, я надеюсь, что это может кому-то помочь.
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;
}
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
Используйте это, одна функция решает все проблемы запятой и цитаты:
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;
}
Конвертер csv в DataTable. Вы можете выбрать разделитель, isFirstRowHeaders и префикс для дополнительных заголовков, если ваша первая строка не является полным списком заголовков, или вы автоматически генерируете заголовки.
public DataTable GetDataFromCsv(string path, char separator, bool isFirstRowHeaders = true, string prefixAutoHeader = "AutoHeader_")
{
DataTable dt = new DataTable();
string csvData;
try
{
using (StreamReader sr = new StreamReader(path))
{
csvData = sr.ReadToEnd().ToString();
//Split csvData by Rows
List<string> csvRows = new List<string>(csvData.Split('\n'));
//Split rows by cells with selected separator
List<List<string>> csvCells = new List<List<string>>();
csvRows.ForEach(r => csvCells.Add(new List<string>(r.Split(separator))));
//definition row max size, for adding extra headers
int maxSizeRow = csvCells.OrderByDescending(r => r.Count).First().Count;
//if isFirstRowHeaders then filling datatable headers from first csvRow
if (isFirstRowHeaders)
{
foreach (string header in csvCells[0])
{
dt.Columns.Add(header);
}
}
//Adding extra headers in datatable or create AutoHeaders if isFirstRowHeaders == false
for (int i = dt.Columns.Count; i < maxSizeRow; i++)
{
dt.Columns.Add(prefixAutoHeader + i);
}
//Filling datatable
foreach (var row in csvCells)
{
//Skip the first row if it is consist headers
if (isFirstRowHeaders)
{
isFirstRowHeaders = false;
}
else
{
//creating datatable row and Add to datatable
int i = 0;
DataRow toInsert = dt.NewRow();
foreach (string cell in row)
{
try
{
toInsert[i] = cell;
}
catch (Exception ex) { }
i++;
}
dt.Rows.Add(toInsert);
}
}
}
return dt;
}
catch (Exception e)
{
return null;
}
}