问题:
我有一个Web应用程序,人们可以上传XML,XML,CSV文件。然后,我将他们的内容插入我的Oracle DB。
技术详细信息:
我最近遇到了一个问题,我会发现尝试使用数据的异常。以前的开发人员创建了数据列表以管理数据。但是,这给了我们OutofMemory例外。我们正在使用LinqToExcel
库。
示例代码:
excel = new ExcelQueryFactory(excelFile);
IEnumerable<RowNoHeader> data = from row in excel.WorksheetNoHeader(sheetName)
select row;
List<List<string>> d = new List<List<string>>(data.Count());
foreach (RowNoHeader row in data)
{
List<string> list = new List<string>();
foreach (Cell cell in row)
{
string cellValue = cell.Value.ToString().Trim(' ').Trim(null);
list.Add(cellValue);
}
d.Add(list);
}
我试图更改代码,而是这样做了:
string connectionstring = string.Format(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 12.0;HDR=YES;';", excelFile);
OleDbConnection connection = new OleDbConnection();
connection.ConnectionString = connectionstring;
OleDbCommand excelCommand = new OleDbCommand();
excelCommand.Connection = connection;
excelCommand.CommandText = String.Format("Select * FROM [{0}$]", sheetName);
connection.Open();
DataTable dtbl = CreateTable(TableColumns);
OleDbDataReader reader = excelCommand.ExecuteReader();
while (reader.Read())
{
DataRow row = dtbl.NewRow();
dtbl.Rows.Add(row);
}
using (OracleCommand command = new OracleCommand(selectCommand, _oracleConnection))
{
using (OracleDataAdapter adapter = new OracleDataAdapter(command))
{
using (OracleCommandBuilder builder = new OracleCommandBuilder(adapter))
{
OracleTransaction trans = _oracleConnection.BeginTransaction();
command.Transaction = trans;
adapter.InsertCommand = builder.GetInsertCommand(true);
adapter.Update(dtbl);
trans.Commit();
}
}
}
但是,我仍然得到相同的 OUTOFMEMORY异常。我已经在网上阅读了,并且已经看到我应该制作我的项目X64并使用以下内容:
<runtime>
<gcAllowVeryLargeObjects enabled="true" />
</runtime>
但是,我无法将我的Web应用程序更改为在X64上运行。
我的解决方案是在这样的批处理中进行。
int rowCount = 0;
while (reader.Read())
{
DataRow row = dtbl.NewRow();
dtbl.Rows.Add(row);
if (rowCount % _batches == 0 && rowCount != 0)
{
DBInsert(dtbl, selectCommand);
dtbl = CreateTable(TableColumns);
}
}
private void DBInsert(DataTable dt, string selectCommand)
{
using (OracleCommand command = new OracleCommand(selectCommand, _oracleConnection))
{
using (OracleDataAdapter adapter = new OracleDataAdapter(command))
{
using (OracleCommandBuilder builder = new OracleCommandBuilder(adapter))
{
OracleTransaction trans = _oracleConnection.BeginTransaction();
command.Transaction = trans;
adapter.InsertCommand = builder.GetInsertCommand(true);
adapter.Update(dt);
trans.Commit();
}
}
}
}
}
它有效,但是这很慢。我想知道是否有一种方法可以解决记忆的问题,或并行写入内存。
我尝试使用线程并行插入数据,但这需要大量内存并抛出 OUTOFMEMORY异常。
Just 将1M行加载到数据表中。使用任何可用的大量导入机制来加载行流。Oracle,就像SQL Server一样,提供了几种批量导入数据的方法。
诸如列表或DataTable之类的集合使用内部缓冲区来存储它们在填充时重新分配的数据,使用原始大小的两倍。带有1M行,可导致重新分配的 lot 和记忆片段的 lot 。运行时可能不再能够甚至找到一个足以存储2M条目的内存的连续块。这就是为什么在创建新列表时设置capacity
参数很重要的原因。
除此之外,它没有任何目的,可以将所有内容加载到内存中,然后将其发送到数据库。实际上,一旦读取每个文件,或者一旦加载足够大的数字,就可以立即发送数据。而不是尝试一次加载1m行,而是每次读取500或1000行,然后将其发送到数据库。
此外,Oracle的Ado.net提供商还包括OracleBulkcopy类,该类以类似于SQLBulkCopy的SQL Server的方式工作。WriteToServer
方法可以接受DataTable 或DataReader 。您可以使用Databal Overload发送一批项目。一个更好的主意是使用接受读者的过载并让 class 收集批次并将其发送到数据库。
eg:
using(var bcp=OracleBulkCopy(connectionString))
{
bcp.BatchSize=5000;
bcp.DestinationTableName = "MyTable";
//For each source/target column pair, add a mapping
bcp.ColumnMappings.Add("ColumnA","ColumnA");
var reader = excelCommand.ExecuteReader();
bcp.WriteToServer(reader);
}