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); }
为什么原来的代码这么慢?
- EPPlus逐Cell读取:每次访问
workSheet.Cells[row, col].Value都会触发EPPlus内部的解析逻辑,重复次数多了开销极大; - EF逐行Add+单次SaveChanges:EF会跟踪每个实体的状态,SaveChanges时生成N条INSERT语句,数据库需要多次解析和执行,IO开销呈指数级增长。
按照上面的方案优化后,处理几十MB的Excel文件应该能从几小时压缩到几分钟甚至几十秒。
内容的提问来源于stack exchange,提问作者Minhal




