You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

MVC5下EPPlus导入大体积Excel至SQL过慢,求高效优化方案

优化MVC5中EPPlus导入大Excel文件到SQL的性能方案

看来你卡在大Excel文件导入的性能瓶颈上了——当前逐行读取+EF逐行添加的方式,在处理1MB以上的文件时必然会慢到离谱,毕竟每一步都在做低效的重复操作。下面给你几个立竿见影的优化方向,从读取到入库全面提速:

一、先优化EPPlus的读取效率

你现在是逐单元格读取每一行的数据,这是EPPlus最耗时的读取方式之一。换成批量读取整行/整表数据的API,速度能提升好几倍:

优化后的读取示例

public ActionResult Application(FormCollection formCollection)
{
    List<bomApplicationImportTgt> usersList = new List<bomApplicationImportTgt>();
    if (Request != null)
    {
        HttpPostedFileBase file = Request.Files["UploadedFile"];
        if ((file != null) && (file.ContentLength > 0) && !string.IsNullOrEmpty(file.FileName))
        {
            string fileName = file.FileName;
            using (var package = new ExcelPackage(file.InputStream))
            {
                var workSheet = package.Workbook.Worksheets.First();
                // 直接获取整个工作表的范围值,返回二维数组,避免逐Cell访问
                var rangeValues = workSheet.Cells[1, 1, workSheet.Dimension.End.Row, workSheet.Dimension.End.Column].Value;
                
                // 从第2行开始遍历(跳过表头,注意二维数组索引从0开始)
                for (int rowIterator = 1; rowIterator < rangeValues.GetLength(0); rowIterator++)
                {
                    var rowData = (object[])rangeValues[rowIterator];
                    var user = new bomApplicationImportTgt();
                    user.date = Convert.ToDateTime(rowData[0]);
                    user.Description = rowData[1]?.ToString();
                    user.SequenceNumber = Convert.ToInt32(rowData[2]);
                    // ... 其他字段依次对应rowData的索引
                    user.FileName = fileName;
                    usersList.Add(user);
                }
            }
        }
    }
    // 后续入库逻辑见下面的优化
    return View("Application");
}

二、用批量入库替代EF逐行添加

你现在用foreach循环Add然后单次SaveChanges,EF会为每个实体生成单独的INSERT语句,大数量下数据库IO会直接炸锅。推荐两种方案:

方案1:使用SqlBulkCopy(最快的原生方案)

直接把内存中的列表转成DataTable,然后用SqlBulkCopy批量写入数据库,这是处理大量数据最快的方式:

// 假设已经读取到usersList
DataTable dt = new DataTable();
// 手动添加DataTable列,和你的bomApplicationImportTgt实体字段对应
dt.Columns.Add("date", typeof(DateTime));
dt.Columns.Add("Description", typeof(string));
dt.Columns.Add("SequenceNumber", typeof(int));
// ... 添加其他所有字段

// 填充DataTable
foreach (var item in usersList)
{
    DataRow row = dt.NewRow();
    row["date"] = item.date;
    row["Description"] = item.Description;
    row["SequenceNumber"] = item.SequenceNumber;
    // ... 其他字段赋值
    row["FileName"] = item.FileName;
    dt.Rows.Add(row);
}

// 使用SqlBulkCopy批量插入
using (var connection = new SqlConnection(excelImportDBEntities.Database.Connection.ConnectionString))
{
    connection.Open();
    using (var bulkCopy = new SqlBulkCopy(connection))
    {
        bulkCopy.DestinationTableName = "bomApplicationImportTgts"; // 数据库表名
        // 映射DataTable列和数据库表列(列名一致可省略,自动映射)
        bulkCopy.ColumnMappings.Add("date", "date");
        bulkCopy.ColumnMappings.Add("Description", "Description");
        // ... 其他列映射
        bulkCopy.WriteToServer(dt);
    }
}

方案2:使用EF批量操作库

如果你想继续用EF的语法,可以安装EntityFramework.BulkInsert这类库(注意对应EF版本),代码会更简洁:

using (Dev_Purchasing_New_ModelEntities excelImportDBEntities = new Dev_Purchasing_New_ModelEntities())
{
    excelImportDBEntities.BulkInsert(usersList);
    // 不需要手动SaveChanges,库会自动处理批量插入
}

三、分批次处理(避免内存溢出+进一步提速)

如果文件特别大(比如几十MB),一次性把所有数据读到内存可能会导致内存溢出,建议分批次读取和插入:

// 读取时每10000行就批量插入一次
int batchSize = 10000;
int currentBatch = 0;
List<bomApplicationImportTgt> batchList = new List<bomApplicationImportTgt>(batchSize);

foreach (var rowData in rangeValues)
{
    // 构造实体添加到batchList
    batchList.Add(user);
    currentBatch++;
    if (currentBatch % batchSize == 0)
    {
        // 批量插入当前批次
        InsertBatch(batchList); // 这里调用上面的SqlBulkCopy或EF批量插入方法
        batchList.Clear();
    }
}
// 插入剩余的不足一个批次的数据
if (batchList.Count > 0)
{
    InsertBatch(batchList);
}

为什么原来的代码这么慢?

  1. EPPlus逐Cell读取:每次访问workSheet.Cells[row, col].Value都会触发EPPlus内部的解析逻辑,重复次数多了开销极大;
  2. EF逐行Add+单次SaveChanges:EF会跟踪每个实体的状态,SaveChanges时生成N条INSERT语句,数据库需要多次解析和执行,IO开销呈指数级增长。

按照上面的方案优化后,处理几十MB的Excel文件应该能从几小时压缩到几分钟甚至几十秒。

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

火山引擎 最新活动