【问题标题】:How to extend epplus data extractor with method like WithAllProperties如何使用 WithAllProperties 等方法扩展 epplus 数据提取器
【发布时间】:2019-02-08 23:31:10
【问题描述】:

我正在处理 excel 文件。我必须读取存储在 excel 表中的表格值并将它们反序列化为对象。为此,我在 EPPlus.DataExtractor 的帮助下使用了 OfficeOpenXml。我的 Excel 表有几列,因此我的类有几个属性 - 具有不同的数据类型字符串、整数、日期时间、双精度和可空整数、日期时间、双精度。我不能假设更多类型不会及时出现。例如,用于反序列化 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>> 

我不能动态生成这个值(没有一些对我来说似乎不聪明的技巧,比如切换属性变量的类型,并创建所需的表达式。它可以工作,但是当新类型出现时,这个开关必须扩展并且我确信它可以使用反射动态地完成)。另一件事是 /2/ ,它是对应属性的 Column 属性的值 - 为此我不知道如何获取它。

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

【问题讨论】:

    标签: c# excel generics reflection epplus


    【解决方案1】:

    好的,由于没有人有时间提供任何提示,我制定了自己的解决方案。这并不理想 - 也许可以改进,但我对结果很满意,因为我的代码减少了很多。

    第一个更改是从静态 GetColumnAnnotation 方法中清除所有 excel 模型,例如 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);
    
        }
    }
    

    可能解决方案可以进一步改进,如果有人提供任何提示,我将非常感激,但我对结果非常满意 - 我有一个通用的提取器,它对我可以使用的任何新的 excel 模型都很有用永远创造。如果需要一些额外的数据类型,我将向我的助手类添加两个方法。

    【讨论】:

    • 感谢您使用图书馆。我一直在考虑扩展它以支持属性,很高兴知道它对其他人有用。
    猜你喜欢
    • 1970-01-01
    • 2016-02-13
    • 2015-10-20
    • 2010-11-14
    • 1970-01-01
    • 2015-01-02
    • 2017-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多