You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# CSVHelper读取CSV空字段报错 如何配置ShouldSkipRecord跳过异常

CsvHelper读取CSV空字段抛异常修复方案

现有代码不生效的核心问题

  • 你定义的CsvConfiguration实例没有传入CsvReader构造函数,所有自定义配置完全未生效,程序全程使用CsvHelper默认配置运行。
  • 你在字段映射上加的.Optional()作用是CSV表头不存在对应列时不触发缺失异常,无法处理行内字段为空、类型转换失败的场景。
  • CsvConfiguration不存在Encoding属性,你写在配置里的编码设置完全无效,编码需要在初始化StreamReader时指定。
  • 实体类中int类型的LineNumber既没有配置映射规则,也没有标记忽略,CsvHelper默认会自动匹配同名列,空值/缺列时会触发类型转换异常。

正确配置方法(含ShouldSkipRecord用法)

1. 补全ClassMap配置

给不需要从CSV读取的字段加忽略标记,避免自动映射出错:

public class WheelProsAccessoriesInventoryItemMap : ClassMap<WheelProsAccessoriesInventoryItem>
{
    public WheelProsAccessoriesInventoryItemMap()
    {
        Map(m => m.PartNumber).Name("PartNumber");
        Map(m => m.Brand).Name("Brand");
        Map(m => m.PartDescription).Name("PartDescription");

        Map(m => m.CapHardwareDescription).Name("CapHardwareDescription").Optional();
        Map(m => m.CapScrewQuantity).Name("CapScrewQuantity").Optional();
        Map(m => m.CapWrench).Name("CapWrench").Optional();
        Map(m => m.CapStyleDescription).Name("CapStyleDescription").Optional();

        Map(m => m.ImageURL).Name("ImageURL");
  
        Map(m => m.TotalQOH).Name("TotalQOH");
        Map(m => m.Msrp).Name("MSRP");
        Map(m => m.Map).Name("MAP");
        Map(m => m.RunDate).Name("RunDate"); 

        Map(m => m.StockLa1).Name("1011");
        Map(m => m.StockLa2).Name("1031");
        Map(m => m.StockLa3).Name("1085");
        Map(m => m.StockTX).Name("1086");
        Map(m => m.StockLangley).Name("4033");
        Map(m => m.StockToronto).Name("4035");
        Map(m => m.num1088).Name("1088"); 

        Map(m => m.num1015).Name("1015").Optional();
        Map(m => m.num1019).Name("1019").Optional();
        Map(m => m.num1022).Name("1022").Optional();
        Map(m => m.num1028).Name("1028").Optional();
        Map(m => m.num1036).Name("1036").Optional();
        Map(m => m.num1071).Name("1071").Optional();
        Map(m => m.num1072).Name("1072").Optional();

        // 不需要从CSV映射的字段直接标记忽略,避免自动匹配报错
        Map(m => m.LineNumber).Ignore();
    }
}

2. 修正读取逻辑,正确传入配置+使用ShouldSkipRecord

ShouldSkipRecord委托返回true时会直接跳过当前行,你可以在这里自定义无效行判断规则(比如核心必填字段为空、行字段数和表头不匹配时直接跳过),同时把缺失字段、坏数据的异常回调配置好,避免抛异常中断读取:

public static IEnumerable<T> GetRecords<T, TMap>(string filename, bool hasFrenchChars = false)
    where T : class
    where TMap : ClassMap<T>
{
    var errorRecsCollection = new List<string>();
    // 先初始化正确的Csv配置
    var config = new CsvConfiguration(CultureInfo.InvariantCulture)
    {
        Delimiter = ",",
        HasHeaderRecord = true,
        IgnoreBlankLines = true,
        // 缺字段时不抛异常
        MissingFieldFound = null,
        // 读取到坏数据时记录日志,不抛异常
        BadDataFound = context =>
        {
            errorRecsCollection.Add(context.RawRecord);
        },
        // 自定义跳过规则,返回true的行直接跳过
        ShouldSkipRecord = context =>
        {
            // 规则1:行字段数和表头列数不一致时跳过
            if (context.Record.Length != context.Parser.Count)
            {
                errorRecsCollection.Add($"行{context.Row.Parser.Row}字段数不匹配:{string.Join(",", context.Record)}");
                return true;
            }
            // 规则2:核心必填字段PartNumber为空时跳过
            var partNumberIndex = context.Parser.GetFieldIndex("PartNumber");
            if (partNumberIndex >=0 && string.IsNullOrWhiteSpace(context.Record[partNumberIndex]))
            {
                errorRecsCollection.Add($"行{context.Row.Parser.Row}PartNumber为空:{string.Join(",", context.Record)}");
                return true;
            }
            return false;
        }
    };

    // 编码在StreamReader初始化时指定,不要写在CsvConfiguration里
    var encoding = hasFrenchChars ? Encoding.GetEncoding("ISO-8859-1") : Encoding.UTF8;
    using (var streamReader = new StreamReader(filename, encoding))
    {
        // 把配置传入CsvReader构造函数,这步是之前配置不生效的核心原因
        using (var csvReader = new CsvReader(streamReader, config))
        {
            csvReader.Context.RegisterClassMap<TMap>();
            return csvReader.GetRecords<T>().ToList();
        }
    }
}

额外注意事项

  • 如果后续需要给LineNumber赋值行号,直接在映射里用Convert配置即可,不需要让CsvHelper去CSV里匹配列:
    Map(m => m.LineNumber).Convert(row => row.Row.Parser.Row);
    
  • 所有非string类型的字段如果允许空值,一定要定义成可空类型(比如int?、decimal?),否则空值转换时会触发类型转换异常。
  • 读取大文件时不要直接用.ToList()一次性加载到内存,可以逐行遍历GetRecords<T>()返回的迭代器,降低内存占用。

内容的提问来源于stack exchange,提问作者Lucas DJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:21:25