我正在使用带有ObjectReader(FastMember)的SqlBulkCopy(.NET)来从基于XML的文件执行导入。我添加了正确的列映射。
在某些情况下,我收到错误:无法将参数值从String转换为Int32。
我想了解如何做 1.跟踪失败的实际表列 2.获取"当前"在ObjectReader上
示例代码:
using (ObjectReader reader = genericReader.GetReader())
{
try
{
sbc.WriteToServer(reader); //sbc is SqlBulkCopy instance
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
" ex"携带更多信息然后只是错误:
System.InvalidOperationException : The given value of type String from the data source cannot be converted to type int of the specified target column.
答案 0 :(得分:0)
简单回答
简单的答案是否定的。 .NET's SqlBulkCopy
如此之快的原因之一是它不记录它所做的任何事情。您无法直接从.NET's SqlBulkCopy
例外获取任何其他信息。但是,David Catriel已经撰写了一篇关于此问题的文章并提供了一个可以解决的问题,您可以完全阅读here。
即使这个方法可能提供您正在寻找的答案,我建议在调试时仅使用辅助方法,因为如果在代码中一致地运行,这可能会对性能产生一些影响。
为什么要使用
缺少伐木肯定会加快速度,但是当你的时候 抽了几十万行然后突然失败了 其中一个是因为一个约束,你被困住了。一切 SqlException会告诉你一个给定的东西出了什么问题 约束(你至少得到约束的名字),但是那个 关于它。然后你不得不回到你的来源,跑 在其上单独的SELECT语句(或手动搜索),并找到 罪魁祸首就是你自己。
最重要的是,如果你这样做,它可能是一个非常漫长而且反复的过程 由于SqlBulkCopy,因此得到了几个潜在故障的数据 一旦第一次失败就会停止。一旦你纠正了 一,你需要重新运行负载以找到第二个错误等。
优势:
报告SqlBulkCopy遇到的所有可能错误
报告所有罪魁祸首数据行,以及行将导致的例外情况
整个事务在最后回滚的事务中运行,因此不会提交任何更改。
<强>缺点:强>
对于极大量的数据,可能需要几分钟时间。
此解决方案具有反应性;即,错误不会作为SqlBulkCopy.WriteToServer()进程引发的异常的一部分返回。相反,在引发异常之后执行此辅助方法,以尝试捕获所有可能的错误及其相关数据。这意味着,如果发生异常,您的流程运行时间会比运行批量复制更长。
您无法从失败的SqlBulkCopy中重用相同的DataReader对象,因为读取器只能转发无法重置的消防软管。您需要创建一个相同类型的新阅读器(例如,重新发布原始SqlCommand,根据相同的DataTable重新创建阅读器等)。
使用GetBulkCopyFailedData方法
private void TestMethod()
{
// new code
SqlConnection connection = null;
SqlBulkCopy bulkCopy = null;
DataTable dataTable = new DataTable();
// load some sample data into the DataTable
IDataReader reader = dataTable.CreateDataReader();
try
{
connection = new SqlConnection("connection string goes here ...");
connection.Open();
bulkCopy = new SqlBulkCopy(connection);
bulkCopy.DestinationTableName = "Destination table name";
bulkCopy.WriteToServer(reader);
}
catch (Exception exception)
{
// loop through all inner exceptions to see if any relate to a constraint failure
bool dataExceptionFound = false;
Exception tmpException = exception;
while (tmpException != null)
{
if (tmpException is SqlException
&& tmpException.Message.Contains("constraint"))
{
dataExceptionFound = true;
break;
}
tmpException = tmpException.InnerException;
}
if (dataExceptionFound)
{
// call the helper method to document the errors and invalid data
string errorMessage = GetBulkCopyFailedData(
connection.ConnectionString,
bulkCopy.DestinationTableName,
dataTable.CreateDataReader());
throw new Exception(errorMessage, exception);
}
}
finally
{
if (connection != null && connection.State == ConnectionState.Open)
{
connection.Close();
}
}
}
GetBulkCopyFailedData()然后打开一个到数据库的新连接, 创建一个事务,并开始批量复制a中的一行数据 时间。它通过读取提供的DataReader来实现 将每一行复制到一个空的DataTable中。然后DataTable是批量的 复制到目标数据库,以及产生的任何异常 从中捕获,记录(以及导致的DataRow) 然后循环再次与下一行重复。在末尾 我们回滚事务并返回完整的DataReader 错误信息。现在应该修复数据源中的问题 微风。
GetBulkCopyFailedData方法
/// <summary>
/// Build an error message with the failed records and their related exceptions.
/// </summary>
/// <param name="connectionString">Connection string to the destination database</param>
/// <param name="tableName">Table name into which the data will be bulk copied.</param>
/// <param name="dataReader">DataReader to bulk copy</param>
/// <returns>Error message with failed constraints and invalid data rows.</returns>
public static string GetBulkCopyFailedData(
string connectionString,
string tableName,
IDataReader dataReader)
{
StringBuilder errorMessage = new StringBuilder("Bulk copy failures:" + Environment.NewLine);
SqlConnection connection = null;
SqlTransaction transaction = null;
SqlBulkCopy bulkCopy = null;
DataTable tmpDataTable = new DataTable();
try
{
connection = new SqlConnection(connectionString);
connection.Open();
transaction = connection.BeginTransaction();
bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.CheckConstraints, transaction);
bulkCopy.DestinationTableName = tableName;
// create a datatable with the layout of the data.
DataTable dataSchema = dataReader.GetSchemaTable();
foreach (DataRow row in dataSchema.Rows)
{
tmpDataTable.Columns.Add(new DataColumn(
row["ColumnName"].ToString(),
(Type)row["DataType"]));
}
// create an object array to hold the data being transferred into tmpDataTable
//in the loop below.
object[] values = new object[dataReader.FieldCount];
// loop through the source data
while (dataReader.Read())
{
// clear the temp DataTable from which the single-record bulk copy will be done
tmpDataTable.Rows.Clear();
// get the data for the current source row
dataReader.GetValues(values);
// load the values into the temp DataTable
tmpDataTable.LoadDataRow(values, true);
// perform the bulk copy of the one row
try
{
bulkCopy.WriteToServer(tmpDataTable);
}
catch (Exception ex)
{
// an exception was raised with the bulk copy of the current row.
// The row that caused the current exception is the only one in the temp
// DataTable, so document it and add it to the error message.
DataRow faultyDataRow = tmpDataTable.Rows[0];
errorMessage.AppendFormat("Error: {0}{1}", ex.Message, Environment.NewLine);
errorMessage.AppendFormat("Row data: {0}", Environment.NewLine);
foreach (DataColumn column in tmpDataTable.Columns)
{
errorMessage.AppendFormat(
"\tColumn {0} - [{1}]{2}",
column.ColumnName,
faultyDataRow[column.ColumnName].ToString(),
Environment.NewLine);
}
}
}
}
catch (Exception ex)
{
throw new Exception(
"Unable to document SqlBulkCopy errors. See inner exceptions for details.",
ex);
}
finally
{
if (transaction != null)
{
transaction.Rollback();
}
if (connection.State != ConnectionState.Closed)
{
connection.Close();
}
}
return errorMessage.ToString();