ASP.NET Core MVC框架下如何实现用户选中CSV文件导入数据库功能
ASP.NET Core MVC Database First模式下CSV导入数据库实现方案
前置准备
你现有的Database First生成的实体类、DbContext可以直接复用,无需额外修改数据结构,先安装必要的NuGet包:
CsvHelper:专门用于CSV文件解析的工具,自动处理类型转换、表头匹配,无需手写解析逻辑- 若需要高性能批量操作,可额外安装
EFCore.BulkExtensions
第一步:前端上传页面改造
可以在原有数据列表页添加上传入口,也可以单独新建Import.cshtml页面,表单必须添加enctype="multipart/form-data"属性才能接收文件:
<!-- 示例:在列表页添加CSV上传表单 --> <form asp-action="ImportCsv" method="post" enctype="multipart/form-data" class="my-3"> <div class="form-group"> <label>选择CSV文件</label> <input type="file" name="CsvFile" accept=".csv" class="form-control" required> </div> <button type="submit" class="btn btn-primary">导入并更新数据</button> </form>
其中accept=".csv"限制仅能选择CSV格式文件,避免用户上传错误格式文件。
第二步:Controller逻辑实现
核心逻辑包含:文件校验、CSV解析、业务校验、数据库更新四个部分,代码示例如下(需将示例中的实体类、DbContext替换为你项目中实际生成的对应类):
using CsvHelper; using CsvHelper.Configuration; using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; // 示例为Product表对应的Controller,替换为你实际业务的Controller public class ProductController : Controller { private readonly YourDbContext _context; // 替换为你Database First生成的DbContext public ProductController(YourDbContext context) { _context = context; } // 原有的Index、Create、Edit等Action保持不变即可 [HttpPost] public async Task<IActionResult> ImportCsv(IFormFile CsvFile) { // 1. 基础文件校验 if (CsvFile == null || CsvFile.Length == 0) { TempData["Error"] = "请选择有效的CSV文件"; return RedirectToAction(nameof(Index)); } if (Path.GetExtension(CsvFile.FileName).ToLower() != ".csv") { TempData["Error"] = "仅支持.csv格式的文件"; return RedirectToAction(nameof(Index)); } // 2. 配置CSV读取规则 var config = new CsvConfiguration(CultureInfo.InvariantCulture) { // 自动适配表头,忽略大小写和空格 PrepareHeaderForMatch = args => args.Header.ToLower().Replace(" ", ""), HasHeaderRecord = true, // 若CSV第一行为表头设为true,无表头则设为false MissingFieldFound = null // 忽略缺失字段,避免解析报错 }; // 3. 解析CSV为实体类集合,Product替换为你实际的数据库实体类 List<Product> importRecords; try { using var reader = new StreamReader(CsvFile.OpenReadStream()); using var csv = new CsvReader(reader, config); importRecords = csv.GetRecords<Product>().ToList(); } catch (Exception ex) { TempData["Error"] = $"CSV文件解析失败:{ex.Message}"; return RedirectToAction(nameof(Index)); } // 4. 自定义业务校验,可按需调整规则 var validRecords = importRecords.Where(x => !string.IsNullOrEmpty(x.ProductCode)).ToList(); if (!validRecords.Any()) { TempData["Error"] = "文件中未找到有效数据"; return RedirectToAction(nameof(Index)); } // 5. 写入/更新数据库,按主键判断是新增还是更新 foreach (var record in validRecords) { var existRecord = await _context.Products.FirstOrDefaultAsync(x => x.ProductId == record.ProductId); if (existRecord != null) { // 已有数据则更新对应字段,按需调整要更新的字段 existRecord.ProductName = record.ProductName; existRecord.Price = record.Price; existRecord.Stock = record.Stock; } else { // 无对应数据则新增 await _context.Products.AddAsync(record); } } // 6. 统一保存变更 await _context.SaveChangesAsync(); TempData["Success"] = $"导入成功,共处理{validRecords.Count}条数据"; return RedirectToAction(nameof(Index)); } }
可选优化点
- 大数据量优化:如果单次导入数据超过1000条,可使用
EFCore.BulkExtensions的批量插入/更新方法,比循环单条操作性能提升10倍以上 - 事务控制:添加事务包裹逻辑,导入过程中出现错误全部回滚,避免部分数据写入数据库
- 错误明细返回:记录解析失败、校验失败的行号和原因,返回给用户方便排查问题
- 权限控制:在导入方法上添加权限校验,避免未授权用户修改数据库数据
- 自定义表头映射:如果CSV表头和实体类属性差异较大,可以单独写继承
ClassMap<T>的映射类,明确指定字段对应关系
内容的提问来源于stack exchange,提问作者GG6565
相关产品推荐
相关产品推荐

