ASP.NET MVC4 - ADO.NET - 将大量文件从 ZipArchives 保存到 sql 服务器
ASP.NET MVC4 - ADO.NET - Saving a large number of files from ZipArchives to sql server
我有一组传入的 zip 文件,总共可达 2GB,将包含数千个文件。 (文件包括jpg、pdf、txt、doc等)
每个文件都将作为单独的行保存在 SQL Server 2014 数据库 table 中,使用采用 Table 值参数并通过 [=29 调用的存储过程=]. table 对文件名使用 varchar,对文件本身使用 varbinary(max)。
以前,传入的 zip 文件是在内存中提取的,内容存储在 Dictionary<T>
中,只需调用一次数据库就可以保存整个集合,但这会导致提取集合后出现内存问题可以超过 2GB,因此字典对象变得大于 CLR 对象的最大大小。(2GB) 我知道这可以在 .NET 4.5.1 中被覆盖,但是我暂时不想选择那个选项。
为了解决这个内存不足的问题,我将文件直接传递到我的数据访问 class 并执行如下操作。基本上,创建最多 500MB 的较小批次并将其提交给 SQL 服务器。所以Large object heap中managed object(datatable)的大小不能超过500MB。不属于当前批次的文件仍然保存在非托管内存中。
但是,我认为数据甚至在事务完成之前就已经被处理掉了,所以它会静静地失败而不会抛出任何异常。但是,当我显着减少批处理的大小时(如 2MB 左右),它工作得很好。
我该如何解决这个问题?理想情况下,我希望批大小为 500MB,因为单个文件的大小可以达到 250MB。
Using System.IO.Compression;
public SaveFiles(int userId, HttpFileCollectionBase files)
{
try
{
const long maxBatchSize = 524288000; //500MB
var myCollection = namesOfValidFilesBasedOnBusinessLogic;
var dataTable = new DataTable("@Files");
dataTable.Columns.Add("FileName", typeof(string));
dataTable.Columns.Add("File", typeof(byte[]));
for (var i = 0; i < files.Count; i++)
{
using (var zipFile = new ZipArchive(files[i].InputStream))
{
var validEntries = zipFile.Entries.Where(e => myCollection.Contains(e.name));
long currentBatchSize = 0;
foreach (var entry in validEntries)
{
if (currentBatchSize < maxBatchSize)
{
currentBatchSize = currentBatchSize + entry.length;
using (var stream = entry.Open())
{
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
dataTable.Rows.Add(entry.Name, ms.ToArray());
}
}
}
else
{
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
using (var cmd = new Sqlcommand("dbo.SaveFiles", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@UserId", userId);
cmd.Parameters.AddWithValue("@Files", dataTable);
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery(); //control just disappears after this line
}
dataTable = new DataTable("@Files");
dataTable.Columns.Add("FileName", typeof(string));
dataTable.Columns.Add("File", typeof(byte[]));
}
}
}
}
}
}
catch (Exception ex)
{
throw ex; //Not getting any exception
}
}
//control just disappears after this line
我假设你的意思是下一行永远不会执行。
当向 Sql 服务器发送大量数据以进行保存时,这很可能是您所观察到的情况,实际上这些数据必须发送到服务器并且似乎没有任何反应然后进行处理,500 MB
可能需要一段时间才能完成。
如果您将命令的超时更改为 200 秒之类的值,我敢打赌您会在 200 秒后因超时收到 SqlException
。因为您将其设置为 0
它将无限期等待。
cmd.CommandTimeout = 200;
如果这是不可取的,那么您需要根据每 XX MB 所花费的时间量在时间和批量大小之间找到良好的平衡。您可以衡量的唯一方法是根据您的环境(网络容量、sql 服务器负载、客户端负载等)使用各种批量大小进行测试。
我有一组传入的 zip 文件,总共可达 2GB,将包含数千个文件。 (文件包括jpg、pdf、txt、doc等)
每个文件都将作为单独的行保存在 SQL Server 2014 数据库 table 中,使用采用 Table 值参数并通过 [=29 调用的存储过程=]. table 对文件名使用 varchar,对文件本身使用 varbinary(max)。
以前,传入的 zip 文件是在内存中提取的,内容存储在 Dictionary<T>
中,只需调用一次数据库就可以保存整个集合,但这会导致提取集合后出现内存问题可以超过 2GB,因此字典对象变得大于 CLR 对象的最大大小。(2GB) 我知道这可以在 .NET 4.5.1 中被覆盖,但是我暂时不想选择那个选项。
为了解决这个内存不足的问题,我将文件直接传递到我的数据访问 class 并执行如下操作。基本上,创建最多 500MB 的较小批次并将其提交给 SQL 服务器。所以Large object heap中managed object(datatable)的大小不能超过500MB。不属于当前批次的文件仍然保存在非托管内存中。
但是,我认为数据甚至在事务完成之前就已经被处理掉了,所以它会静静地失败而不会抛出任何异常。但是,当我显着减少批处理的大小时(如 2MB 左右),它工作得很好。
我该如何解决这个问题?理想情况下,我希望批大小为 500MB,因为单个文件的大小可以达到 250MB。
Using System.IO.Compression;
public SaveFiles(int userId, HttpFileCollectionBase files)
{
try
{
const long maxBatchSize = 524288000; //500MB
var myCollection = namesOfValidFilesBasedOnBusinessLogic;
var dataTable = new DataTable("@Files");
dataTable.Columns.Add("FileName", typeof(string));
dataTable.Columns.Add("File", typeof(byte[]));
for (var i = 0; i < files.Count; i++)
{
using (var zipFile = new ZipArchive(files[i].InputStream))
{
var validEntries = zipFile.Entries.Where(e => myCollection.Contains(e.name));
long currentBatchSize = 0;
foreach (var entry in validEntries)
{
if (currentBatchSize < maxBatchSize)
{
currentBatchSize = currentBatchSize + entry.length;
using (var stream = entry.Open())
{
using (var ms = new MemoryStream())
{
stream.CopyTo(ms);
dataTable.Rows.Add(entry.Name, ms.ToArray());
}
}
}
else
{
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
using (var cmd = new Sqlcommand("dbo.SaveFiles", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@UserId", userId);
cmd.Parameters.AddWithValue("@Files", dataTable);
cmd.CommandTimeout = 0;
cmd.ExecuteNonQuery(); //control just disappears after this line
}
dataTable = new DataTable("@Files");
dataTable.Columns.Add("FileName", typeof(string));
dataTable.Columns.Add("File", typeof(byte[]));
}
}
}
}
}
}
catch (Exception ex)
{
throw ex; //Not getting any exception
}
}
//control just disappears after this line
我假设你的意思是下一行永远不会执行。
当向 Sql 服务器发送大量数据以进行保存时,这很可能是您所观察到的情况,实际上这些数据必须发送到服务器并且似乎没有任何反应然后进行处理,500 MB
可能需要一段时间才能完成。
如果您将命令的超时更改为 200 秒之类的值,我敢打赌您会在 200 秒后因超时收到 SqlException
。因为您将其设置为 0
它将无限期等待。
cmd.CommandTimeout = 200;
如果这是不可取的,那么您需要根据每 XX MB 所花费的时间量在时间和批量大小之间找到良好的平衡。您可以衡量的唯一方法是根据您的环境(网络容量、sql 服务器负载、客户端负载等)使用各种批量大小进行测试。