CsvHelper和不可变类型

时间:2019-07-11 14:28:38

标签: c# csv

我有一个不可变的类,我想写入和读取CSV文件。问题是,尽管已映射对象并设置了允许其正常工作的配置,但在读取CSV时却出现了异常。

为此,我正在使用CsvHelper。不可变的类如下所示。

public class ImmutableTest
{
    public Guid Id { get; }

    public string Name { get; }

    public ImmutableTest(string name) : this(Guid.NewGuid(), name)
    {
    }

    public ImmutableTest(Guid id, string name)
    {
        Id = id;
        Name = name;
    }
}

将其写入CSV文件没有问题,但是当我尝试从文件中读取它时,出现以下异常。

  

没有为“ CsvTest.Program + ImmutableTest”类型映射成员

但是,我已经在下面的map类中映射了该类的成员。

public sealed class ImmutableTestMap : ClassMap<ImmutableTest>
{
    public ImmutableTestMap()
    {
        Map(immutableTest => immutableTest.Id)
            .Index(0)
            .Name(nameof(ImmutableTest.Id).ToUpper());

        Map(immutableTest => immutableTest.Name)
            .Index(1)
            .Name(nameof(ImmutableTest.Name));
    }
}

我还尝试通过以下配置将读取器配置为使用构造函数来构建对象。

Configuration config = new Configuration
{
    IgnoreBlankLines = true
};

config.RegisterClassMap<ImmutableTestMap>();
config.ShouldUseConstructorParameters = type => true;
config.GetConstructor = type => type.GetConstructors()
    .MaxBy(constructor => constructor.GetParameters().Length)
    .FirstOrDefault();

这似乎没有用。我要去哪里错了?


完整的MCVE .NET Framework控制台示例

安装软件包

Install-Package CsvHelper
Install-Package morelinq

示例控制台程序

using System;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using MoreLinq;

namespace CsvTest
{
    class Program
    {
        static void Main()
        {
            Configuration config = new Configuration
            {
                IgnoreBlankLines = true
            };

            config.RegisterClassMap<ImmutableTestMap>();
            config.ShouldUseConstructorParameters = type => true;
            config.GetConstructor = type => type.GetConstructors()
                .MaxBy(constructor => constructor.GetParameters().Length)
                .FirstOrDefault();

            const string filePath = "Test.csv";
            using (FileStream file = new FileStream(filePath, FileMode.Create))
            using (StreamWriter fileWriter = new StreamWriter(file))
            using (CsvSerializer csvSerializer = new CsvSerializer(fileWriter, config))
            using (CsvWriter csvWriter = new CsvWriter(csvSerializer))
            {
                csvWriter.WriteHeader<ImmutableTest>();
                csvWriter.NextRecord();
                csvWriter.WriteRecord(new ImmutableTest("Test 1"));
                csvWriter.NextRecord();
                csvWriter.WriteRecord(new ImmutableTest("Test 2"));
                csvWriter.NextRecord();
            }

            using (FileStream file = new FileStream(filePath, FileMode.Open))
            using (StreamReader fileReader = new StreamReader(file))
            using (CsvReader csvReader = new CsvReader(fileReader, config))
            {
                foreach (ImmutableTest record in csvReader.GetRecords<ImmutableTest>())
                {
                    Console.WriteLine(record.Id);
                    Console.WriteLine(record.Name);
                    Console.WriteLine();
                }
            }
        }

        public sealed class ImmutableTestMap : ClassMap<ImmutableTest>
        {
            public ImmutableTestMap()
            {
                Map(immutableTest => immutableTest.Id)
                    .Index(0)
                    .Name(nameof(ImmutableTest.Id).ToUpper());

                Map(immutableTest => immutableTest.Name)
                    .Index(1)
                    .Name(nameof(ImmutableTest.Name));
            }
        }

        public class ImmutableTest
        {
            public Guid Id { get; }

            public string Name { get; }

            public ImmutableTest(string name) : this(Guid.NewGuid(), name)
            {
            }

            public ImmutableTest(Guid id, string name)
            {
                Id = id;
                Name = name;
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

如果类型是不可变的,它将使用构造函数映射。您的构造函数变量名称必须与标题名称匹配。您可以使用import vehicle; class MainProgram { public static int main() { vehicle bicycle= new vehicle("BMX", 2, "4.5 Bar"); vehicle car = new vehicle("VW", 4, "1.9 Bar"); . . class vehicle { //Instance variables: private String name; private int tyrenumbers; private String tyrestyle; private boolean drive; 来做到这一点。

Configuration.PrepareHeaderForMatch

答案 1 :(得分:0)

所以这里的问题是类映射中没有ParameterMaps

使用上面的示例解决此问题的简单方法是编写类似

的内容
public ImmutableTestMap()
{
    AutoMap();

    Map(immutableTest => immutableTest.Id)
        .Index(0)
        .Name(nameof(ImmutableTest.Id).ToUpper());
}

但这会导致一个问题,即CSV文件中的列标题为IDName,而生成的参数映射为idname。它们彼此不相等,因此读者抛出错误,说找不到名称为ID的列。它还说您可以将头信息验证为null。玩完这个之后,我最终完成了配置

Configuration config = new Configuration
{
    IgnoreBlankLines = true,
    ShouldUseConstructorParameters = type => true,
    GetConstructor = type => type.GetConstructors()
        .MaxBy(constructor => constructor.GetParameters().Length)
        .FirstOrDefault(),
    HeaderValidated = null,
    MissingFieldFound = null
};

config.RegisterClassMap<ImmutableTestMap>();

正在尝试解析空字段,并且无法将其转换为GUID。所以看起来这条路已经死了。

为解决此问题,我查看了检查自动映射生成的每个参数映射的“ id”值,然后将其替换为“ ID”。但是,这也不起作用,因为自动生成的地图是使用空名称生成的。仅在在配置中注册地图时调用SetMapDefaults时才分配名称。

所以我盲目地将参数映射的名称设置为显式定义的成员映射。

public ImmutableTestMap()
{
    AutoMap();
    Map(immutableTest => immutableTest.Id)
        .Index(0)
        .Name(nameof(ImmutableTest.Id).ToUpper());

    Map(immutableTest => immutableTest.Name)
        .Index(0)
        .Name(nameof(ImmutableTest.Id));

    if (MemberMaps.Count != ParameterMaps.Count)
    {
        return;
    }

    for (int i = 0; i < MemberMaps.Count; i++)
    {
        ParameterMaps[i].Data.Name = MemberMaps[i].Data.Names[0];
    }
}

但是,我想说的是,这不只是一个解决之道,而且还可以解决其他问题。