Skip to main content

Is it possible to pass a DataSet to SQL and process the data in a SQLCLR stored procedure? This is the question I set out to answer. The .NET Framework 2.0 adds some features to the DataSet that make this scenario possible (WriteXml, ReadXml). I created a sample that proves this concept. First we need the CLR stored proc to handle the passed in dataset. Start by creating a new "SQL Server Project" in Visual Studio 2005. This example uses the Northwind database installed with SQLExpress.

// ============================================================================
// Passing a DataSet to a SQL CLR Stored Procedure
//
// Is it possible to pass a DataSet to SQL and process the data in a SQLCLR
// stored procedure? This is the question I set out to answer.
//
// The .NET Framework 2.0 adds some features to the DataSet that make this
// scenario possible (WriteXml, ReadXml). I created a sample that proves this
// concept.
//
// First we need the CLR stored proc to handle the passed in dataset. Start by
// creating a new "SQL Server Project" in Visual Studio 2005. This example uses
// the Northwind database installed with SQLExpress.
//
// https://blogs.msdn.microsoft.com/jpapiez/2005/09/26/passing-a-dataset-to-a-sqlclr-stored-procedure/
// ============================================================================

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;
using System.Xml.Serialization;
using System.Text;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    publicstaticvoid uspProcessCustomers(string customerData)
    {
        // Need to convert the inbound string to a byte array
        UTF8Encoding encoding = new UTF8Encoding();
        DataSet ds = new DataSet();

        // Convert the string to a byte array, then load into a MemoryStream
        using (MemoryStream ms = new MemoryStream(encoding.GetBytes(customerData)))
        {
            // Populate the DataSet from the MemoryStream
            ds.ReadXml(ms);
        }

        // Iterate through the dataset and process each record.
        foreach (DataRow dr in ds.Tables["customers"].Rows)
        {
            if (dr.RowState != DataRowState.Unchanged &&
                dr.RowState != DataRowState.Detached)
            {
                string procName = string.Empty;
                // Instantiate a SqlCommand object to perform the
                // required DB operation
                using (SqlCommand cmd = new SqlCommand())
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    // determine which procedure to execute
                    // based on the state of the row
                    switch (dr.RowState)
                    {
                        caseDataRowState.Added:
                        caseDataRowState.Modified:
                            if (dr.RowState == DataRowState.Added)
                            {
                                procName = "uspInsCustomer";
                            }
                            else
                            {
                                procName = "uspUpdCustomer";
                            }

                            // Populate the Parameters collection
                            cmd.Parameters.Add(new SqlParameter("@customerID", dr["CustomerID"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@companyName", dr["CompanyName"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@contactName", dr["ContactName"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@contactTitle", dr["ContactTitle"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@address", dr["Address"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@city", dr["City"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@region", dr["Region"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@postalCode", dr["PostalCode"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@country", dr["Country"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@phone", dr["Phone"].ToString()));
                            cmd.Parameters.Add(new SqlParameter("@fax", dr["Fax"].ToString()));

                            break;
                        caseDataRowState.Deleted:
                            procName = "uspDelCustomer";

                            // Populate the Parameters collection
                            cmd.Parameters.Add(new SqlParameter("@customerID", dr["CustomerID"].ToString()));

                            break;
                    }

                    using (SqlConnection conn = new SqlConnection("context connection=true"))
                    {
                        cmd.Connection = conn;
                        cmd.CommandText = procName;
                        cmd.Connection.Open();
                        cmd.ExecuteNonQuery();
                        cmd.Connection.Close();
                    }
                }
            }
        }
    }
}

// ============================================================================
// If you use Visual Studio to deploy the stored procedure to the database
// you'll need to drop and recreate the procedure. This is because the procedure
// gets created with the customerData parameter as NVarChar(4000). In order to
// accept even a minimal DataSet (in my test I populated the DS with 10
// records), 4000 chars isn't going to be large enough. To get around this you
// can use the new variable text types in SQL Server 2005, in this case
// NVarChar(max):
//
// CREATE PROCEDURE uspProcessCustomers
//     @CustomerData AS NVarChar(max)
// AS
// EXTERNAL NAME SQLData.StoredProcedures.uspProcessCustomers;
// ============================================================================

// ============================================================================
// Usage
//
// The procedures for inserting/updating/deleting aren't that interesting, so
// I'll leave those to your imagination. Now we need a client app that consumes
// this stored procedure. Start by creating a new Windows Forms client. Add to
// the client a button, then add the following to the button click event
// handler:
// ============================================================================

private void Click()
{
   // Create the dataset
   DataSet ds = new DataSet();

   // Create the data table
   DataTable dt = new DataTable("Customers");

   ds.Tables.Add(dt);

   // Create the table schema
   DataColumn dc = dt.Columns.Add("CustomerID", typeof(string));

   // the first column is the primary key
   dc.AllowDBNull = false;

   dc.Unique = true;
   dt.Columns.Add("CompanyName", typeof(string));
   dt.Columns.Add("ContactName", typeof(string));
   dt.Columns.Add("ContactTitle", typeof(string));
   dt.Columns.Add("Address", typeof(string));
   dt.Columns.Add("City", typeof(string));
   dt.Columns.Add("Region", typeof(string));
   dt.Columns.Add("PostalCode", typeof(string));
   dt.Columns.Add("Country", typeof(string));
   dt.Columns.Add("Phone", typeof(string));
   dt.Columns.Add("Fax", typeof(string));

   // Add a few records to the data table.
   for (int i = 1; i <= 10; i++)
   {
      // create a new  row
      DataRow dr = dt.NewRow();

      // populate the fields
      dr[0] = "C" + i.ToString();

      dr[1] = "Company Name " + i.ToString();
      dr[2] = "Contact Name " + i.ToString();
      dr[3] = "Contact Title " + i.ToString();
      dr[4] = "Address " + i.ToString();
      dr[5] = "City " + i.ToString();
      dr[6] = "Region " + i.ToString();
      dr[7] = "POSTL " + i.ToString();
      dr[8] = "Country " + i.ToString();
      dr[9] = "Phone " + i.ToString();
      dr[10] = "FAX " + i.ToString();

      // add the row to the table
      dt.Rows.Add(dr);
   }

   string strConnection = "Data Source=.\\sqlexpress;" +
      "Initial Catalog=Northwind;Integrated Security=True";

   // Now, connect to the database and pass the dataset
   using (SqlConnection conn = new SqlConnection(strConnection))
   {
      SqlCommand cmd = new SqlCommand("uspProcessCustomers", conn);
      cmd.CommandTimeout = 1800;
      cmd.CommandType = CommandType.StoredProcedure;

      // SqlParameter to hold the dataset
       SqlParameter param1 = new SqlParameter("@customerData", SqlDbType.NVarChar);

      // Create a temporary MemoryStream to hold the output
      // of the WriteXml method of the DataSet
      using (MemoryStream memoryStream = new MemoryStream())
      {
         ds.WriteXml(memoryStream);
         UTF8Encoding encoding = new UTF8Encoding();
         param1.Value = encoding.GetString(memoryStream.ToArray());
      }

      // Add the SqlParameter to the Parameters collection
      cmd.Parameters.Add(param1);

      // Open the connection to the database
      cmd.Connection.Open();

      // Execute the query
      cmd.ExecuteNonQuery();

      // Finally, close the connection
      cmd.Connection.Close();
   }
}