问题描述
EF 是 ORM,而不是导入工具。ORM 的工作是加载相关实体的图,并给人以使用对象而不是表和 sql 的印象。导入作业中没有实体或图形,但有文件、字段、表格和映射。
在 .NET 中导入数据 sql Server 表的最快方法是使用SqlBulkCopy类。这使用bcp
工具或BULK INSERT
命令中使用的相同机制,以使用最少的日志记录将数据作为流导入。输入可以是 DataTable 或 IDataReader 实例。
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
或类映射:
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}");
}
}
解决方法
我正在使用通过实体框架连接到我的 ASP.NET Mvc 应用程序的 Sql Server Db。应用程序部署在由集群管理的虚拟机上。目前最大内存设置为 32 GB。我可以把它提高到 64 GB。用户可用的解决方案之一是将csv
文件加载到数据库。每个文件为一天,由200 列的 80-90k 条记录组成。对于这个解决方案,我使用了这个设置:
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,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,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的主要目标是检测一段时间内数据量的错误。有没有人碰巧有通过实体框架处理这么多数据的经验?是否有任何解决方案可以将资源分配给每个任务,以便每个请求都能得到响应?