Excel数据导出小记二: [大数据示例]

文章目录

前情

.net8.0需从excel下载500MB以上的Excel。记录一下过程。
Excel数据导出小记[链接],之前记录了三种方式:

1、前端导出:远程服务器数据传输本地过慢

2、后端分页导出:SQLServer版本低分页麻烦,过于依赖建表、清表

3、后端批量导出:NOPI 导出

本文在记录在第三种情况下的小案例。

流程

导出流程分 业务流程、文件流程。

1、业务流程: 请求=> 数据查询=>查询结果(DataReader)=>workbook=>回收ODBC返回结果(查询成功正在下载)

2、文件流程: 查询结果(DataReader)=>workbook=> 保存服务器本地=>移动到共享文件服务器。

思路1、为什么流程分两个?

如果是一个线程,在保存完成之前,odbc的connect和前端request将一直被占用。

不如请求完了,异步保存。

思路2、为什么返回结果之前先将数据写入workbook?

因为 DataReader在 connect关闭后回收。所以在断开数据库之前,必须将数据写入内存,以供之后保存。

思路3、为什么要保存服务器本地?

API服务器(A)、文件服务器(B)

如新建到B,从workbook中写到excel,远程传输很慢,制约于A、B之间的网络正常。

而新建到A,再剪切到B,对网络依赖更低。更稳定。

思路4、为什么不直接保存到我的电脑

数据库、API服务器、文件服务器在同一网段,是局域网内的数据移动。

比api传输JSON,再本地写进EXCEL。效率快一倍有余。

存进共享文件服务器,也可降低同一工作的重复、协作文件传输频率。

数据查询实现

csharp 复制代码
public static async Task<bool> OutPutExcel(SqlConnection connection, CommandType cmdType, List<CmdData> list, string extra)
{
    var workbook = new SXSSFWorkbook(null, 100, true); //NPOI  XSSF
    //定义路径tempPath 为api服务器临时路径   extra 为前端请求保存路径(文件服务器)
    string tempPath = @"C:\wwwroot\ExcelTemp\"; //需设置读写路径读写权限
    tempPath += Path.GetFileName(extra);
     //1、查询数据
    using (SqlCommand cmd = new SqlCommand(list[0].sql, connection)) 
    {
        cmd.CommandType = cmdType;
        if (list[0].parameter != null) cmd.Parameters.AddRange(list[0].parameter);
        cmd.CommandTimeout = timeout; // 超时定义
        //1.1 转移数据 到 wookbook
         using (DbDataReader reader = await cmd.ExecuteReaderAsync())
         {
                ExcelHelper.OutPut(reader, tempPath, ref workbook);
        }
    }
    //2、异常存储EXCEL
    _ = Task.Run(() => ExcelHelper.SaveExcel(tempPath, workbook, extra));
    //3、告知response,数据查询结束,保存正在进行
    return true;
}

1、 查询数据 ==> 2、结果存内存 ==> 3、告知前端在下载了,等会你看看结果。

数据导出实现

csharp 复制代码
using NPOI.SS.UserModel;
using NPOI.XSSF.Streaming;
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
public class ExcelHelper
{
    /// <summary>
    /// 1、DbDataReader=>workbook  
    /// </summary>
    public static  void OutPut(DbDataReader reader, string filePath, ref SXSSFWorkbook workbook)
    {
        const int maxRowsPerSheet = 1_000_000;  // 每个Sheet最大行数 超过新建sheet
        const int bufferSize = 50;            // 一次写入条数(优化内存)
        // 初始化流式工作簿(关键内存优化)
        try
        {
            int sheetIndex = 1;
            ISheet currentSheet = workbook.CreateSheet($"Sheet_{sheetIndex}");
            // 创建标题行
            IRow headerRow = currentSheet.CreateRow(0);
            for (int i = 0; i < reader.FieldCount; i++)
            {
                headerRow.CreateCell(i).SetCellValue(reader.GetName(i));
            }
            int rowIndex = 1;  // 数据行从1开始(0是标题行)
            while (reader.Read())
            {
                // 达到最大行数时切换Sheet
                if (rowIndex >= maxRowsPerSheet)
                {
                    // 正确调用无参数FlushRows(NPOI 2.7.3+)
                    ((SXSSFSheet)currentSheet).FlushRows();  // 刷新当前Sheet[^1]

                    sheetIndex++;
                    currentSheet = workbook.CreateSheet($"Sheet_{sheetIndex}");
                    rowIndex = 1;  // 新Sheet重置行索引
                }
                // 创建数据行
                IRow dataRow = currentSheet.CreateRow(rowIndex);
                // 写入所有列数据(类型安全处理)
                for (int col = 0; col < reader.FieldCount; col++)
                {
                    var cell = dataRow.CreateCell(col);
                    // 根据数据类型安全写入
                    if (reader.IsDBNull(col))
                    {
                        cell.SetCellValue((string)null);
                    }
                    else
                    {
                        //字符转换 
                        switch (Type.GetTypeCode(reader.GetFieldType(col)))
                        {
                            case TypeCode.String:
                               
                                cell.SetCellValue(reader.GetString(col));
                                break;
                            case TypeCode.DateTime:
                                cell.SetCellValue(reader.GetDateTime(col));
                                break;
                            case TypeCode.Int16:
                                 cell.SetCellValue(reader.GetInt16(col));
                                break;
                            case TypeCode.Int32:
                                cell.SetCellValue(reader.GetInt32(col));
                                break;
                            case TypeCode.Int64:
                                cell.SetCellValue(reader.GetInt64(col));
                                break;
                            case TypeCode.Decimal:
                                cell.SetCellValue((double)reader.GetDecimal(col));
                                break;
                            case TypeCode.Double:
                                cell.SetCellValue(reader.GetDouble(col));
                                break;
                            case TypeCode.Single:
                                cell.SetCellValue(reader.GetDouble(col));
                                break;
                            case TypeCode.Boolean:
                                cell.SetCellValue(reader.GetBoolean(col));
                                break;
                            default:
                                cell.SetCellValue(reader.GetValue(col).ToString());
                                break;
                        }
                    }
                }
                if (rowIndex % 100000 == 0)
                {
                     _ = Task.Run(() => System.GC.Collect());
                }
                rowIndex++;
            }
        }
        catch (Exception ex)
        {
        }
       
    }
    /// <summary>
    ///2、保存 workbook=>Excel
    /// </summary>
    public static void SaveExcel(string tempPath, SXSSFWorkbook workbook, string truePath)
    {
        try
        {
            //保存本地
            using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
            {
                workbook.Write(fs, false);
            }
           //异步执行剪切
            _ = Task.Run(() => ExcelHelper.CutFile(tempPath, truePath));
        }
        catch (Exception ex)
        {
        }
        finally
        {
            workbook.Dispose();
            System.GC.Collect();
        }
    }

    /// <summary>
    ///3、 剪切  本地 'a.xlsx'=>远程 '正在执行_a.xlsx'
    /// </summary>
    public static void CutFile(string fromPath, string truePath)
    {
        //临时路径
        string tempPath=Path.GetDirectoryName(truePath)+"\\正在执行_"+ Path.GetFileName(truePath);
        // 检查源文件是否存在
        while (!File.Exists(tempPath))
        {
            Task.Delay(1000); // 减少CPU占用
            if (File.Exists(fromPath))
                File.Move(fromPath, tempPath);
        }
        //重命名
        while (File.Exists(tempPath)&&(!File.Exists(fromPath)))
        {
            Task.Delay(1000); // 减少CPU占用
            ReName(tempPath, truePath);
        }
    }
    /// <summary>
    /// 4、重命名 '正在执行_a.xlsx'=>'a.xlsx'
    /// </summary>
    public static void ReName(string fromPath, string truePath)
    {
        File.Move(fromPath, truePath);
    }
}

'正在执行_' 前缀,可识别,文件是否完成,以便打开。

1、保存到内存==> 2、保存到iis所在服务器 ==> 3、移动到共享服务器

==> 4、重命名 以表示 传输结束

相关推荐
天行健,君子而铎12 分钟前
AI赋能·精准适配——知影-API风险监测系统筑牢教育数据流转安全防线
大数据·人工智能·安全
XTIOT66613 分钟前
俄罗斯诚信标签Chestny ZNAK技术约束分析与智能化应对思路
大数据·人工智能·嵌入式硬件·物联网
朴马丁13 分钟前
流程PLM的智能化未来:AI与数字孪生如何赋能工艺优化与预测性运营
大数据·人工智能·ai·流程行业plm
谁似人间西林客21 分钟前
工业大数据:点亮汽车制造质量之路,驱动数字孪生工厂高效转型
大数据·汽车·制造
大大大大晴天️32 分钟前
Flink技术实践:RocksDB 状态后端技术解密
大数据·flink
深圳市九鼎创展科技35 分钟前
九鼎创展 X7110 开发板(JH7110):国产 RISC-V 多媒体平台全解析
大数据·linux·人工智能·嵌入式硬件·ubuntu·risc-v
跨境猫小妹40 分钟前
邮政与燃油附加同步抬升之后跨境卖家如何预留尾程成本缓冲
大数据·人工智能·产品运营·跨境电商·营销策略
跨境牛马哥43 分钟前
2026爬虫开发:Playwright对决Puppeteer
大数据·网络·网络协议
龙亘川1 小时前
BM²T 电池管理技术深度解析:大容量储能时代的安全、精准与高效解决方案
大数据·bm²t 电池管理技术深度解析