简单的答案
简单回答是不。速度如此之快的原因之一.NET's SqlBulkCopy
是它不会记录它所做的任何事情。您无法直接从.NET's SqlBulkCopy
异常中获取任何附加信息。但是,据说David Catriel写了一篇关于此的文章并提供了一个可能的解决方案,您可以在此处完整阅读。
尽管此方法可能会提供您正在寻找的答案,但我建议仅在调试时使用辅助方法,因为如果在您的代码中始终如一地运行,这很可能会对性能产生一些影响。
为什么要使用变通方法
缺少日志记录肯定会加快速度,但是当您正在抽取数十万行并且由于限制而突然其中一个出现故障时,您就会陷入困境。所有 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 中来实现。然后将 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();