我正在使用通过实体框架连接到我的 ASP.NET Mvc 应用程序的 Sql Server Db。应用程序部署在由集群管理的虚拟机上。目前最大内存设置为 32 GB。我可以把它提高到 64 GB。用户可用的解决方案之一是将csv文件加载到数据库。每个文件为一天,由200 列的 80-90k 条记录组成。对于这个解决方案,我使用了这个设置:
csv
public ActionResult AddRecordsFromCSVFile2(FileInput fileInputs) { BWSERWISEntities bWDiagnosticEntities2 = new BWSERWISEntities(); bWDiagnosticEntities2.Configuration.AutoDetectChangesEnabled = true; bWDiagnosticEntities2.Configuration.ValidateOnSaveEnabled = false; var Files = fileInputs.File; if (ModelValidate()) { foreach (var fileInput in Files) { List<VOLTER_LOGS> LogsToAdd = new List<VOLTER_LOGS>(); using (MemoryStream memoryStream = new MemoryStream()) { fileInput.InputStream.CopyTo(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); { using (var streamReader = new StreamReader(memoryStream)) { var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";", HasHeaderRecord = false }; using (var csvReader = new CsvReader(streamReader, csvConfig)) { var records = csvReader.GetRecords<ReadAllRecords>().ToList(); var firstRecord = records.Where(x => x.Parametr4 != "0" && x.Parametr5 != "0" && x.Parametr6 != "0").FirstOrDefault(); var StartDateToCheck = Convert.ToDateTime(firstRecord.Parametr4 + "-" + firstRecord.Parametr5 + "-" + firstRecord.Parametr6 + " " + firstRecord.Parametr3 + ":" + firstRecord.Parametr2 + ":" + firstRecord.Parametr1, new CultureInfo("en-GB", true)); var lastRecord = records.Where(x => x.Parametr4 != "0" && x.Parametr5 != "0" && x.Parametr6 != "0").LastOrDefault(); var EndDateToCheck = Convert.ToDateTime(lastRecord.Parametr4 + "-" + lastRecord.Parametr5 + "-" + lastRecord.Parametr6 + " " + lastRecord.Parametr3 + ":" + lastRecord.Parametr2 + ":" + lastRecord.Parametr1, new CultureInfo("en-GB", true)); int matchingMachineID = int.Parse(firstRecord.Parametr7); var matchingElements = bWDiagnosticEntities2.VOLTER_LOGS.Where(x => (x.Parametr7 == matchingMachineID) && (x.Parametr1 >= StartDateToCheck && x.Parametr1 <= EndDateToCheck)).ToList(); bWDiagnosticEntities2.VOLTER_LOGS.RemoveRange(matchingElements); bWDiagnosticEntities2.SaveChanges(); foreach (var record in records) { if (record.Parametr4 != "0" && record.Parametr5 != "0" && record.Parametr6 != "0") { bWDiagnosticEntities2.Configuration.AutoDetectChangesEnabled = false; string date = record.Parametr4 + "-" + record.Parametr5 + "-" + record.Parametr6 + " " + record.Parametr3 + ":" + record.Parametr2 + ":" + record.Parametr1; DateTime recordDate = DateTime.Parse(date, new CultureInfo("en-GB", true)); // DateTime recordDate = DateTime.ParseExact(date, "dd-MM-yyyy HH:mm:ss", new CultureInfo("en-GB")); VOLTER_LOGS vOLTER_LOGS = new VOLTER_LOGS(); vOLTER_LOGS.Parametr1 = recordDate; vOLTER_LOGS.Parametr2 = 0; vOLTER_LOGS.Parametr3 = 0; vOLTER_LOGS.Parametr4 = 0; vOLTER_LOGS.Parametr5 = 0; vOLTER_LOGS.Parametr6 = 0; vOLTER_LOGS.Parametr7 = int.Parse(record.Parametr7); 7-200... vOLTER_LOGS.Parametr200 = int.Parse(record.Parametr200); LogsToAdd.Add(vOLTER_LOGS); } } } } } bWDiagnosticEntities2.VOLTER_LOGS.AddRange(LogsToAdd); bWDiagnosticEntities2.SaveChanges(); } } } return View(); }
此实现需要5-8 分钟并占用虚拟机内存的45-55%,但在保存过程中有时很难得到其他简单请求的响应(获取第一个和最后一个日期)并出现错误:
The request limited has expired or server is not responding.
服务器有很多可用内存,但表已经有 7kk 条记录并且还在增长。在其他用户请求期间,最终可能的文件不会被保存。现在我想知道将实体框架用于该数据量是个好主意。Application的主要目标是检测一段时间内数据量的错误。有没有人碰巧有通过实体框架处理这么多数据的经验?是否有任何解决方案可以将资源分配给每个任务,以便每个请求都能得到响应?
EF 是 ORM,而不是导入工具。ORM 的工作是加载相关实体的图,并给人以使用对象而不是表和 SQL 的印象。导入作业中没有实体或图形,但有文件、字段、表格和映射。
在 .NET 中导入数据 SQL Server 表的最快方法是使用SqlBulkCopy类。这使用bcp工具或BULK INSERT命令中使用的相同机制,以使用最少的日志记录将数据作为流导入。输入可以是 DataTable 或 IDataReader 实例。
bcp
BULK INSERT
CsvHelper或ExcelDataReader等库提供开箱即用的 IDataReader 派生类。在其他情况下,FastMember的 ObjectReader 可用于在任何 IEnumerable 上创建 IDataReader 包装器。
使用 CsvHelper 和 SqlBulkCopy 将 CSV 加载到表中可以像这样简单,只要文件和表列匹配:
public async Task SimpleImport(string path, string table, string connectionString) { using var reader = new StreamReader(path); var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";" }; using var csv = new CsvReader( reader, csvConfig); using var dr = new CsvDataReader(csv); using var bcp = new SqlBulkCopy(connectionString); bcp.DestinationTableName = table; await bcp.WriteToServerAsync(dr); }
如果没有标头,则必须通过 CsvHelper 的配置提供它们,例如通过GetDynamicPropertyName或类映射:
GetDynamicPropertyName
string[] fields=new[] {.....}; csvConfig.GetDynamicPropertyName = args=> { if (args.FieldIndex<10) { return fields[args.FieldIndex]; } else { return $"Value_{args.FieldIndex}"; } };
使用 ClassMap 允许指定类型转换,包括复杂的转换
public class Foo { ... public int Year { get; set; } public int Month { get; set; } public int Day { get; set; } public DateTime Date { get; set; } } public class FooMap : ClassMap<Foo> { public FooMap() { AutoMap(CultureInfo.InvariantCulture); Map(m => m.Date).Convert(row => DateFromParts(row.GetField(5),row.GetField(6),row.GetField(7)); } DateTime DateFromParts(string year, string month, string say) { return DateTime.Parse($"{year}-{month}-{day}"); } }