如何使用CSV Helper导出多层嵌套对象的非重复CSV格式数据?
多层级对象导出CSV(顶层数据去重)
问题背景
给定以下C#类结构:
public class Person { public int Id { get; set; } public string Name { get; set;} public List<Book> Books { get; set; } } public class Book { public Guid Id { get; set; } public string Name { get; set;} public List<Chapter> Chapters{ get; set; } public List<Author> Authors { get; set; } } public class Author { public string Name { get; set; } } public class Chapter { public string Name { get; set;} }
需要导出CSV文件,要求顶层Person的personId和Name字段不重复,关联的Book、Author、Chapter对应多行展示,重复的上层字段留空,示例格式如下:
personId,Name,BookName,AuthorName,ChapterName 1,name1,Book1Name,AuthorName1,chapterName1 ,,,AuthorName2, ,,Book2Name,AuthorName1,chapterName1 2,name2,Book3Name,AuthorName3,chapterName3 3,name3,Book1Name,,chapterName4 ,,,,chapterName5
解决方案
要实现这种格式,核心是遍历所有层级对象,为每个需单独成行的节点构建CSV记录,重复的上层字段留空。以下是手动构建记录并写入CSV的实现代码:
实现代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; public class CsvExporter { public void ExportPersonsToCsv(List<Person> persons, string filePath) { // 定义CSV表头 var headers = new List<string> { "personId", "Name", "BookName", "AuthorName", "ChapterName" }; using (var writer = new StreamWriter(filePath)) { // 写入表头 writer.WriteLine(string.Join(",", headers)); foreach (var person in persons) { bool isFirstPersonRow = true; foreach (var book in person.Books ?? Enumerable.Empty<Book>()) { bool isFirstBookRow = true; // 先处理当前书籍的所有作者行 foreach (var author in book.Authors ?? Enumerable.Empty<Author>()) { var row = new List<string>(); // 填充Person字段:仅第一行写入,后续留空 if (isFirstPersonRow && isFirstBookRow) { row.Add(person.Id.ToString()); row.Add(person.Name ?? string.Empty); isFirstPersonRow = false; } else { row.Add(string.Empty); row.Add(string.Empty); } // 填充Book字段:仅当前书籍的第一行写入,后续留空 row.Add(isFirstBookRow ? (book.Name ?? string.Empty) : string.Empty); isFirstBookRow = false; // 填充Author和Chapter字段 row.Add(author.Name ?? string.Empty); row.Add(string.Empty); // 字段加双引号,避免含逗号导致格式错误 writer.WriteLine(string.Join(",", row.Select(f => $"\"{f}\""))); } // 再处理当前书籍的所有章节行 foreach (var chapter in book.Chapters ?? Enumerable.Empty<Chapter>()) { var row = new List<string> { string.Empty, string.Empty, string.Empty, string.Empty, chapter.Name ?? string.Empty }; writer.WriteLine(string.Join(",", row.Select(f => $"\"{f}\""))); } } } } } } // 使用示例 // var persons = new List<Person> { /* 初始化你的数据 */ }; // var exporter = new CsvExporter(); // exporter.ExportPersonsToCsv(persons, @"C:\output.csv");
代码说明
- 表头写入:先定义CSV列名,写入文件第一行;
- 层级遍历逻辑:
- 遍历每个Person,标记是否为该Person的首行;
- 遍历每个Book,标记是否为该Book的首行;
- 先处理Book下的所有Author:首行填充Person和Book字段,后续仅填充Author字段;
- 再处理Book下的所有Chapter:所有上层字段留空,仅填充Chapter字段;
- 格式兼容:每个字段用双引号包裹,避免字段含逗号导致CSV格式紊乱。
如果需要调整Author和Chapter的关联逻辑(比如每个Author对应所有Chapter),只需修改内层循环的组合方式即可。
内容的提问来源于stack exchange,提问作者Morks
相关产品推荐
相关产品推荐

