SQLBulkCopy完成时行计数

26

我正在使用SQLBulkCopy移动大量数据。我已经实现了通知事件来在处理特定数量的行时通知我,但是当作业完成时,OnSqlRowsCopied事件不会触发。在SQLBulkCopy writetoserver完成后,如何获得复制的总行数?

8个回答

35

以下 hack(使用反射)是一种选择:

    /// <summary>
    /// Helper class to process the SqlBulkCopy class
    /// </summary>
    static class SqlBulkCopyHelper
    {
        static FieldInfo rowsCopiedField = null;

        /// <summary>
        /// Gets the rows copied from the specified SqlBulkCopy object
        /// </summary>
        /// <param name="bulkCopy">The bulk copy.</param>
        /// <returns></returns>
        public static int GetRowsCopied(SqlBulkCopy bulkCopy)
        {
            if (rowsCopiedField == null)
            {
                rowsCopiedField = typeof(SqlBulkCopy).GetField("_rowsCopied", BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
            }

            return (int)rowsCopiedField.GetValue(bulkCopy);
        }
    }

然后按以下方式使用该类:

int rowsCopied = SqlBulkCopyHelper.GetRowsCopied(bulkCopyObjectInYourCode);
希望这可以帮助你。

10
为什么不将它作为扩展方法呢?public static int GetRowsCopied(this SqlBulkCopy bulkCopy) - mhenry1384
3
我唯一的担忧是它获取了一个内部字段而没有使用公共API。那个内部字段在未来的实现中可能会更改,而不会破坏API,这将导致这段代码出错。(可能性虽小,但有可能发生,我以前见过类似的情况。) 访问私有字段因为这个原因有点危险--今天可能可以工作,但不能保证明天也能工作。(事实上,如果Microsoft在这里直接公开了一个属性,那就好了。) - Ari Roth
很奇怪,事件参数中它是一个 long,但却是一个 int。如果复制超过 int.max 行,会发生什么呢? - marsze

8

以下是我所做的——这是对Rahul Modi在本线程中提供的解决方案的轻微修改(基本上只是把SqlRowsCopied事件放到了同一行,我认为在这种情况下这样做比创建新的事件处理方法要更简洁一些):

private long InsetData(DataTable dataTable, SqlConnection connection)
{
   using (SqlBulkCopy copier = new SqlBulkCopy(connection))
   {
      var filesInserted = 0L;

      connection.Open();

      copier.DestinationTableName = "dbo.MyTable";
      copier.NotifyAfter = dataTable.Rows.Count;
      copier.SqlRowsCopied += (s, e) => filesInserted = e.RowsCopied;
      copier.WriteToServer(dataTable);

      connection.Close();

      return filesInserted;
   }
}

5

NotifyAfter设置为1。在 SqlRowsCopied 的处理程序中递增计数器。在完成 WriteToServer 后,读取计数器。


@MatthewMacFarland 拍手! - Ronnie Overby

5

为了完整性,我已经实现了一个扩展方法并包含了命名空间。如果你想要一个快速的解决方案来获取复制计数,请复制并粘贴这个类。注意:当设置“忽略重复项”时,此计数不考虑实际插入的行数。

namespace System.Data.SqlClient
{    
    using Reflection;

    public static class SqlBulkCopyExtension
    {
        const String _rowsCopiedFieldName = "_rowsCopied";
        static FieldInfo _rowsCopiedField = null;

        public static int RowsCopiedCount(this SqlBulkCopy bulkCopy)
        {
            if (_rowsCopiedField == null) _rowsCopiedField = typeof(SqlBulkCopy).GetField(_rowsCopiedFieldName, BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);            
            return (int)_rowsCopiedField.GetValue(bulkCopy);
        }
    }
}

4

通过使用SqlBulkCopy.SqlRowsCopied事件(在处理由NotifyAfter属性指定的行数之后每次发生)我们可以实现SQLBulkCopy完成后的行数统计。

using (SqlBulkCopy s = new SqlBulkCopy(db.Database.Connection as SqlConnection))
{
  s.SqlRowsCopied += new SqlRowsCopiedEventHandler(sqlBulk_SqlRowsCopied);
  s.BatchSize = csvFileData.Rows.Count;//DataTable
  s.NotifyAfter = csvFileData.Rows.Count;
  foreach (var column in csvFileData.Columns)
     s.ColumnMappings.Add(column.ToString(), column.ToString());
  // Set the timeout.
  s.BulkCopyTimeout = 60;
  s.DestinationTableName = "Employee_Data";
  s.WriteToServer(csvFileData);
}

private static void sqlBulk_SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
{
    long Count = e.RowsCopied;
}

1

这是我从 SqlBulkCopy 中获取行数的方法,重要的是你需要将 NotifyAfter 设置为 1:

var rowsInserted = 0L;
using var sbc = new SqlBulkCopy(dbConnection, SqlBulkCopyOptions.KeepIdentity, transaction);
sbc.NotifyAfter = 1;
sbc.SqlRowsCopied += (s, e) => rowsInserted = e.RowsCopied;
sbc.WriteToServer(dr);
//Get row count
return rowsInserted;

1
扩展方法:
(基于Benzi的答案
using System;
using System.Reflection;
using System.Data.SqlClient;
using static System.Reflection.BindingFlags;

namespace Extensions
{
    public static class SqlBulkCopyExtensions
    {
        private static readonly Lazy<FieldInfo> _rowsCopiedLazy = new Lazy<FieldInfo>(()
            => typeof(SqlBulkCopy).GetField("_rowsCopied", NonPublic | GetField | Instance));

        public static int GetRowsCopied(this SqlBulkCopy sqlBulkCopy)
            => (int)_rowsCopiedLazy.Value.GetValue(sqlBulkCopy);
    }
}

经测试,适用于.NET 4.6.1

请注意,字段类型为int(而事件参数中属性的类型为long)。如果复制了超过int.MaxValue行,则不确定会发生什么。


1
非常感谢 @marsze 和 @benzi!使用 sqlBulkCopyObject.GetRowsCopied(),这个方法非常好用! - TechSpud

0

我认为在完成后,您需要在表上运行COUNT()查询,就像MSDN示例这里中所示。

除此之外,您不能事先告诉吗?例如,如果您将DataTable传递给WriteToServer(),则可以通过对其执行.Rows.Count来知道有多少条记录。


如果您正在使用IDataReader,只需将其包装起来即可,实际上不应该有调用count的必要,但这是一种可行的方法。 - Sam Saffron
@Sam,你说的“wrap it”是什么意思?我有一个SqlDataReader,最接近行数的属性是RecordsAffected,但在这种情况下它总是-1... - chezy525
1
这是比下面列出的方法更安全的方法(诚然,它们很流畅!)——访问私有字段可能会在未来突然中断(Microsoft 可以通过更改字段名称来更改公共 API 的实现而不破坏公共 API),但计数查询仍将起作用。 - Ari Roth
@AriRoth 我们明白了,人们必须停止在每个反射解决方案上发表评论。出于许多原因,它仍然比使用count()更安全(也许表已经在之前被填充,也许我们没有从源复制所有行,也许这只是一个很长的表,而count()需要很长时间...) - marsze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接