简单回答
简单的答案是否定的。 .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() 然后打开一个到数据库的新连接,
创建一个事务,并开始批量复制数据的一行
时间。它通过阅读提供的 DataReader 和
将每一行复制到一个空的 DataTable 中。然后数据表是大容量的
复制到目标数据库,并导致任何异常
从中捕获并记录(连同导致
它),然后循环在下一行重复。在末尾
我们回滚事务并返回完整的 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();