如何使用WithAllProperties之类的方法扩展epplus数据提取器

时间:2019-02-05 16:09:40

标签: c# excel generics reflection epplus

我正在处理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属性值-为此,我不知道如何获取它。

需要任何帮助/提示/线索。

1 个答案:

答案 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模型一样发挥作用曾经创造。如果需要一些额外的数据类型,我将在助手类中添加两个方法。