java 0x05_0x05 - 综合示例,导出 CSV

现在,我们来完成一个稍微复杂一点的场景用例。

将实体导出为 CSV 文件

为了使下文的示例更加符合生产实际,我们在这里引入一个具体的场景。

我们需要将实体导出为 CSV 文件。

CSV 文件一般包含两个部分。

第一个部分是文件的头部。头部中包含了每一列的列名。

第二个部分是内容部分。内容部分每行都是一条记录。

不论是头部还是内容部分每个属性之间都使用逗号进行分隔。

这里我们使用前文使用到的OrderInfo进行演示:

public class OrderInfo

{

public int OrderId { get; set; }

public string Buyer { get; set; }

public decimal TotalPrice { get; set; }

}

则导出的 CSV 样例如下:

OrderId,Buyer,TotalPrice

1,yueluo,99999

2,newbe36524,36524

3,traceless,123456

分析实现思路

这个业务场景的实现方式可以非常多样化,此处我们简要将逻辑分为以下部分:

使用 object visitor 访问OrderInfo的所有属性

将所有属性传递给 CSV Writer 进行输出

实现 CSV 写入器

首先,我们先添加第 2 步所需要的 CSV 写入器:

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, 我们便可以首先来生成一个样例中的表格:

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 来输出表头:

var sb = new StringBuilder();

var csvWriter = new CsvWriter(new StringWriter(sb));

default(OrderInfo)

.V()

.WithExtendObject()

.ForEach((name, value, w) => w.WriteHeader(name))

.Run(new OrderInfo(), csvWriter);

csvWriter.FinishHead();

Console.WriteLine(sb.ToString());

这样我就会得到如下的结果:

OrderId,Buyer,TotalPrice

输出表行

现在,我们在增加一个 object visitor 来输出表的每行内容:

var rowWriter = default(OrderInfo)

.V()

.WithExtendObject()

.ForEach((name, value, w) => w.WriteCell(value != null ? value.ToString() : ""))

.Cache();

var orders = new List

{

new OrderInfo

{

OrderId = 1,

Buyer = "yueluo",

TotalPrice = 99999M

},

new OrderInfo

{

OrderId = 2,

Buyer = "newbe36524",

TotalPrice = 36524M

},

new OrderInfo

{

OrderId = 3,

Buyer = "traceless",

TotalPrice = 123456M

}

};

foreach (var order in orders)

{

rowWriter.Run(order, csvWriter);

csvWriter.FinishRow();

}

Console.WriteLine(sb.ToString());

这样就会得到如下的内容:

1,yueluo,99999

2,newbe36524,36524

3,traceless,123456

这正是我们期望的表中的行数据。

创建 CsvExtensions

现在,我们将以上的表头和表行的相关逻辑进行整合,将他们全部都添加到一个 CsvExtensions 的类型中。并且增加对于IEnumerable的扩展方法,这样在进行调用时就会更加简单。这将仿照先前FormatToStringExtensions中的做法。

public static class CsvExtensions

{

public static string ToCsv(this IEnumerable items)

where T : new()

{

var re = CsvFilerHelper.Instance.ToCsv(items);

return re;

}

private static class CsvFilerHelper

where T : new()

{

internal static readonly ICsvHelper Instance = new CsvHelper();

public interface ICsvHelper

{

string ToCsv(IEnumerable items);

}

private class CsvHelper : ICsvHelper

{

private readonly ICachedObjectVisitor _headerWriter;

private readonly ICachedObjectVisitor _bodyWriter;

public CsvHelper()

{

_headerWriter = default(T)

.V()

.WithExtendObject()

.ForEach((name, value, w) => w.WriteHeader(name))

.Cache();

_bodyWriter = default(T)

.V()

.WithExtendObject()

.ForEach((name, value, w) => w.WriteCell(value != null ? value.ToString() : ""))

.Cache();

}

public string ToCsv(IEnumerable 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:

public class IgnoreAttribute : Attribute

{

}

然后将这个 Attribute 标记在不希望输出的属性上:

public class OrderInfo

{

public int OrderId { get; set; }

[Ignore]

public string Buyer { get; set; }

public decimal TotalPrice { get; set; }

}

然后,我们只要在 object visitor 中忽略这些被标记为 Ingore 的属性即可。

其中核心的修改如下:

static bool Filter(PropertyInfo p) => p.GetCustomAttribute() == null;

_headerWriter = default(T)

.V()

.WithExtendObject()

.FilterProperty(Filter)

.ForEach((name, value, w) => w.WriteHeader(name))

.Cache();

_bodyWriter = default(T)

.V()

.WithExtendObject()

.FilterProperty(Filter)

.ForEach((name, value, w) => w.WriteCell(value != null ? value.ToString() : ""))

.Cache();

这样我们在输出 CSV 时也就不存在这一列了:

OrderId,TotalPrice

1,99999

2,36524

3,123456

总结

我们通过一个简单的生产实例来理解 object visitor 的用法。实际生产问题会比这个更加复杂。开发者可以在生产实际中进行尝试,强化理解。

发布说明

Newbe.ObjectVisitor 0.4.4 发布,模型验证器上线

Newbe.ObjectVisitor 0.3.7 发布,自动生成 FluentAPI

Newbe.ObjectVisitor 0.2.10 发布,更花里胡哨

Newbe.ObjectVisitor 0.1.4 发布,初始版本

使用样例

Newbe.ObjectVisitor 样例 1

0x01 - 我的第一个 Object Visitor

0x02 - 创建并缓存 Object Visitor

0x03-ForEach 全面观

0x04 - 过滤属性

0x05 - 综合示例,导出 CSV

开发文档可能随版本发生变化,查看最新的开发文档需移步http://cn.ov.newbe.pro

番外分享

寻找性能更优秀的动态 Getter 和 Setter 方案

寻找性能更优秀的不可变小字典

我画着图,FluentAPI 她自己就生成了

GitHub 项目地址:https://github.com/newbe36524/Newbe.ObjectVisitor

Gitee 项目地址:https://gitee.com/yks/Newbe.ObjectVisitor

newbe_object_visitor_banner.svg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值