现在,我们来完成一个稍微复杂一点的场景用例。
将实体导出为 CSV 文件 为了使下文的示例更加符合生产实际,我们在这里引入一个具体的场景。
我们需要将实体导出为 CSV 文件。
CSV 文件一般包含两个部分。
第一个部分是文件的头部。头部中包含了每一列的列名。
第二个部分是内容部分。内容部分每行都是一条记录。
不论是头部还是内容部分每个属性之间都使用逗号进行分隔。
这里我们使用前文使用到的 OrderInfo
进行演示:
1 2 3 4 5 6 public class OrderInfo { public int OrderId { get ; set ; } public string Buyer { get ; set ; } public decimal TotalPrice { get ; set ; } }
则导出的 CSV 样例如下:
1 2 3 4 OrderId,Buyer,TotalPrice 1,yueluo,99999 2,newbe36524,36524 3,traceless,123456
分析实现思路 这个业务场景的实现方式可以非常多样化,此处我们简要将逻辑分为以下部分:
使用 object visitor 访问OrderInfo
的所有属性 将所有属性传递给 CSV Writer 进行输出 实现 CSV 写入器 首先,我们先添加第 2 步所需要的 CSV 写入器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public interface ICsvWriter { ICsvWriter WriteHeader (string header ) ; ICsvWriter FinishHead () ; ICsvWriter WriteCell (string cell ) ; ICsvWriter FinishRow () ; } public class CsvWriter : ICsvWriter { public string Separator { get ; set ; } = "," ; private readonly TextWriter _writer; public CsvWriter ( TextWriter writer ) { _writer = writer; } private bool _firstHead = true ; public ICsvWriter WriteHeader (string header ) { if (_firstHead) { _firstHead = false ; } else { _writer.Write(Separator); } _writer.Write(header); return this ; } public ICsvWriter FinishHead () { _writer.WriteLine(); return this ; } private bool _firstCell = true ; public ICsvWriter WriteCell (string cell ) { if (_firstCell) { _firstCell = false ; } else { _writer.Write(Separator); } _writer.Write(cell); return this ; } public ICsvWriter FinishRow () { _firstCell = true ; _writer.WriteLine(); return this ; } }
有了这样一个基础的CsvWriter
,我们便可以首先来生成一个样例中的表格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var sb = new StringBuilder();var csvWriter = new CsvWriter(new StringWriter(sb));csvWriter.WriteHeader("OrderId" ) .WriteHeader("Buyer" ) .WriteHeader("TotalPrice" ) .FinishHead() .WriteCell("1" ) .WriteCell("yueluo" ) .WriteCell("99999" ) .FinishRow() .WriteCell("2" ) .WriteCell("newbe36524" ) .WriteCell("36524" ) .FinishRow() .WriteCell("3" ) .WriteCell("traceless" ) .WriteCell("123456" ) .FinishRow(); Console.WriteLine(sb.ToString());
输出表头 现在,我们使用第一个 object visitor 来调用上文的 CsvWriter 来输出表头:
1 2 3 4 5 6 7 8 9 10 11 var sb = new StringBuilder();var csvWriter = new CsvWriter(new StringWriter(sb));default (OrderInfo) .V() .WithExtendObject<OrderInfo, CsvWriter>() .ForEach((name, value , w) => w.WriteHeader(name)) .Run(new OrderInfo(), csvWriter); csvWriter.FinishHead(); Console.WriteLine(sb.ToString());
这样我就会得到如下的结果:
1 OrderId,Buyer,TotalPrice
输出表行 现在,我们在增加一个 object visitor 来输出表的每行内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 var rowWriter = default (OrderInfo) .V() .WithExtendObject<OrderInfo, CsvWriter>() .ForEach((name, value , w) => w.WriteCell(value != null ? value .ToString() : "" )) .Cache(); var orders = new List<OrderInfo>{ new OrderInfo { OrderId = 1 , Buyer = "yueluo" , TotalPrice = 99999 M }, new OrderInfo { OrderId = 2 , Buyer = "newbe36524" , TotalPrice = 36524 M }, new OrderInfo { OrderId = 3 , Buyer = "traceless" , TotalPrice = 123456 M } }; foreach (var order in orders){ rowWriter.Run(order, csvWriter); csvWriter.FinishRow(); } Console.WriteLine(sb.ToString());
这样就会得到如下的内容:
1 2 3 1,yueluo,99999 2,newbe36524,36524 3,traceless,123456
这正是我们期望的表中的行数据。
创建 CsvExtensions 现在,我们将以上的表头和表行的相关逻辑进行整合,将他们全部都添加到一个 CsvExtensions 的类型中。并且增加对于IEnumerable<T>
的扩展方法,这样在进行调用时就会更加简单。这将仿照先前FormatToStringExtensions
中的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public static class CsvExtensions { public static string ToCsv <T >(this IEnumerable<T> items ) where T : new () { var re = CsvFilerHelper<T>.Instance.ToCsv(items); return re; } private static class CsvFilerHelper <T > where T : new () { internal static readonly ICsvHelper Instance = new CsvHelper(); public interface ICsvHelper { string ToCsv (IEnumerable<T> items ) ; } private class CsvHelper : ICsvHelper { private readonly ICachedObjectVisitor<T, CsvWriter> _headerWriter; private readonly ICachedObjectVisitor<T, CsvWriter> _bodyWriter; public CsvHelper () { _headerWriter = default (T) .V() .WithExtendObject<T, CsvWriter>() .ForEach((name, value , w) => w.WriteHeader(name)) .Cache(); _bodyWriter = default (T) .V() .WithExtendObject<T, CsvWriter>() .ForEach((name, value , w) => w.WriteCell(value != null ? value .ToString() : "" )) .Cache(); } public string ToCsv (IEnumerable<T> items ) { var sb = new StringBuilder(); var csvWriter = new CsvWriter(new StringWriter(sb)); _headerWriter.Run(new T(), csvWriter); csvWriter.FinishHead(); foreach (var item in items) { _bodyWriter.Run(item, csvWriter); csvWriter.FinishRow(); } var re = sb.ToString(); return re; } } } }
与先前的FormatToStringExtensions
一样,此处采用的是泛型静态类配合扩展方法的形式来创建帮助方法。不同的是,在这个示例中存在两个 object visitor。因此多考虑抽象了一个ICsvHelper
和实现类来实现复杂逻辑的聚合。
输出时跳过特定的列 我们希望在输出 CSV 的时候跳过一些特定的列,这就需要对属性进行过滤。
我们增加一个新的 Attribute:
1 2 3 public class IgnoreAttribute : Attribute { }
然后将这个 Attribute 标记在不希望输出的属性上:
1 2 3 4 5 6 7 public class OrderInfo { public int OrderId { get ; set ; } [Ignore ] public string Buyer { get ; set ; } public decimal TotalPrice { get ; set ; } }
然后,我们只要在 object visitor 中忽略这些被标记为 Ingore 的属性即可。
其中核心的修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static bool Filter (PropertyInfo p ) => p.GetCustomAttribute<IgnoreAttribute>() == null ;_headerWriter = default (T) .V() .WithExtendObject<T, CsvWriter>() .FilterProperty(Filter) .ForEach((name, value , w) => w.WriteHeader(name)) .Cache(); _bodyWriter = default (T) .V() .WithExtendObject<T, CsvWriter>() .FilterProperty(Filter) .ForEach((name, value , w) => w.WriteCell(value != null ? value .ToString() : "" )) .Cache();
这样我们在输出 CSV 时也就不存在这一列了:
1 2 3 4 OrderId,TotalPrice 1,99999 2,36524 3,123456
总结 我们通过一个简单的生产实例来理解 object visitor 的用法。实际生产问题会比这个更加复杂。开发者可以在生产实际中进行尝试,强化理解。
发布说明 使用样例 开发文档可能随版本发生变化,查看最新的开发文档需移步 http://cn.ov.newbe.pro
番外分享 GitHub 项目地址:https://github.com/newbe36524/Newbe.ObjectVisitor
Gitee 项目地址:https://gitee.com/yks/Newbe.ObjectVisitor