Поэтому, потратив примерно час на то, чтобы в отчаянии дернуть себя за волосы, я решил последовать совету всех присутствующих и не реализовывать свой собственный CSV-парсер.
Поэтому я пошел с FileHelpers вместо.
Но у меня возникли некоторые проблемы с его правильным использованием.
Мой CSV файл выглядит примерно так:
50382018,50319368,eBusiness Manager,IT02,3350_FIB4,IT,2480
50370383,50373053,CRM Manager,IT01,3200_FIB3,xyz,2480
50320067,50341107,"VP, Business Information Officer",IT03,3200_FI89,xyz,2480
50299061,50350088,Project Expert,IT02,8118_FI09,abc,2480
Моя потребность в FileHelpers (и, в частности, CsvEngine
) находится в строке 3 - обратите внимание на третий столбец, заключенный в кавычки, поскольку он имеет внутреннюю запятую (которая в противном случае используется в качестве разделителя).
Мой код для чтения файла:
var co = new FileHelpers.Options.CsvOptions("Employee", columnDeliminator, 7);
var ce = new CsvEngine(co);
var records = ce.ReadFile(pathToCSVFile);
Работает нормально - вроде. Он правильно анализирует строки и распознает значения с заключенными в них разделителями.
Но.
Возвращаемым значением ReadFile()
-method является object[]
. И его содержимое выглядит как динамический тип.
Это выглядит примерно так - где столбцы названы "Field_1", "Field_2" и т.д.
Я создал "класс данных", предназначенный для хранения проанализированных строк. Это выглядит так:
public class Employee
{
public string DepartmentPosition;
public string ParentDepartmentPosition;
public string JobTitle;
public string Role;
public string Location;
public string NameLocation;
public string EmployeeStatus;
}
Есть ли способ, чтобы класс CsvEngine
возвращал строго типизированные данные?
Если бы я мог просто использовать "базовый" парсер FileHelpers, я мог бы использовать этот код:
var engine = new FileHelperEngine<Employee>();
var records = engine.ReadFile("Input.txt");
Есть ли способ, чтобы CsvEngine
возвращал экземпляры моего класса "Сотрудник"? Или я должен написать свой собственный код отображения для поддержки этого?
У @shamp00 правильный ответ - и я также нашел его в escape-разделителе FileHelper.
Я взял свой модельный класс и украсил каждое свойство на нем как предложено:
(Мне, вероятно, не нужно украшать все свойства, но пока это работает)
[DelimitedRecord((","))]
public class Employee
{
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string DepartmentPosition;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string ParentDepartmentPosition;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string JobTitle;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string Role;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string Location;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string NameLocation;
[FieldQuoted('"', QuoteMode.OptionalForBoth)]
public string EmployeeStatus;
}
Теперь мне просто нужен этот код:
TextReader reader = new StreamReader(contents);
var engine = new FileHelperEngine<Employee>()
{
Options = { IgnoreFirstLines = 1 }
};
var myRecords = engine.ReadStream(reader);
Используя CsvHelper как жизнеспособную альтернативу и предполагая, что файл CSV не имеет заголовков,
сопоставление может быть создано для класса Employee
например
public sealed class EmployeeClassMap : ClassMap<Employee> {
public EmployeeClassMap() {
Map(_ => _.Location).Index(0);
Map(_ => _.NameLocation).Index(1);
Map(_ => _.JobTitle).Index(2);
//...removed for brevity
}
}
Где индекс сопоставлен с соответствующим свойством в строго типизированной объектной модели.
Чтобы использовать это сопоставление, вам необходимо зарегистрировать сопоставление в конфигурации.
using (var textReader = new StreamReader(pathToCSVFile)) {
var csv = new CsvReader(textReader);
csv.Configuration.RegisterClassMap<EmployeeClassMap>();
var records = csv.GetRecords<Employee>();
//...
}
Документация работала для меня одним простым способом:
Сначала в вашем классе нужна пара декораторов:
Редактировать Используйте декоратор FieldQuoted для разбора чего-либо в кавычках и игнорирования включенной запятой
[DelimitedRecord(",")]
class Person
{
[FieldQuoted]
public string Name { get; set; }
[FieldConverter(ConverterKind.Int32)]
public int Age { get; set; }
public string State { get; set; }
}
DelimitedRecord
для класса и ожидаемый разделитель (это может быть проблемой, если что-то изменится позже.
и FieldConverter для него появляется ничего, кроме строки.
Затем немного измените ваш метод чтения:
var fhr = new FileHelperEngine<Person>();
var readLines = fhr.ReadFile(pathToFile);
и тогда это работает, строго набрано:
foreach(var person in readLines)
{
Console.WriteLine(person.Name);
}
CsvEngine
. Вы должны сделать картографирование самостоятельно. Использование FileHelperEngine<Employee>
- лучший подход, но вам нужно украсить класс с помощью [DelimitedRecord(",")]
и украсить свойство JobTitle с помощью [FieldQuoted(QuoteMode.OptionalForRead)]
.
Если эта библиотека не работает, вы также можете попробовать использовать встроенный синтаксический анализатор .Net CSV TextFieldParser. Например: https://coding.abel.nu/2012/06/built-in-net-csv-parser/
ДОБАВЛЕНО: Для типов (с автоматическим преобразованием):
static void run()
{
// split with any lib line of CSV
string[] line = new string[]{"john", "doe", "201"};
// needed prop names of class
string[] propNames = "fname|lname|room".Split('|');
Person p = new Person();
parseLine<Person>(p, line, propNames);
}
static void parseLine<T>(T t, string[] line, string[] propNames)
{
for(int i = 0;i<propNames.Length;i++)
{
string sprop = propNames[i];
PropertyInfo prop = t.GetType().GetProperty(sprop);
object val = Convert.ChangeType(line[i], prop.PropertyType);
prop.SetValue(t, val );
}
}
class Person
{
public string fname{get;set;}
public string lname{get;set;}
public int room {get;set;}
}