Bulk Copy within a Sql Server CLR Stored Procedure

Whilst it’s not immediately obvious, one can perform a Bulk Copy operation as part of a Sql Server CLR Stored Procedure. There are however some gotcha’s.

First off is that you need to stay away from Context Connections for the actual operation. Whilst you can technically use a Context Connection for the first part, I’d personally not recommend you do for reasons that will become apparent shortly.

The code below is taken from one of my procedures using a homebrew class that provides me with information regarding a table – including the various elements of a fully qualified 4 part name (Server.Database.Schema.Table).

The first part of the code handles the bulk import:

  1. using (SqlConnection sourceCx = new SqlConnection(string.Format("Data Source={0};Initial Catalog={1};Integrated Security=SSPI;", sourceTable.ServerName, sourceTable.DatabaseName)))
  2.             {
  3.                 sourceCx.Open();
  4.  
  5.                 using (SqlCommand command = new SqlCommand(string.Format("Select * From {0};", sourceTable.FqTableName2Part), sourceCx))
  6.                 {
  7.                     using (SqlDataReader reader = command.ExecuteReader())
  8.                     {
  9.                         using (SqlBulkCopy bulkCopy = new SqlBulkCopy(string.Format("Data Source={0};Initial Catalog={1};Integrated Security=SSPI;", destinationTable.ServerName, destinationTable.DatabaseName)))
  10.                         {
  11.                             bulkCopy.DestinationTableName = destinationTable.FqTableName2Part;
  12.                             bulkCopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(bulkCopy_SqlRowsCopied);
  13.                             bulkCopy.DestinationTableName = destination;
  14.                             bulkCopy.BatchSize = 10000;
  15.                             bulkCopy.NotifyAfter = bulkCopy.BatchSize;
  16.                             bulkCopy.WriteToServer(reader);
  17.                             bulkCopy.Close();
  18.                         }
  19.                     }
  20.                 }
  21.             }

 

As you can see I’m using two SSPI connections in place of the Context based Connection that’s normally provided. I decided to use SSPI to avoid needing to store user names and passwords. Your mileage and methodology may vary.

The next section is the reason why I advise not using a Context Connection for the DataReader:

  1. static void bulkCopy_SqlRowsCopied(object sender, SqlRowsCopiedEventArgs e)
  2.      {
  3.          string call = string.Format("RaisError('{0} rows copied', 0, 0) With NoWait", e.RowsCopied);
  4.          SqlContext.Pipe.ExecuteAndSend(new SqlCommand(call));  
  5.      }

 

In order for this event to work correctly the Context Connection must be free – which will not be the case if you use it for the DataReader. Running the RaisError via ExecuteAndSend ensures the results are not gobbled up by the any special pipe that SqlCommand.ExecuteNoQuery creates (see Vadim Tryshev’s excellent post on RAISERROR in CLR Routines). Finally I use a 0 for both the State and Severity to avoid any subsequent messages that RaisError would otherwise produce.

Sql Server 2008, SP1

0 comments:

Post a Comment