gpt4 book ai didi

c# - 如何使用 WithAllProperties 等方法扩展 epplus 数据提取器

转载 作者:行者123 更新时间:2023-12-04 21:50:45 27 4
gpt4 key购买 nike

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

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 的调用要进行多次。而且,对于每个类(class),我都有单独的相应提取器。

我想过创建一个通用版本的提取器,比如
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 属性的值 - 为此我不知道如何获得它。

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

最佳答案

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

第一个更改是从静态 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 模型都具有吸引力。如果需要一些额外的数据类型,我将向我的助手类添加两个方法。

关于c# - 如何使用 WithAllProperties 等方法扩展 epplus 数据提取器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54538532/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com