Appearance
.NET 导入导出实践
在企业级应用中,Excel/CSV 的导入导出是极高频的需求。本文总结了一套适用于 .NET 环境的通用架构方案,旨在解决性能瓶颈、代码耦合以及错误反馈不友好等常见问题。
1. 核心定位与设计目标
1.1 基础设施组件化
导入导出功能不应仅作为散落在各处的工具类,而应被视为 基础设施组件 (Infrastructure Component)。这意味着它需要具备统一的接口抽象、可插拔的底层实现以及标准化的数据模型。
1.2 设计原则
- 简单易用:开发人员应能通过少量代码完成基本的导入导出任务。
- 职责分离:Controller 仅负责请求协调,Service 负责数据查询,基础设施层负责格式处理。
- 错误友好:导入时必须具备完整的错误收集机制,避免“一行报错就停”。
- 可扩展性:预留多格式(CSV、PDF、Word)及异步任务的扩展空间。
2. 技术选型建议
在 .NET 生态中,针对 Excel 处理有多种成熟方案。推荐根据业务规模进行选型:
| 库 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| MiniExcel | 流式读写,内存占用极低;完全免费;API 极简。 | 功能相对简单,不支持复杂样式和图表。 | 首选方案;大数据量处理;个人/小团队项目。 |
| ClosedXML | MIT 协议;API 友好;功能全(样式、公式)。 | 大数据量下性能一般,内存消耗较大。 | 需要复杂 Excel 样式、公式支持的场景。 |
| EPPlus | 性能优秀,功能极其强大。 | 商用需授权(v5+)。 | 企业内部已购授权的重型报表系统。 |
| NPOI | 功能最强;完全免费。 | API 极其复杂(偏底层),维护成本高。 | 极端复杂的 Excel 历史文档处理。 |
3. 架构设计方案
3.1 抽象接口定义
通过接口抽象底层实现,确保在未来替换库(如从 MiniExcel 迁往 ClosedXML)时,业务逻辑层无需改动。
csharp
public interface IExcelExporter
{
Task<ExportResult> ExportAsync<T>(IEnumerable<T> data, Dictionary<string, string> columnMapping, string fileName);
}
public interface IExcelImporter
{
Task<ImportResult<T>> ImportAsync<T>(Stream stream, Dictionary<string, string> columnMapping) where T : class, new();
Task<ExportResult> GenerateTemplateAsync<T>(Dictionary<string, string> columnMapping);
}3.2 错误收集机制
这是提升导入用户体验的关键。不要抛出第一个异常就终止,而应收集所有验证错误并返回。
csharp
public class ImportResult<T>
{
public int SuccessCount { get; set; }
public int FailureCount { get; set; }
public List<T> SuccessData { get; set; } = new();
public List<ImportError> Errors { get; set; } = new();
public byte[]? ErrorFile { get; set; } // 可选:返回带批注的错误文件
}
public class ImportError
{
public int RowNumber { get; set; }
public string FieldName { get; set; }
public string ErrorMessage { get; set; }
}4. 核心实践指南
4.1 统一入口模式
Controller 不应直接引用 MiniExcel 或 ClosedXML。推荐在 Controller 中注入抽象接口,由基础设施服务完成具体工作。
csharp
[HttpGet("export")]
public async Task<IActionResult> Export([FromQuery] QueryParameter param)
{
// 1. 获取业务数据
var data = await _businessService.GetDataAsync(param);
// 2. 调用封装好的导出服务
var result = await _excelExporter.ExportAsync(
data,
ExportConfig.Columns, // 属性名 -> 列标题映射
"业务报表"
);
return File(result.Stream, result.ContentType, result.FileName);
}4.2 导入模板机制
为避免用户因为“少了一列”或“顺序不对”导致导入失败,每个导入功能应配对提供一个动态生成的模板下载接口。模板的列顺序应与导入逻辑严格一致。
4.3 安全红线
- 数量限制:同步导出接口必须设置
MaxRows(如 50,000 条),超过则强制要求用户添加筛选条件。 - 权限验证:导出动作通常涉及敏感数据,必须在 API 层强制执行角色权限检查。
5. 多格式扩展策略
当系统需要支持 CSV、PDF 等多种格式时,建议引入 工厂模式 和 策略模式:
- IDataExporter:定义通用导出接口(包含
FileExtension和MimeType)。 - ExporterFactory:根据前端传入的格式参数(
format=csv或format=xlsx)返回对应的实现类。 - 流式打包:对于批量导出多个文件的场景,应使用
System.IO.Compression进行流式 ZIP 压缩,避免内存溢出。
6. 性能优化建议
- 流式处理:大数据量导出时,优先选择支持
IAsyncEnumerable或流式写入的库(如 MiniExcel)。 - 同步 vs 异步:
< 10,000 行:同步处理,直接返回文件。10,000 - 50,000 行:同步处理,需前端显示 Loading。> 50,000 行:异步处理,提交任务到后台队列(如 Hangfire),完成后通知用户下载。
- 内存管理:在转换数据模型时,尽量使用
yield return或直接操作数据流,避免在内存中创建巨大的List<T>。
