CSVHelper: 使用IndexAttribute将空字段写入输出

3
我们有一个固定的输出记录格式,其中所有字段都由管道符分隔。布局是固定的,但所需字段不按顺序排列(这不能更改,因为客户正在使用现有布局)。我们能否使用CSVHelper和Index属性来写出空字段,例如[Index(1)],[Index(4)],[Index(7)]将产生类似以下内容的结果:
"Field1Value,,,Field2Value,,,Field3Value"?

我们可能会逐步添加缺失的字段(由于客户的影响,需要逐步考虑),但希望能够创建具有所需字段值的最小输出记录格式。

我们正在使用 .Net Core 2.2/C# 进行开发。

我查看了几个支持使用数据属性(如 [Column(xx)] 和 [FieldOrder(xx)])装饰属性的 nuget 和其他库,但最终选择了 CSVHelper,因为它似乎非常强大且被广泛使用。

public class Person
{
    [Index(1)]
    public string Name { get; set; }
    [Index(4)]
    public short Age { get; set; }
    [Index(7)]
    public string StreetAddress { get; set; }
}

static void Main(string[] args)
{
    var records = new List<Person>
    {
        new Person { Name = "Jon Doe", Age = 100, StreetAddress = "123 Market Place" }
    };

    using (var writer = new StreamWriter("C:\\Projects\\CsvHelperDemo\\file.csv"))
    using (var csv = new CsvWriter(writer))
    {
        csv.WriteRecords(records);
    }
}

期望的结果:

Name,,,Age,,,StreetAddress
Jon Doe,,,100,,,123 Market Place

实际情况:

Name,Age,StreetAddress
Jon Doe,100,123 Market Place

为此,您应该使用字符串构建器编写自己的CSV编写器。首先,您可以添加所需字段数量的标题,然后执行for循环并添加数据。 - Claudio Corchez
CsvHelper非常方便读取各种数据类型。它也适用于写入,但编写CSV writer非常容易。毫不夸张地说,我认为它几乎可以放在注释中。 - Scott Hannen
2个回答

3
如果可以的话,我会将这些属性添加到Person类中。如果它们没有被填充,那么你只会有列名而没有数据。这有点奇怪,但与“空白”列名和空列并没有太大区别。
但是,如果你想得到完全符合你所描述的结果,你可以这样做:
public class Person
{
    [Index(0)]
    public string Name { get; set; }

    [Index(3)]
    public short Age { get; set; }

    [Index(6)]
    public string StreetAddress { get; set; }

    #region For spacing only

    [Name("")]
    [Index(1)]
    public string Abc { get; set; }

    [Name("")]
    [Index(2)]
    public string Xyz { get; set; }

    [Name("")]
    [Index(4)]
    public string Foo { get; set; }

    [Name("")]
    [Index(5)]
    public string Blarg { get; set; }

    #endregion
}

我使用了虚假的名称,您可以使用真实姓名代替。这并不重要,因为列标题将为空。

一个缺点是您可能会开始填充您现在未使用的其中一个额外属性,然后该列将被填充而没有标题,除非您记得返回并删除[Name(" ")]属性。

您也可以创建一个独立的继承类而不改变Person。这样,您就可以填充Person,然后将其映射到继承类并将其用于编写:

public class PersonWithExtraFields : Person
{
    [Name("")]
    [Index(1)]
    public string Abc { get; set; }

    [Name("")]
    [Index(2)]
    public string Xyz { get; set; }

    [Name("")]
    [Index(4)]
    public string Foo { get; set; }

    [Name("")]
    [Index(5)]
    public string Blarg { get; set; }
}

这是使用CsvHelper进行操作的字面答案。这里有一种更简单的方法:
public class PersonCsvWriter
{
    public void Write(List<Person> people, string path)
    {
        using (var file = new StreamWriter(path))
        {
            file.WriteLine("Name,,,Age,,,StreetAddress");
            people.ForEach(p => file.WriteLine($"{p.Name},,,{p.Age},,,{p.StreetAddress}"));
        }
    }
}

0

我遇到了完全相同的问题。这是我的解决方案:

public class Person
{
    [Index(1)]
    public string Name { get; set; }
    [Index(4)]
    public short Age { get; set; }
    [Index(7)]
    public string StreetAddress { get; set; }
}

static void Main(string[] args)
{
    var records = new List<Person>
    {
        new Person { Name = "Jon Doe", Age = 100, StreetAddress = "123 Market Place" }
    };

    using (var writer = new StreamWriter("C:\\Projects\\CsvHelperDemo\\file.csv"))
    using (var csv = new CsvWriter(writer))
    {
        csv.Context.RegisterClassMap<DynamicMap<Person>>();
        csv.WriteRecords(records);
    }
}

internal sealed class DynamicMap<T> : ClassMap<T>
{
    public DynamicMap()
    {
        AutoMap(CultureInfo.InvariantCulture);

        var properties = typeof(T).GetProperties();
        var indexAttributes = properties.SelectMany(x => x.GetCustomAttributes(typeof(IndexAttribute), false))
            .Cast<IndexAttribute>().ToList();

        var maxIndex = indexAttributes.Max(x => x.Index);
        for (int i = 0; i < maxIndex; ++i)
        {
            if (indexAttributes.All(x => x.Index != i))
            {
                Map().Index(i).Constant("");
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接