C# 如何通过datatable提高CSV上传的性能
我有一个上传CSV文件的工作解决方案。目前,我使用C# 如何通过datatable提高CSV上传的性能,c#,csv,datatable,asp.net-core-2.0,sqlbulkcopy,C#,Csv,Datatable,Asp.net Core 2.0,Sqlbulkcopy,我有一个上传CSV文件的工作解决方案。目前,我使用IFormCollection让用户从一个视图上载多个CSV文件 CSV文件保存为临时文件,如下所示: List<string> fileLocations = new List<string>(); foreach (var formFile in files) { filePath = Path.GetTempFileName(); if (formFile.Length > 0) {
IFormCollection
让用户从一个视图上载多个CSV文件
CSV文件保存为临时文件,如下所示:
List<string> fileLocations = new List<string>();
foreach (var formFile in files)
{
filePath = Path.GetTempFileName();
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
}
fileLocations.Add(filePath);
}
然后,此代码将对象传递给创建datatable并插入数据
private DataTable CreateRawDataTable
{
get
{
var dt = new DataTable();
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("SerialNumber", typeof(string));
dt.Columns.Add("ReadingNumber", typeof(int));
dt.Columns.Add("ReadingDate", typeof(string));
dt.Columns.Add("ReadingTime", typeof(string));
dt.Columns.Add("RunTime", typeof(string));
dt.Columns.Add("Temperature", typeof(double));
dt.Columns.Add("ProjectGuid", typeof(Guid));
dt.Columns.Add("CombineDateTime", typeof(string));
return dt;
}
}
public void SaveRawData(List<RawDataModel> data)
{
DataTable dt = CreateRawDataTable;
var count = data.Count;
for (var i = 1; i < count; i++)
{
DataRow row = dt.NewRow();
row["Id"] = data[i].Id;
row["ProjectGuid"] = data[i].ProjectGuid;
row["SerialNumber"] = data[i].SerialNumber;
row["ReadingNumber"] = data[i].ReadingNumber;
row["ReadingDate"] = data[i].ReadingDate;
row["ReadingTime"] = data[i].ReadingTime;
row["CombineDateTime"] = data[i].CombineDateTime;
row["RunTime"] = data[i].RunTime;
row["Temperature"] = data[i].Temperature;
dt.Rows.Add(row);
}
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlTransaction tr = conn.BeginTransaction())
{
using (var sqlBulk = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, tr))
{
sqlBulk.BatchSize = 1000;
sqlBulk.DestinationTableName = "RawData";
sqlBulk.WriteToServer(dt);
}
tr.Commit();
}
}
}
专用数据表CreateRawDataTable
{
得到
{
var dt=新数据表();
添加(“Id”,typeof(int));
添加(“序列号”,类型(字符串));
dt.Columns.Add(“ReadingNumber”,typeof(int));
添加(“读取日期”,类型(字符串));
添加(“ReadingTime”,typeof(string));
添加(“运行时”,typeof(字符串));
dt.列。添加(“温度”,类型(双));
添加(“ProjectGuid”,typeof(Guid));
添加(“CombineDateTime”,typeof(string));
返回dt;
}
}
公共void SaveRawData(列表数据)
{
DataTable dt=CreateRawDataTable;
var count=data.count;
对于(变量i=1;i
是否有其他方法可以做到这一点,或者有更好的方法来提高性能,从而减少上载时间,因为这可能需要很长时间,而且我看到内存使用量不断增加,达到500MB左右
TIA您可以通过删除数据表并直接从输入流中读取数据来提高性能 SqlBulkCopy有一个重载,它接受IDataReader而不是整个数据表 可以使用StreamReader作为输入来读取CSV文件。它提供CsvDataReader作为CSV数据之上的
IDataReader
实现。这允许直接从输入流读取并写入SqlBulkCopy
以下方法将从文件中读取,使用CsvHelper解析流,并使用CSV的字段配置SqlBulkCopy实例:
public async Task ToTable(IFormFile file, string table)
{
using (var stream = file.OpenReadStream())
using (var tx = new StreamReader(stream))
using (var reader = new CsvReader(tx))
using (var rd = new CsvDataReader(reader))
{
var headers = reader.Context.HeaderRecord;
var bcp = new SqlBulkCopy(_connection)
{
DestinationTableName = table
};
//Assume the file headers and table fields have the same names
foreach(var header in headers)
{
bcp.ColumnMappings.Add(header, header);
}
await bcp.WriteToServerAsync(rd);
}
}
这样就不会将任何内容写入临时表或缓存在内存中。上传的文件将被解析并直接写入数据库。您可以通过删除数据表并直接从输入流中读取来提高性能
SqlBulkCopy有一个重载,它接受IDataReader而不是整个数据表
可以使用StreamReader作为输入来读取CSV文件。它提供CsvDataReader作为CSV数据之上的IDataReader
实现。这允许直接从输入流读取并写入SqlBulkCopy
以下方法将从文件中读取,使用CsvHelper解析流,并使用CSV的字段配置SqlBulkCopy实例:
public async Task ToTable(IFormFile file, string table)
{
using (var stream = file.OpenReadStream())
using (var tx = new StreamReader(stream))
using (var reader = new CsvReader(tx))
using (var rd = new CsvDataReader(reader))
{
var headers = reader.Context.HeaderRecord;
var bcp = new SqlBulkCopy(_connection)
{
DestinationTableName = table
};
//Assume the file headers and table fields have the same names
foreach(var header in headers)
{
bcp.ColumnMappings.Add(header, header);
}
await bcp.WriteToServerAsync(rd);
}
}
这样就不会将任何内容写入临时表或缓存在内存中。上传的文件将被解析并直接写入数据库。除了@Panagiotis的回答之外,为什么不将文件处理与文件上传交错?用异步方法包装文件处理逻辑,将循环更改为Parallel.Foreach,并在每个文件到达时进行处理,而不是等待所有文件
private static readonly object listLock = new Object(); // only once at class level
List<string> fileLocations = new List<string>();
Parallel.ForEach(files, (formFile) =>
{
filePath = Path.GetTempFileName();
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
await ProcessFileInToDbAsync(filePath);
}
// Added lock for thread safety of the List
lock (listLock)
{
fileLocations.Add(filePath);
}
});
private static readonly object listLock=new object();//只有一次在班级级别
List fileLocations=新列表();
Parallel.ForEach(文件,(formFile)=>
{
filePath=Path.GetTempFileName();
如果(formFile.Length>0)
{
使用(var stream=newfilestream(filePath,FileMode.Create))
{
等待formFile.CopyToAsync(流);
}
等待ProcessFileInToDbAsync(文件路径);
}
//为列表中的线程安全性添加了锁
锁(列表锁)
{
添加(文件路径);
}
});
除了@Panagiotis的答案之外,您为什么不将文件处理与文件上载交错?用异步方法包装文件处理逻辑,将循环更改为Parallel.Foreach,并在每个文件到达时进行处理,而不是等待所有文件
private static readonly object listLock = new Object(); // only once at class level
List<string> fileLocations = new List<string>();
Parallel.ForEach(files, (formFile) =>
{
filePath = Path.GetTempFileName();
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
await ProcessFileInToDbAsync(filePath);
}
// Added lock for thread safety of the List
lock (listLock)
{
fileLocations.Add(filePath);
}
});
private static readonly object listLock=new object();//只有一次在班级级别
List fileLocations=新列表();
Parallel.ForEach(文件,(formFile)=>
{
filePath=Path.GetTempFileName();
如果(formFile.Length>0)
{
使用(var stream=newfilestream(filePath,FileMode.Create))
{
等待formFile.CopyToAsync(流);
}
等待ProcessFileInToDbAsync(文件路径);
}
//为列表中的线程安全性添加了锁
锁(列表锁)
{
添加(文件路径);
}
});
多亏了@Panagiotis Kanavos,我才知道该怎么办。首先,我调用方法的方式是将它们留在内存中。我的CSV文件分为两部分,垂直元数据和通常的水平信息。所以我需要把它们一分为二。将它们保存为tmp文件w
public async Task SplitCsvData(IFormFile file, string uid)
{
var data = string.Empty;
var m = new List<string>();
var r = new List<string>();
var records = new List<string>();
using (var stream = file.OpenReadStream())
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var header = line.Split(',')[0].ToString();
bool parsed = int.TryParse(header, out int result);
if (!parsed)
{
m.Add(line);
}
else
{
r.Add(line);
}
}
}
//TODO: Validation
//This splits the list into the Meta data model. This is just a single object, with static fields.
var metaData = SplitCsvMetaData.SplitMetaData(m, uid);
DataTable dtm = CreateMetaData(metaData);
var serialNumber = metaData.LoggerId;
await SaveMetaData("MetaData", dtm);
//
var lrd = new List<RawDataModel>();
foreach (string row in r)
{
lrd.Add(new RawDataModel
{
Id = 0,
SerialNumber = serialNumber,
ReadingNumber = Convert.ToInt32(row.Split(',')[0]),
ReadingDate = Convert.ToDateTime(row.Split(',')[1]).ToString("yyyy-MM-dd"),
ReadingTime = Convert.ToDateTime(row.Split(',')[2]).ToString("HH:mm:ss"),
RunTime = row.Split(',')[3].ToString(),
Temperature = Convert.ToDouble(row.Split(',')[4]),
ProjectGuid = uid.ToString(),
CombineDateTime = Convert.ToDateTime(row.Split(',')[1] + " " + row.Split(',')[2]).ToString("yyyy-MM-dd HH:mm:ss")
});
}
await SaveRawData("RawData", lrd);
}
public async Task SaveMetaData(string table, DataTable dt)
{
using (SqlBulkCopy sqlBulk = new SqlBulkCopy(_configuration.GetConnectionString("DefaultConnection"), SqlBulkCopyOptions.Default))
{
sqlBulk.DestinationTableName = table;
await sqlBulk.WriteToServerAsync(dt);
}
}
public async Task SaveRawData(string table, IEnumerable<LogTagRawDataModel> lrd)
{
using (SqlBulkCopy sqlBulk = new SqlBulkCopy(_configuration.GetConnectionString("DefaultConnection"), SqlBulkCopyOptions.Default))
using (var reader = ObjectReader.Create(lrd, "Id","SerialNumber", "ReadingNumber", "ReadingDate", "ReadingTime", "RunTime", "Temperature", "ProjectGuid", "CombineDateTime"))
{
sqlBulk.DestinationTableName = table;
await sqlBulk.WriteToServerAsync(reader);
}
}