不会为嵌套集合生成 CSVHelper 标头

CSVHelper headers wont get generated for nested collection

提问人:G J 提问时间:1/12/2022 最后编辑:G J 更新时间:1/13/2022 访问量:396

问:

尝试将列表转换为CSV文件,我在其中嵌套了集合。

当 CSV 读入对象集合时,转换可以完美工作,但当使用相同的集合使用相同的映射生成 CSV 时,不会生成所有标头。无法弄清楚我做错了什么,生成除名为列的集合之外的所有列,例如.Children, GrandChildren

using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Xunit;

namespace CSVHelperNugetHierarchyTest
{
    class Program
    {
        static List<Parent> Parents;
        static List<string> Headers;
        static void Main(string[] args)
        {
            using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(GetCSV())))
            {
                using (var reader = new StreamReader(stream))
                {
                    using (var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.GetCultureInfo("en-US")) { HasHeaderRecord = true }))
                    {
                        csv.Context.TypeConverterCache.AddConverter<List<Child>>(new ChildrenConverter());
                        csv.Context.TypeConverterCache.AddConverter<List<GrandChild>>(new GrandChildrenConverter());
                        csv.Context.RegisterClassMap<ParentMapper>();
                        csv.Context.RegisterClassMap<ChildMapper>();
                        csv.Context.RegisterClassMap<GrandChildMapper>();
                        Parents = csv.GetRecords<Parent>().ToList();
                        Headers = csv.HeaderRecord.ToList();
                    }
                }
            }

            using (var stream = new MemoryStream())
            {
                using (var writer = new StreamWriter(stream))
                {
                    using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.GetCultureInfo("en-US")) { HasHeaderRecord = true }))
                    {
                        csv.Context.TypeConverterCache.AddConverter<List<Child>>(new ChildrenConverter());
                        csv.Context.TypeConverterCache.AddConverter<List<GrandChild>>(new GrandChildrenConverter());
                        csv.Context.RegisterClassMap<ParentMapper>();
                        csv.Context.RegisterClassMap<ChildMapper>();
                        csv.Context.RegisterClassMap<GrandChildMapper>();
                        csv.WriteRecords(Parents);
                    }
                }
                var streamBuffer = stream.ToArray();
                var content = Encoding.UTF8.GetString(streamBuffer);
                foreach (var item in Headers)
                {
                    Assert.Contains(item, content);
                }
            }
        }

        static string GetCSV()
        {
            return "Property0,Property1,Property2,Property3,Property4"
                + Environment.NewLine
                + "a,b,c,d,e";
        }
    }
    class Parent
    {
        public string Property0 { get; set; }
        public string Property1 { get; set; }
        public List<Child> Children { get; set; }
    }
    class Child
    {
        public string Property2 { get; set; }
        public string Property3 { get; set; }
        public List<GrandChild> GrandChildren { get; set; }
    }
    class GrandChild
    {
        public string Property4 { get; set; }
    }
    class ParentMapper : ClassMap<Parent>
    {
        public ParentMapper()
        {
            Map(m => m.Property0);
            Map(m => m.Property1);
            Map(m => m.Children).Index(0);
        }
    }
    class ChildMapper : ClassMap<Child>
    {
        public ChildMapper()
        {
            Map(m => m.Property2);
            Map(m => m.Property3);
            Map(m => m.GrandChildren).Index(0);
        }
    }
    class GrandChildMapper : ClassMap<GrandChild>
    {
        public GrandChildMapper()
        {
            Map(m => m.Property4);
        }
    }
    class ChildrenConverter : ITypeConverter
    {
        public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            var children = new List<Child>();
            children.Add(row.GetRecord<Child>());
            return children;
        }

        public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
        {
            var children = value as List<Child>;
            foreach (var child in children)
            {
                row.WriteRecord(child);
            }
            return null;
        }
    }
    class GrandChildrenConverter : ITypeConverter
    {
        public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
        {
            var children = new List<GrandChild>();
            children.Add(row.GetRecord<GrandChild>());
            return children;
        }

        public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
        {
            var children = value as List<GrandChild>;
            foreach (var child in children)
            {
                row.WriteRecord(child);
            }
            return null;
        }
    }
}

收到的输出:

Property0,Children,Property1
a,c,e,d,b

预期输出:

Property0,Property1,Property2,Property3,Property4
a,b,c,d,e

我在 .NET Core 3.1 环境中使用 CsvHelper 版本。27.2.1

尝试了很多方法,例如将列、索引和列 + 索引一起添加。

PS:省略了一些空检查和验证。

C# .net-core 标头 嵌套列表 csvhelper

评论

0赞 David Specht 1/12/2022
你只会有 1 个孩子和 1 个孙子吗?如果是这样,为什么有 和 ?如果每个孩子和孙子都有多个孩子和孙子,则复杂性将会增加。List<Child>List<Grandchild>
0赞 G J 1/13/2022
实际上,我会在 和 中使用半列分隔值。在我的版本中,我正在转换器中解析。;ChildrenGrandChildren
0赞 David Specht 1/13/2022
我认为不可能在 CSV 文件中有一个 and。您是否尝试过使用分隔值来阅读它?你怎么知道有多少属于第一个.是 1、3、100 吗?如果有解决方案,我认为是手动读取每一行并创建记录。List<Child>List<Grandchild>;GrandChildrenChildGrandChildren

答: 暂无答案