C#实现3GB级大CSV文件高效导入SQL表的方案咨询(需为每行附加文件名与日期字段)
高效导入大CSV到SQL:CsvHelper + SqlBulkCopy 解决方案
嘿,你选CsvHelper搭配SqlBulkCopy绝对是找对方向了——这俩组合处理大文件的效率比EF循环高太多,完美解决你3GB文件导入慢的问题!针对你需要给每行附加文件名和日期的需求,咱们可以通过在读取CSV时动态注入字段的方式实现,下面给你详细的步骤和代码:
第一步:安装必要的NuGet包
首先确保你安装了这两个包:
CsvHelper:用来高效读取CSV/TSV文件Microsoft.Data.SqlClient(或System.Data.SqlClient,根据你的.NET版本选择):提供SqlBulkCopy功能
第二步:定义实体类与Csv映射
先把你的目标实体类整理好,包含CSV里的字段加上需要附加的IMPORT_FILE_NM和IMPORT_DT:
public class ImportDestinationTable { public string ImportCol0 { get; set; } public string ImportCol1 { get; set; } public int? ImportCol2 { get; set; } public string ImportCol3 { get; set; } public string ImportCol4 { get; set; } public string ImportCol5 { get; set; } public string IMPORT_FILE_NM { get; set; } public DateTime IMPORT_DT { get; set; } }
然后定义CsvHelper的映射类,用来处理CSV列和实体属性的对应,同时处理Nullable类型的转换:
using CsvHelper.Configuration; public class ImportDestinationTableMap : ClassMap<ImportDestinationTable> { public ImportDestinationTableMap() { // 按CSV列索引映射(对应你原来的row[0]、row[1]...) Map(m => m.ImportCol0).Index(0); Map(m => m.ImportCol1).Index(1); // 把空字符串转换成null,替代你原来的TryParseNullable方法 Map(m => m.ImportCol2).Index(2).TypeConverterOption.NullValues(""); Map(m => m.ImportCol3).Index(3); Map(m => m.ImportCol4).Index(4); Map(m => m.ImportCol5).Index(5); // 这两个字段CSV里没有,告诉CsvHelper忽略它们,后面手动赋值 Map(m => m.IMPORT_FILE_NM).Ignore(); Map(m => m.IMPORT_DT).Ignore(); } }
第三步:实现高效批量导入的核心代码
下面是替换你原有Process方法的代码,重点是流式读取+批量导入,不会把整个大文件加载到内存:
using CsvHelper; using CsvHelper.Configuration; using Microsoft.Data.SqlClient; using System.Globalization; using System.Reflection; public void Process(string inputFilePath) { DateTime fileDate = DateTime.Today; string[] files = Directory.GetFiles(inputFilePath); // 替换成你的数据库连接字符串 string connectionString = "Server=your-server;Database=your-db;Trusted_Connection=True;"; foreach (var file in files) { string fileName = Path.GetFileName(file); var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = "\t", // 你的文件是制表符分隔的,对应原来的Split('\t') HasHeaderRecord = true, // 跳过前两行(对应你原来代码里的两次reader.ReadLine()) ShouldSkipRecord = record => record.Context.Row <= 2, // 如果不需要严格验证表头和缺失字段,可以关闭这些检查提升速度 HeaderValidated = null, MissingFieldFound = null }; using (var streamReader = new StreamReader(file)) using (var csvReader = new CsvReader(streamReader, csvConfig)) { // 注册映射类 csvReader.Context.RegisterClassMap<ImportDestinationTableMap>(); // 流式读取CSV记录,并注入文件名和日期——这里不会一次性加载所有数据到内存 var records = csvReader.GetRecords<ImportDestinationTable>() .Select(record => { record.IMPORT_FILE_NM = fileName; record.IMPORT_DT = fileDate; return record; }); // 使用SqlBulkCopy批量写入数据库 using (var bulkCopy = new SqlBulkCopy(connectionString)) { bulkCopy.DestinationTableName = "ImportDestinationTable"; // 你的SQL表名 bulkCopy.BatchSize = 10000; // 每次批量写入的行数,可根据内存调整(比如1万-10万) bulkCopy.BulkCopyTimeout = 300; // 超时时间(秒),避免大批次超时 bulkCopy.EnableStreaming = true; // 开启流式传输,进一步降低内存占用 // 自动映射实体属性和数据库列名(如果两者名称一致的话) foreach (PropertyInfo prop in typeof(ImportDestinationTable).GetProperties()) { bulkCopy.ColumnMappings.Add(prop.Name, prop.Name); } // 执行批量写入 bulkCopy.WriteToServer(records.AsDataReader()); } Console.WriteLine($"✅ 文件 {fileName} 导入完成!"); } } }
关键优化点说明
- 流式处理:
csvReader.GetRecords()返回的是可枚举对象,配合Select注入字段时,不会一次性把3GB文件加载到内存,而是边读边处理,避免内存溢出。 - SqlBulkCopy效率:相比EF的逐行Add+SaveChanges,SqlBulkCopy直接和数据库底层交互,用批量插入的方式,速度能提升几十甚至上百倍。
- 跳过前两行:通过
ShouldSkipRecord配置替代手动ReadLine(),代码更简洁且符合CsvHelper的使用规范。 - Nullable转换:映射类里的
TypeConverterOption.NullValues("")自动处理空字符串转null,替代你原来的TryParseNullable方法。
额外优化建议
- 临时禁用索引:导入前禁用目标表的非聚集索引,导入完成后再重建,能大幅降低写入开销(索引会减慢插入速度)。
- 调整BatchSize:如果你的服务器内存充足,可以把
BatchSize调大(比如5万或10万),减少数据库交互次数;内存紧张则调小。 - 错误处理:可以添加
try-catch块捕获SqlException,处理导入过程中的异常(比如数据格式错误)。
内容的提问来源于stack exchange,提问作者caj
相关产品推荐
相关产品推荐

