Чтение CSV файлов с использованием С#
Я пишу простое приложение импорта и должен прочитать CSV файл, показать результат в DataGrid
и показать поврежденные строки файла CSV в другой сетке. Например, покажите строки, которые короче 5 значений в другой сетке. Я пытаюсь сделать это вот так:
StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
row = line.Split(',');
importingData.Add(new Transaction
{
Date = DateTime.Parse(row[0]),
Reference = row[1],
Description = row[2],
Amount = decimal.Parse(row[3]),
Category = (Category)Enum.Parse(typeof(Category), row[4])
});
}
но в этом случае очень сложно работать с массивами. Есть ли лучший способ разделить значения?
Ответы
Ответ 1
Не изобретайте велосипед. Воспользуйтесь тем, что уже есть в .NET BCL.
- добавьте ссылку на
Microsoft.VisualBasic
(да, он говорит VisualBasic, но он также работает на С#) - помните, что в конце это всего лишь IL)
- используйте класс
Microsoft.VisualBasic.FileIO.TextFieldParser
для разбора CSV файла
Вот пример кода:
using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
//Processing row
string[] fields = parser.ReadFields();
foreach (string field in fields)
{
//TODO: Process field
}
}
}
Он отлично работает для меня в моих проектах на С#.
Вот еще несколько ссылок/информации:
Ответ 2
Мой опыт в том, что существует много разных форматов csv. Специально, как они обрабатывают экранирование кавычек и разделителей внутри поля.
Это варианты, с которыми я столкнулся:
- цитируются и удваиваются (например, 15 "- > поле1," 15 "", поле3
- цитаты не изменяются, если поле не цитируется по какой-либо другой причине. то есть 15 "- > поле1,15", fields3
- цитаты экранируются с помощью \. то есть 15 "- > поле1," 15\"", поле3
- цитаты вообще не изменяются (это не всегда возможно правильно разобрать)
- разделитель цитируется (excel). то есть a, b → field1, "a, b", field3
- разделитель с \. то есть a, b → field1, a \, b, field3
Я пробовал многие из существующих синтаксических анализаторов csv, но нет ни одного, который мог бы обрабатывать варианты, с которыми я столкнулся. Из документации, которая избегает вариантов, поддерживаемых парсерами, также трудно найти.
В моих проектах теперь я использую либо VB TextFieldParser, либо пользовательский разделитель.
Ответ 3
Я рекомендую CsvHelper из Nuget.
(Добавление ссылки на Microsoft.VisualBasic просто не кажется правильным, это не только уродливо, но и, возможно, даже не межплатформенное.)
Ответ 4
Иногда использование библиотек классно, когда вы не хотите изобретать колесо, но в этом случае можно выполнять ту же работу с меньшим количеством строк кода и читать легче по сравнению с использованием библиотек.
Вот другой подход, который я считаю очень простым в использовании.
- В этом примере я использую StreamReader для чтения файла
- Regex для определения разделителя из каждой строки.
- Массив для сбора столбцов от индекса 0 до n
using (StreamReader reader = new StreamReader(fileName))
{
string line;
while ((line = reader.ReadLine()) != null)
{
//Define pattern
Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
//Separating columns to array
string[] X = CSVParser.Split(line);
/* Do something with X */
}
}
Ответ 5
CSV может быстро усложниться.
Используйте что-то надежное и проверенное:
FileHelpers:
www.filehelpers.net
FileHelpers - бесплатная и простая в использовании библиотека .NET для импорта/экспорта данных с фиксированной длины или разделенных записей в файлах, строках или потоках.
Ответ 6
Я использую это здесь:
http://www.codeproject.com/KB/database/GenericParser.aspx
В прошлый раз, когда я искал что-то подобное, я нашел его в качестве ответа на этот question.
Ответ 7
Cinchoo ETL - библиотека с открытым исходным кодом для чтения и записи файлов CSV.
Для образца файла CSV ниже
Id, Name
1, Tom
2, Mark
Быстро вы можете загрузить их, используя библиотеку, как показано ниже
using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
foreach (dynamic item in reader)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
}
}
Если у вас есть класс POCO, соответствующий файлу CSV
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
Вы можете использовать его для загрузки файла CSV, как показано ниже
using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
foreach (var item in reader)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
}
}
Пожалуйста, ознакомьтесь со статьями в CodeProject о том, как его использовать.
Отказ от ответственности: я автор этой библиотеки
Ответ 8
private 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;
}
private static void WriteToDb(DataTable dt)
{
string connectionString =
"Data Source=localhost;" +
"Initial Catalog=Northwind;" +
"Integrated Security=SSPI;";
using (SqlConnection con = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";
con.Open();
cmd.ExecuteNonQuery();
}
}
}
Ответ 9
Чтобы выполнить предыдущие ответы, может понадобиться коллекция объектов из его файла CSV, либо проанализированная методом TextFieldParser
, либо string.Split
, а затем каждая строка преобразуется в объект через Reflection. Вы, очевидно, сначала должны определить класс, который соответствует строкам CSV файла.
Я использовал простой CSV-сериализатор от Майкла Кропата, найденный здесь: Общий класс для CSV (все свойства)
и повторно использовал свои методы для получения полей и свойств желаемого класса.
Я десериализую свой CSV файл следующим способом:
public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
if (!File.Exists(fileFullPath))
{
return null;
}
var list = new List<T>();
var csvFields = GetAllFieldOfClass<T>();
var fieldDict = new Dictionary<int, MemberInfo>();
using (TextFieldParser parser = new TextFieldParser(fileFullPath))
{
parser.SetDelimiters(delimiter);
bool headerParsed = false;
while (!parser.EndOfData)
{
//Processing row
string[] rowFields = parser.ReadFields();
if (!headerParsed)
{
for (int i = 0; i < rowFields.Length; i++)
{
// First row shall be the header!
var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
if (csvField != null)
{
fieldDict.Add(i, csvField);
}
}
headerParsed = true;
}
else
{
T newObj = new T();
for (int i = 0; i < rowFields.Length; i++)
{
var csvFied = fieldDict[i];
var record = rowFields[i];
if (csvFied is FieldInfo)
{
((FieldInfo)csvFied).SetValue(newObj, record);
}
else if (csvFied is PropertyInfo)
{
var pi = (PropertyInfo)csvFied;
pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
}
else
{
throw new Exception("Unhandled case.");
}
}
if (newObj != null)
{
list.Add(newObj);
}
}
}
}
return list;
}
public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
return
from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
select mi;
}
Ответ 10
Прежде всего, нужно понять, что такое CSV и как его записать.
- Каждая следующая строка (
/r/n
) - следующая строка таблицы.
- Элементы "Таблица" разделяются символом разделителя. Чаще всего используются символы
\t
или ,
- Каждая ячейка может содержать этот символ разделителя (ячейка должна начинаться с символа кавычек и заканчивается этим символом в этом случае)
- Каждая ячейка может содержать
/r/n
sybols (ячейка должна начинаться с символа кавычек и заканчивается этим символом в этом случае)
Самый простой способ работы с CSV файлами С#/Visual Basic - использовать стандартную библиотеку Microsoft.VisualBasic
. Вам просто нужно добавить необходимую ссылку и следующую строку в ваш класс:
using Microsoft.VisualBasic.FileIO;
Да, вы можете использовать его на С#, не волнуйтесь. Эта библиотека может читать относительно большие файлы и поддерживает все необходимые правила, поэтому вы сможете работать со всеми CSV файлами.
Некоторое время назад я написал простой класс для чтения/записи CSV на основе этой библиотеки. Используя этот простой класс, вы сможете работать с CSV как с массивом размеров.
Вы можете найти мой класс по следующей ссылке:
https://github.com/ukushu/DataExporter
Простой пример использования:
Csv csv = new Csv("\t");//delimiter symbol
csv.FileOpen("c:\\file1.csv");
var row1Cell6Value = csv.Rows[0][5];
csv.AddRow("asdf","asdffffff","5")
csv.FileSave("c:\\file2.csv");
Ответ 11
Я думаю, что теперь есть новый способ с Linq. проверьте эту ссылку для разбора CSV с С# https://youtu.be/3tnLIBL0J2k