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

如何用SqlBulkCopy异步从Stream读取Excel并导入SQL Server?

可行实现方案:流式处理XLSX并批量写入SQL Server

核心思路是流式读取XLSX内容,同时通过SqlBulkCopy异步写入数据库,完全规避磁盘存储、全内存加载的问题,且基于开源工具实现。

关键依赖选择

使用EPPlus(MIT开源协议,非商业场景无限制;商业场景若有顾虑可替换为Apache协议的NPOI),两者均支持从Stream流式读取XLSX,且提供异步API,无需将整个文件加载到内存。

分步实现代码示例

1. Web API控制器接收文件流

从MultipartReader直接获取文件流,保持流的打开状态直到处理完成:

[HttpPost("upload-xlsx")]
public async Task<IActionResult> UploadXlsx()
{
    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        _defaultFormOptions.MultipartBoundaryLengthLimit);
    var reader = new MultipartReader(boundary, Request.Body);

    var section = await reader.ReadNextSectionAsync();
    while (section != null)
    {
        var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
        if (hasContentDispositionHeader && contentDisposition.IsFileDisposition())
        {
            await ProcessXlsxStreamAsync(section.Body);
            return Ok("导入完成");
        }
        section = await reader.ReadNextSectionAsync();
    }
    return BadRequest("未找到有效文件");
}

2. 流式读取XLSX并批量写入数据库

通过自定义DataReader实现逐行流式提供数据给SqlBulkCopy,避免全量加载:

private async Task ProcessXlsxStreamAsync(Stream xlsxStream)
{
    using var package = new ExcelPackage(xlsxStream);
    var worksheet = package.Workbook.Worksheets.First();
    
    var connectionString = "Your_SQL_Server_Connection_String";
    const int batchSize = 1000; // 可根据内存情况调整批次大小
    var rowCount = worksheet.Dimension.Rows;
    var columnCount = worksheet.Dimension.Columns;

    using var connection = new SqlConnection(connectionString);
    await connection.OpenAsync();
    
    using var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, null)
    {
        DestinationTableName = "Target_Table_Name",
        BatchSize = batchSize
    };

    // 映射Excel表头与数据库列(假设表头名称与列名一致)
    for (int col = 1; col <= columnCount; col++)
    {
        var columnName = worksheet.Cells[1, col].Text;
        bulkCopy.ColumnMappings.Add(columnName, columnName);
    }

    // 传入自定义流式DataReader
    var dataReader = new ExcelStreamingDataReader(worksheet, rowCount, columnCount);
    await bulkCopy.WriteToServerAsync(dataReader);
}

// 自定义DataReader,流式读取Excel行数据
public class ExcelStreamingDataReader : IDataReader
{
    private readonly ExcelWorksheet _worksheet;
    private readonly int _totalRows;
    private readonly int _columnCount;
    private int _currentRow = 1; // 跳过表头,从第二行开始读取

    public ExcelStreamingDataReader(ExcelWorksheet worksheet, int totalRows, int columnCount)
    {
        _worksheet = worksheet;
        _totalRows = totalRows;
        _columnCount = columnCount;
    }

    public async Task<bool> ReadAsync(CancellationToken cancellationToken)
    {
        _currentRow++;
        return _currentRow <= _totalRows;
    }

    public object GetValue(int i)
    {
        var cellValue = _worksheet.Cells[_currentRow, i + 1].Text;
        return string.IsNullOrEmpty(cellValue) ? DBNull.Value : cellValue;
    }

    // 实现IDataReader必要接口方法
    public int FieldCount => _columnCount;
    public bool Read() => ReadAsync(CancellationToken.None).Result;
    // 按需完善其他接口方法(如Close、Dispose等)
}

方案优势

  • 无磁盘存储:直接从请求流读取处理,全程无需落地文件
  • 全异步支持:从文件接收、XLSX解析到数据库写入均为异步操作
  • 低内存占用:通过批次写入+流式DataReader,仅加载当前批次数据到内存
  • 开源免费:基于EPPlus/NPOI开源工具,无商业授权限制

注意事项

  • 若使用EPPlus 5+版本,需确认项目符合其授权规则(非商业场景免费,商业场景需购买授权;若有顾虑直接替换为NPOI)
  • 自定义DataReader需完整实现IDataReader接口的必要方法,避免运行时异常
  • 批次大小可根据服务器内存配置调整,平衡内存占用与写入效率

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 21:45:50