如何配置CsvHelper以跳过MissingFieldFound行

15
public interface ICsvProductReaderConfigurationFactory
{
    Configuration Build();
}

public class CsvProductReaderConfigurationFactory : ICsvProductReaderConfigurationFactory
{
    private readonly ClassMap<ProductDto> classMap;

    public CsvProductReaderConfigurationFactory(IProductDtoClassMapProvider classMapProvider)
    {
        classMap = classMapProvider.Get();
    }

    public Configuration Build()
    {
        var config = new Configuration
        {
            Delimiter = "\t",
            HasHeaderRecord = true,
            IgnoreQuotes = true,
            MissingFieldFound = (rows, fieldIndex, readingContext) =>
                Log.Warn($"Missing Field Found at line {readingContext.Row}\r\n" +
                         $"Field at index {fieldIndex} does not exist\r\n" +
                         $"Raw record: {readingContext.RawRecord}"),
            BadDataFound = context => 
                Log.Warn($"Bad data found at row {context.Row}\r\n" +
                         $"Raw data: {context.RawRecord}")
        };

        config.RegisterClassMap(classMap);
        return config;
    }
}


public interface ICvsProductReader
{
    IEnumerable<ProductDto> GetAll(string filePath);
}

public class CvsProductReader : ICvsProductReader
{
    private readonly ICsvProductReaderConfigurationFactory csvProductReaderConfigurationFactory;

    public CvsProductReader(ICsvProductReaderConfigurationFactory csvProductReaderConfigurationFactory)
    {
        this.csvProductReaderConfigurationFactory = csvProductReaderConfigurationFactory;
    }

    public IEnumerable<ProductDto> GetAll(string filePath)
    {
        var csvReaderConfiguration = csvProductReaderConfigurationFactory.Build();

        using (var streamReader = new StreamReader(filePath))
        using (var csvReader = new CsvReader(streamReader, csvReaderConfiguration))
        {
            return csvReader.GetRecords<ProductDto>().ToArray();
        }
    }
}

MissingFieldFound 属性会在发现缺失字段时被调用,但不会影响结果。

我想知道是否可以配置 CsvHelper 跳过缺失字段的行。


2
csvHelper的文档中有这个。只需将其设置为null即可。 - user6144226
我们可以再加几行代码吗?也许是读取部分?你是在使用没有sGetRecord还是GetRecords - Drag and Drop
@user6144226,将MissingFieldFound设置为null只会关闭抛出异常,而不会过滤结果。 - Makrushin Evgenii
@拖放, 我在pastebin上分享了其余的读取器类:https://pastebin.com/u0wmssx7 - Makrushin Evgenii
我编辑了你的问题,所以代码不再是外部来源。现在更清晰了。 - Drag and Drop
5个回答

14

你做的没错,这里有一个MCVE展示一个完整的例子。

var good = new List<Test>();
var bad = new List<string>();

using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
using (var reader = new StreamReader(stream))
using (var csv = new CsvReader(reader))
{
    writer.WriteLine("FirstName,LastName");
    writer.WriteLine("\"Jon\"hn\"\",\"Doe\"");
    writer.WriteLine("\"JaneDoe\"");
    writer.WriteLine("\"Jane\",\"Doe\"");
    writer.Flush();
    stream.Position = 0;

    var isRecordBad = false;

    csv.Configuration.BadDataFound = context =>
    {
        isRecordBad = true;
        bad.Add(context.RawRecord);
    };

    csv.Configuration.MissingFieldFound = (headerNames, index, context) =>
    {
        isRecordBad = true;
        bad.Add(context.RawRecord);
    };

    while (csv.Read())
    {
        var record = csv.GetRecord<Test>();
        if (!isRecordBad)
        {
            good.Add(record);
        }

        isRecordBad = false;
    }
}

good.Dump();
bad.Dump();

你的解决方案在我的情况下运行良好。但我没有看到一种方法可以复制你的解决方案而不违反SRP。 - Makrushin Evgenii
1
@BanyRule 看起来让它符合SOLID原则真的很难,对于这个回归问题我感到很抱歉。ShouldSkipRecord可以加入一些功能,使其跳过缺失或错误的数据。目前GetRecords不支持此功能。您可以在https://github.com/JoshClose/CsvHelper/issues上提出功能请求。 - Drag and Drop
这似乎更像是一种解决方案,用于自己逐行读取行的情况。例如,在使用CsvDataReaderSqlBulkCopy的情况下,您只需将读取器实例提供给SqlBulkCopy(因为它实现了IDataReader),而不必自己处理读取。 - Laurynas Lazauskas
@LaurynasLazauskas,很抱歉已经过去了好几年。我可能需要更多上下文才能理解。但是你可以使用 GetRecords(带有S)并将 Configuration.BadDataFoundMissingFieldFound 设置为 null,以使用默认处理方式,而不需要手动使用 csv.Read()。没有理由忽略缺少字段的行,Configuration.MissingFieldFound 允许您设置更复杂的规则,以便您检查缺少哪个字段,从而知道应该如何处理:抛出异常、插入到另一个集合中、错误日志记录等。 - Drag and Drop
我正在使用 CsvHelper.CsvDataReader。 没有 GetRecordsGetRecord。 然后将此对象传递给 System.Data.SqlClient.SqlBulkCopy.WriteToServerAsync。 当出现丢失字段的情况时,它仍会被视为具有默认值的行并失败。 在这种情况下,我希望能够配置 CsvHelper 在 MissingFieldFound 中跳过行。但我认为这应该在 GitHub 问题中表达而不是在这里。 - Laurynas Lazauskas
如果您可以使用简单的CSV制定一个最小可重现示例(MRE),那么这里也是一个有效的问题。但是,您应该在两个网站上都发布问题,因为它们使用相同的标记语言,所以只需复制并粘贴,并从gitbug添加到相应的SO问题链接即可。我会支持SO作为主要问题,因为它的规则允许更好的问题,而大多数gitbug则不然。 - Drag and Drop

8

您还可以使用属性配置缺失的字段

[可选]

公共字符串字段 {get; set;}

或者

[忽略]

公共整型字段 {get; set;}


它起作用了。通过添加[Optional]属性解决了我的问题。我正在使用版本19.0.0.0。 - Zeeshan
你在哪里应用这个属性? - Kevin Hudson
@KevinHudson在属性的顶部。就像上面的例子一样。 - Jay

7

使用我所拥有的 csvhelper 新版本 (24.0.1),下面的代码将可用于设置 MissingFieldFound

Dim textReader As TextReader = File.OpenText(filename)

Dim config = New CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture)

        config.Delimiter = ","
        config.MissingFieldFound = Nothing
        config.TrimOptions = True
        config.HeaderValidated = Nothing

Dim csv = New CsvReader(textReader, config)

4

跳过 MissingFieldFound 行的另一种方法是使用 ShouldSkipRecord 并将头记录长度与行记录长度进行比较。

// Load header record if you haven't already (CsvDataReader loads it automatically).
csv.Read();
csv.ReadHeader();

// Then do this.
var expectedRecordLength = csv.Context.HeaderRecord.Length;
csv.Configuration.ShouldSkipRecord = rowRecord => rowRecord.Length != expectedRecordLength;

如果您无法控制读取代码(例如,使用 CsvDataReaderSqlBulkCopy),这将特别有用。


2
Abumoosa的回答对我有用,只是它是VB的。这是C#中的等效代码:
var config = new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture);    
config.MissingFieldFound = null;

using (var reader = new StreamReader("taskList.csv"))
using (var csv = new CsvReader(reader, config)) {
    csv.Read();
    csv.ReadHeader();
    while (csv.Read()){
       //get the record
    }

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