我正在处理Excel文件。我必须阅读存储在Excel工作表中的表格值并将其反序列化为对象。为此,我在EPPlus.DataExtractor的帮助下使用OfficeOpenXml。我的Excel表具有多个列,因此我的类具有多个属性-具有不同的数据类型,字符串,整数,日期时间,双精度数和可为null的整数,日期时间,双精度数。我不能认为不会及时出现更多类型。例如,用于反序列化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; }
}
Column是我自己的属性,它告诉提取程序哪个列包含给定属性的值:
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>>
我无法动态生成该值(没有一些对我来说似乎并不明智的技巧,例如切换属性变量的类型和创建所需的Expression。它可以工作,但是当出现新类型时,必须扩展此开关并我确信可以使用反射来动态完成。另一件事是/ 2 /,它是对应属性的Column属性值-为此,我不知道如何获取它。
需要任何帮助/提示/线索。
答案 0 :(得分:0)
好吧,由于没有人有时间提供任何提示,因此我制定了自己的解决方案。这不是理想的-也许可以改进,但是我对结果感到满意,因为我的代码减少了很多。
第一个更改是从静态GetColumnAnnotation方法中清除所有MyExcel模型,例如MyModel。剩下的就是带有Column属性的纯属性。下一个更改是摆脱通用的AttributeExtractor-不再需要它。
我创建了一个通用的ExcelExtractor类,看起来很苗条:
public class ExcelExtractor<T> where T: class, new()
{
public ExcelExtractor(IExcelPathProvider pathProvider) => _pathProvider = pathProvider;
public List<T> Create(int sheetNumber)
{
using (var excelPackage = new ExcelPackage(new FileInfo(_pathProvider.GetPath())))
{
var worksheet = excelPackage.Workbook.Worksheets[sheetNumber];
return worksheet
.Extract<T>()
.WithAllProperties()
.GetData(2, row => worksheet.Cells[row, 1].Value != null)
.ToList();
}
}
private readonly IExcelPathProvider _pathProvider;
}
接下来,我创建了一些扩展类,如下所示:
public static class ReflectionExtensions
{
public static ICollectionPropertyConfiguration<T> WithAllProperties<T>(
this IDataExtractor<T> extractor) where T : class, new() =>
typeof(T)
.GetProperties()
.Aggregate(extractor, ExtractProperty) as ICollectionPropertyConfiguration<T>;
private static string ToColumn(this PropertyInfo property) =>
((ColumnAttribute)property.GetCustomAttributes(typeof(ColumnAttribute), true)
.First()).Column;
private static IDataExtractor<T> ExtractProperty<T>(IDataExtractor<T> extractor,
PropertyInfo property) where T : class, new()
{
if (property.PropertyType == typeof(string))
return extractor.WithProperty(ExpressionGenerator<T>.GetStringProperty(property), property.ToColumn());
if (property.PropertyType == typeof(int))
return extractor.WithProperty(ExpressionGenerator<T>.GetIntProperty(property), property.ToColumn());
if (property.PropertyType == typeof(int?))
return extractor.WithProperty(ExpressionGenerator<T>.GetNullableIntProperty(property), property.ToColumn());
if (property.PropertyType == typeof(DateTime))
return extractor.WithProperty(ExpressionGenerator<T>.GetDateTimeProperty(property), property.ToColumn());
if (property.PropertyType == typeof(DateTime?))
return extractor.WithProperty(ExpressionGenerator<T>.GetNullableDateTimeProperty(property), property.ToColumn());
if (property.PropertyType == typeof(bool))
return extractor.WithProperty(ExpressionGenerator<T>.GetBooleanProperty(property), property.ToColumn());
if (property.PropertyType == typeof(bool?))
return extractor.WithProperty(ExpressionGenerator<T>.GetNullableBooleanProperty(property), property.ToColumn());
throw new ArgumentException($"Unknown type {property.PropertyType}");
}
private static class ExpressionGenerator<T>
{
public static Expression<Func<T, string>> GetStringProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, string>>(GetMember(property), Parameter);
public static Expression<Func<T, int>> GetIntProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, int>>(GetMember(property), Parameter);
public static Expression<Func<T, int?>> GetNullableIntProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, int?>>(GetMember(property), Parameter);
public static Expression<Func<T, DateTime>> GetDateTimeProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, DateTime>>(GetMember(property), Parameter);
public static Expression<Func<T, DateTime?>> GetNullableDateTimeProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, DateTime?>>(GetMember(property), Parameter);
public static Expression<Func<T, bool>> GetBooleanProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, bool>>(GetMember(property), Parameter);
public static Expression<Func<T, bool?>> GetNullableBooleanProperty(PropertyInfo property) =>
Expression.Lambda<Func<T, bool?>>(GetMember(property), Parameter);
private static readonly ParameterExpression Parameter =
Expression.Parameter(typeof(T), "p");
private static MemberExpression GetMember(PropertyInfo property) =>
Expression.Property(Parameter, property.Name);
}
}
也许该解决方案可以进一步改进,如果有人提供任何提示,我将不胜感激,但是我对结果非常满意-我有一个通用的Extractor,它可以像任何新的excel模型一样发挥作用曾经创造。如果需要一些额外的数据类型,我将在助手类中添加两个方法。