Я работаю с файлами Excel.Я должен прочитать табличные значения, хранящиеся в таблицах Excel, и десериализовать их в объекты.Для этого я использую OfficeOpenXml с помощью EPPlus.DataExtractor.Мои таблицы Excel имеют несколько столбцов, поэтому у моих классов есть несколько свойств - с разными типами данных: строки, целые числа, DateTimes, двойные и пустые числа, DateTimes, double.Я не могу предположить, что больше типов не придет вовремя.Например, класс для десериализации строки Excel может выглядеть следующим образом:
public class MyModel
{
[Column("A")]
public string Id { get; set; }
[Column("B")]
public string Code { get; set; }
[Column("C")]
public int Number { get; set; }
[Column("D")]
public DateTime? ValidTo { get; set; }
}
Столбец - это мой собственный атрибут, который сообщает экстрактору, в каком столбце содержится значение для данного свойства:
public class ColumnAttribute : Attribute
{
public string Column { get; set; }
public ColumnAttribute(string column) => Column = column;
}
Вот почему я могу использовать EPPlus следующим образом
public class MyModelExtractor
{
private readonly string _path;
public MyModelExtractor(string path) => _path = path;
public List<MyModel> Create()
{
using (var excelPackage = new ExcelPackage(new FileInfo(_path)))
{
var worksheet = excelPackage.Workbook.Worksheets[1];
return worksheet
.Extract<MyModel>()
.WithProperty(p => p.Id, MyModel.GetColumnAnnotation(p => p.Id))
.WithProperty(p => p.Code , MyModel.GetColumnAnnotation(p => p.Code ))
.WithProperty(p => p.Number, MyModel.GetColumnAnnotation(p => p.Number))
.WithProperty(p => p.ValidTo , MyModel.GetColumnAnnotation(p => p.ValidTo ))
.GetData(2, row => worksheet.Cells[row, 1].Value != null)
.ToList();
}
}
Теперь в классе MyModel есть нечто большее, а именно:
public static string GetColumnAnnotation<T>(Expression<Func<MyModel, T>> propertySelector) =>
AttributeExtractor.GetPropertyAttributeValue<MyModel, T, ColumnAttribute, string>(propertySelector, attribute => attribute.Column);
, который, как видно, используетсяв методе WithProperty, чтобы получить значение атрибута Column (просто строка).
Для полноты я предоставлю AttributeExtractor, который выглядит следующим образом:
public static class AttributeExtractor
{
public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(Expression<Func<T, TOut>> propertyExpression,
Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute
{
var propertyInfo = (PropertyInfo)((MemberExpression)propertyExpression.Body).Member;
return propertyInfo.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute attr
? valueSelector(attr)
: throw new MissingMemberException(typeof(T).Name + "." + propertyInfo.Name, typeof(TAttribute).Name);
}
}
Теперь в каждой моделикласс (и у меня их десятки) я должен предоставить этот статический метод GetPropertyAttributeValue.Что еще более проблематично, классы содержат много свойств, поэтому вызов WithProperty выполняется много раз.И, опять же, для каждого класса у меня есть отдельный соответствующий экстрактор.
Я думал о создании универсальной версии Extractor, например,
public class Extractor<T> { ... }
, где T будет типом, подобным MyModel, и затем я мог бы написать некоторый метод, например WithAllProperties (), который заменитвсе вызовы WithProperty.
Тогда класс будет выглядеть так:
public class Extractor<T>
{
...ctor and _path, and then:
public List<T> Create()
{
using (var excelPackage = new ExcelPackage(new FileInfo(_path)))
{
var worksheet = excelPackage.Workbook.Worksheets[1];
return worksheet
.Extract<T>()
.WithAllProperties()
.GetData(2, row => worksheet.Cells[row, 1].Value != null)
.ToList();
}
}
}
Теперь я борюсь с методом WithAllProperties.Это должно выглядеть так:
public static ICollectionPropertyConfiguration<T> WithAllProperties<T>(
this IDataExtractor<T> extractor) where T : class, new()
{
foreach(var property in typeof(T).GetProperties())
extractor = extractor.WithProperty(/1/, /2/);
return extractor as ICollectionPropertyConfiguration<T>;
}
Чего не хватает, так это / 1 /, который должен иметь тип
Expression<Func<T,TProperty>>
Я не могу динамически генерировать это значение (без некоторых уловок, которые кажутся непонятными длямне, как переключение типа переменной свойства, и создание необходимого выражения. Это работает, но когда появляются новые типы, этот переключатель должен быть расширен, и я уверен, что это может быть сделано динамически с помощью отражения).Другая вещь - это / 2 /, которая является значением атрибута Column для соответствующего свойства - для этого я не знаю, как его получить.
Нужна любая помощь / подсказки / подсказки.