CSVHelper:使用IndexAttribute写出空字段

qf9go6mv  于 2023-02-14  发布在  其他
关注(0)|答案(2)|浏览(105)

我们有一个固定的输出记录格式,其中所有字段都是竖线分隔的。布局是固定的,但必填字段不是按顺序排列的(这是无法更改的,因为客户正在使用现有布局)。我们是否可以使用CSVHelper和索引属性写出空白字段,例如[索引(1)]、[索引(4)]、[索引(7)]将生成如下内容:

"Field1Value,,,Field2Value,,,Field3Value"?

我们最终可能会添加缺失的字段,但必须分阶段考虑(因为客户的暗示),但希望能够使用必需的字段值创建最小的输出记录格式。
我们正在与.Net Core 2.2/C#合作。
我看了几个nuget和其他支持用[Column(xx)]和[FieldOrder(xx)]等数据属性装饰属性的库,但最终选择了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
hec6srdp

hec6srdp1#

如果可以的话,我会把属性添加到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,然后把它Map到继承类并使用它来编写:

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}"));
        }
    }
}
qmelpv7a

qmelpv7a2#

我也遇到了同样的问题。这是我的解决方案:

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("");
            }
        }
    }
}

相关问题