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

将实体导出为 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


java导出csv文件POI java导出csv文件给前端_java导出csv文件POI