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

ASP.NET MVC中使用StreamReader解析CSV如何处理单元格内换行?

处理CSV中包含换行符的单元格问题

你遇到的这个问题其实很常见——CSV规范里允许单元格内容包含换行符,只要这个单元格是用双引号包裹起来的,但ReadLine()方法只会把换行符当作行的结束标志,完全不管它是不是在引号内部,所以才会把同一个单元格拆成两行。

你现在用的Regex.Split虽然尝试处理引号内的逗号,但因为已经被ReadLine()拆成了多行,所以根本没办法恢复原来的完整单元格。下面给你两种解决思路:


推荐方案:使用成熟的CSV解析库(比如CsvHelper)

自己手动处理CSV的各种边缘情况(比如引号内的换行、转义引号)很容易踩坑,不如直接用业界常用的库来搞定。CsvHelper是一个非常流行的.NET CSV处理库,对这种包含换行的单元格支持得很好。

步骤:

  1. 先通过NuGet安装CsvHelper包;
  2. 修改你的解析代码,示例如下:
using CsvHelper;
using System.Globalization;

public static List<T> ConvertCsvToDataTable<T>(StreamReader csvReader, ref Dictionary<int, List<string>> failures) where T : new()
{
    var objects = new List<T>();
    failures = new Dictionary<int, List<string>>();

    using var csv = new CsvReader(csvReader, CultureInfo.InvariantCulture);
    // 配置自动映射到你的实体类T(确保实体类属性名和CSV列名匹配,或者用特性指定)
    var records = csv.GetRecords<T>();

    int lineNumber = 2; // 表头是第一行,数据从第二行开始
    foreach (var record in records)
    {
        // 这里可以保留你的赋值校验逻辑,把失败记录存入failures
        var lineFailures = AssignValuesFromCsv(record, /* 若需要可从csv.Context获取行数据 */, columnIndex);
        if (lineFailures.Count > 0)
        {
            failures.Add(lineNumber, lineFailures);
        }
        objects.Add(record);
        lineNumber++;
    }

    return objects;
}

CsvHelper会自动处理引号内的换行、转义字符等所有CSV规范里的特殊情况,比自己写正则靠谱多了。


自定义处理方案(不依赖第三方库)

如果你不想引入第三方库,可以修改你的代码逻辑,不再用ReadLine()逐行读取,而是先判断当前行的引号是否成对,不成对的话就继续读取下一行拼接起来,直到引号成对为止。

修改后的核心逻辑示例:

public static List<T> ConvertCsvToDataTable<T>(StreamReader csvReader, ref Dictionary<int, List<string>> failures) where T : new()
{
    var objects = new List<T>();
    var columnIndex = new Dictionary<string, int>();
    var headerLine = csvReader.ReadLine();
    var headers = headerLine.Split(Convert.ToChar(","));
    for (var i = 0; i < headers.Length; i++)
    {
        columnIndex.Add(headers[i], i);
    }

    var lineNumber = 1;
    while (!csvReader.EndOfStream)
    {
        // 初始读取一行
        var line = csvReader.ReadLine();
        if (string.IsNullOrEmpty(line)) continue;

        // 检查当前行的双引号数量是否为奇数,如果是,说明单元格未结束,继续读下一行拼接
        while (line.Count(c => c == '"') % 2 != 0 && !csvReader.EndOfStream)
        {
            line += Environment.NewLine + csvReader.ReadLine();
        }

        var obj = new T();
        // 用原来的正则拆分逻辑处理完整的行
        var csvRow = Regex.Split(line, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
        var lineFailures = AssignValuesFromCsv(obj, csvRow, columnIndex);
        if (lineFailures.Count > 0) failures.Add(lineNumber, lineFailures);
        objects.Add(obj);
        
        lineNumber++;
    }
    return objects;
}

这里的关键是判断当前行的双引号数量是否为偶数——如果是奇数,说明这一行的引号没有闭合,意味着换行是单元格内的内容,需要把下一行拼接到当前行,直到引号成对为止。

不过要注意,这个自定义逻辑只处理了引号内的换行,还有一些其他CSV特殊情况(比如单元格内的转义双引号"")没处理,所以如果你的CSV有这些情况,还是推荐用CsvHelper这类成熟库。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 03:28:29