Nikhil's profileNikhil's SpacePhotosBlogListsMore Tools Help

Blog


    July 30

    TechTalk: Bulk Update into SQL from C# App

    In my previous article, I explained using a sample C# app on how to bulk insert data into SQL Server.

    There is an inherent problem. Bulk Inserts work fine as long as there are no constraint violations. What if the primary key data already exists? Or, how do we update using BulkCopy?

    These are some of the issues I ran into while doing some actual work.

    I will soon post a solution to this problem...

    TechTalk: Bulk Insert into SQL from C# App

     

    One of the common problems I have seen is to bulk upload data to a SQL Server database. If you have the flexibility to directly run your code in SQL, you have a ton of options. But let's say that you have to massage the data before you throw it in to the database, then you have to really know your SQL (well to do it in SQL).

    Let's say you have to read data from an RSS feed, parse it and then load it into SQL. Let's assume further that this feed updates every 2 hours. It would be a trivial task to write a C# app that reads and parses the feed. One crude way to upload this data would be to do a single row insert for each data element. This would be terribly inefficient. The other option would be to use .Net framework's SqlBulkCopy class.

    The basic template would be something like

    private void WriteToDatabase()
    {
        // get your connection string
        string connString = "";
        // connect to SQL
        using (SqlConnection connection = 
                new SqlConnection(connString))
        {
            // make sure to enable triggers
            // more on triggers in next post
            SqlBulkCopy bulkCopy = 
                new SqlBulkCopy
                (
                connection, 
                SqlBulkCopyOptions.TableLock | 
                SqlBulkCopyOptions.FireTriggers | 
                SqlBulkCopyOptions.UseInternalTransaction,
                null
                );
    
            // set the destination table name
            bulkCopy.DestinationTableName = this.tableName;
            connection.Open();
    
            // write the data in the "dataTable"
            bulkCopy.WriteToServer(dataTable);
            connection.Close();
        }
        // reset
        this.dataTable.Clear();
        this.recordCount = 0;
    }

    The above code snippet shows you the API usage. But before you actually do that, you need to follow a couple of steps to setup your data table.

    First, let's look at a simple record structure (as reflected in C# class):

    using System;
    using System.Data;
    using System.Configuration;
    
    /// <summary>
    /// Summary description for MyRecord
    /// </summary>
    public class MyRecord
    {
        public int TestInt;
        public string TestString;
    
    	public MyRecord()
    	{
    	}
    
        public MyRecord(int myInt, string myString)
        {
            this.TestInt = myInt;
            this.TestString = myString;
        }
    }
    

    Now, let's start dissecting the class that we will use to upload the data:

    using System;
    using System.Data;
    using System.Collections.Generic;
    
    using System.Data.SqlClient;
    using System.Configuration;
    using System.IO;
    
    namespace SqlExamples.FileLoader
    {
    /// <summary>
    /// Summary description for BulkUploadToSql
    /// </summary>
    public class BulkUploadToSql
    {
        private List<MyRecord> internalStore;
    
        protected string tableName;
        protected DataTable dataTable = new DataTable();
        protected int recordCount;
        protected int commitBatchSize;

    Note that we have an internal List data structure as well as the DataTable. This is redundant and you can avoid using the internalStore if your application does not need to massage the data before it's sent to SQL.

    I then define 2 private constructors. The reason is that we want to use the factory pattern to return our object to the caller.

        private BulkUploadToSql(
            string tableName, 
            int commitBatchSize)
        {
            internalStore = new List<MyRecord>();
    
            this.tableName = tableName;
            this.dataTable = new DataTable(tableName);
            this.recordCount = 0;
            this.commitBatchSize = commitBatchSize;
    
            // add columns to this data table
            InitializeStructures();
        }
    
        private BulkUploadToSql() : 
            this("MyTableName", 1000) {}
    

    Note that we set the commit batch size. This is a very important factor that needs to be fine tuned for your database. What this defines is the number of records that we would send in one shot to the database.

    The next step is to Initialize the data table with columns that reflect the actual table structure.

    private void InitializeStructures()
    {
        this.dataTable.Columns.Add("TI", typeof(Int32));
        this.dataTable.Columns.Add("TS", typeof(string));
    }
    
    

    I then provided a factory method to load data into my internal structure from a data source. In the example code below, I use a Stream, but this can be any data source from where you wish to populate your data.

    public static BulkUploadToSql Load(Stream dataSource)
    {
        // create a new object to return
        BulkUploadToSql o = new BulkUploadToSql();
        
        // replace the code below
        // with your custom logic 
        for (int cnt = 0; cnt < 10000; cnt++)
        {
            MyRecord rec = 
                new MyRecord
                (
                cnt, 
                string.Format("string{0}", cnt)
                );
            o.internalStore.Add(rec);
        }
    
        return o;
    }

    This would make sure that our class is properly initialized and loaded with data. Once the caller has a valid object, they can now "Flush" the data as shown below:

    public void Flush()
    {
        // transfer data to the datatable
        foreach (MyRecord rec in this.internalStore)
        {
            this.PopulateDataTable(rec);
            if (this.recordCount >= this.commitBatchSize)
                this.WriteToDatabase();
        }
        // write remaining records to the DB
        if (this.recordCount > 0)
            this.WriteToDatabase();
    }
    
    private void PopulateDataTable(MyRecord record)
    {
        DataRow row;
        // populate the values
        // using your custom logic
        row = this.dataTable.NewRow();
    
        row[0] = record.TestInt;
        row[1] = record.TestString;
    
        // add it to the base for final addition to the DB
        this.dataTable.Rows.Add(row);
        this.recordCount++;
    }
    

    In the example above, the call to Flush() actually massages the data (and at the same time loads it into the actual data table). As I mentioned before, you can actually skip this step if your application does not require massaging.

    As a example of an app that uses this class:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using SqlExamples.FileLoader;
    using System.IO;
    
    namespace DemoApp
    {
    class Program
    {
        static void Main(string[] args)
        {
            using (Stream s = 
                new StreamReader(@"C:\TestData.txt"))
            {
                BulkUploadToSql myData = 
                    BulkUploadToSql.Load(s);
                myData.Flush();
            }
        }
    }
    }
    

    As always, this is JUST demo code to explain a concept. This is NOT production quality code and please make sure to follow the coding guidelines in your team.

    Happy coding....

    July 16

    TechTalk: Handling global web service unhandled exceptions

    One of the most tiresome (but important) things when developing web services is handling un-handled exceptions. A good design principle forces you to catch and cast relevant exceptions raised by your web methods into more meaningful SOAP exceptions. But exceptions will occur.

    It is quite tedious to wrap each web method in a try/catch loop. This dictates a need for a common framework to handle unhandled web services exceptions. In this blog post, I will guide you through a step by step process for building one.

    Summary:

    1. Extend SoapExtension class and override the ProcessMessage() method.
    2. In ProcessMessage(), add special handler code for message stage of SoapMessageStage.AfterSerialize.
    3. Modify the web service's web.config file and add a <soapExtensionTypes> node to <webServices> section.

    Details:

    Ok, now let's dig deeper into the code.

    Step 1:

    The first step in the whole process is to extend the SoapExtension class override the ProcessMessage() method.

    using System.IO;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Xml;
    using System;

    public class TelspaceSoapExtension : SoapExtension
    {
        private Stream originalStream;
        private Stream updatedStream;

        public override object GetInitializer(Type serviceType)
        {
            return null;
        }

        public override object GetInitializer
        (LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
        {
            return null;
        }

        public override void Initialize(object initializer)
        {
        }

        public override Stream ChainStream(Stream stream)
        {
            originalStream = stream;
            updatedStream = new MemoryStream();
            return updatedStream;
        }

        private void Transfer(Stream inStream, Stream outStream)
        {
            StreamReader sr = new StreamReader(inStream);
            StreamWriter sw = new StreamWriter(outStream);
            sw.Write(sr.ReadToEnd());
            sw.Flush();
        }

        public override void ProcessMessage(SoapMessage soapMessage)
        {
            switch (soapMessage.Stage)
            {
                case SoapMessageStage.BeforeDeserialize:
                    Transfer(originalStream, updatedStream);
                    updatedStream.Position = 0;
                    break;

                case SoapMessageStage.AfterSerialize:
                    if ((soapMessage.Exception != null))
                    {
                        ExceptionProcessor processor = new ExceptionProcessor();

                        string details;

                        // handle our exception, and get the SOAP <detail> string
                        details = processor.HandleWebServiceException(soapMessage);

                        // read the entire SOAP message stream into a string
                        updatedStream.Position = 0;
                        TextReader tr = new StreamReader(updatedStream);

                        // insert our exception details into the string
                        string s = tr.ReadToEnd();
                        s = s.Replace("<detail />", details);

                        // overwrite the stream with our modified string
                        updatedStream = new MemoryStream();
                        TextWriter tw = new StreamWriter(updatedStream);
                        tw.Write(s);
                        tw.Flush();
                    }

                    updatedStream.Position = 0;
                    Transfer(updatedStream, originalStream);
                    break;
            }
        }

    }

    Step 2:

    The next step is to handle the exception and get out meaningful details from the exception. For this purpose, let's dig deeper into the ExceptionProcessor class. This class has one public method: HandleWebServiceException(System.Web.Services.Protocols.SoapMessage sm). This method is called from our ProcessMessage()case SoapMessageStage.AfterSerialize.

    Here is the code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;
    using System.Web.Services.Protocols;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Reflection;
    using SASMTPLib;
    using System.IO;

    class ExceptionProcessor
    {
        private string currentExceptionDetails;
        private string currentExceptionTypeName;

        private const string RootExceptionName = "System.Web.HttpUnhandledException";
        private const string RootWsExceptionName = "System.Web.Services.Protocols.SoapException";

        public string HandleWebServiceException(System.Web.Services.Protocols.SoapMessage sm)
        {
            HandleException(sm.Exception);
            XmlDocument doc = new XmlDocument();
            XmlNode detailNode = doc.CreateNode(XmlNodeType.Element, SoapException.DetailElementName.Name, SoapException.DetailElementName.Namespace);
            XmlNode typeNode = doc.CreateNode(XmlNodeType.Element, "ExceptionType", SoapException.DetailElementName.Namespace);
            typeNode.InnerText = currentExceptionTypeName;
            detailNode.AppendChild(typeNode);

            XmlNode messageNode = doc.CreateNode(XmlNodeType.Element, "ExceptionMessage", SoapException.DetailElementName.Namespace);
            messageNode.InnerText = sm.Exception.Message;
            detailNode.AppendChild(messageNode);

            XmlNode infoNode = doc.CreateNode(XmlNodeType.Element, "ExceptionInfo", SoapException.DetailElementName.Namespace);
            infoNode.InnerText = currentExceptionDetails;
            detailNode.AppendChild(infoNode);
            return detailNode.OuterXml.ToString();
        }

        private void HandleException(Exception ex)
        {
            try
            {
                currentExceptionDetails = NormalizeException(ex);
                currentExceptionTypeName = ex.GetType().FullName;

                // ignore root exceptions
                if (((currentExceptionTypeName == RootExceptionName) || (currentExceptionTypeName == RootWsExceptionName)))
                {
                    if (ex.InnerException != null)
                    {
                        currentExceptionTypeName = ex.InnerException.GetType().FullName;
                    }
                }
            }
            catch (Exception e)
            {
                currentExceptionDetails = string.Format("Error \'{0}\' while generating exception string", e.Message );
            }

            ExceptionToEmail();
        }

        private string NormalizeException(Exception ex)
        {
            StringBuilder sb = new StringBuilder();

            if (ex.InnerException != null)
            {
                if (((ex.GetType().ToString() == RootExceptionName) || (ex.GetType().ToString() == RootWsExceptionName)))
                {
                    return NormalizeException(ex.InnerException);
                }
                else
                {
                    sb.Append(NormalizeException(ex.InnerException));
                    sb.Append(Environment.NewLine);
                    sb.Append("(Outer Exception)");
                    sb.Append(Environment.NewLine);
                }
            }

            // get exception-specific information
            sb.Append("Exception Type:\t\t");
            try
            {
                sb.Append(ex.GetType().FullName);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("Exception Message:\t\t");
            try
            {
                sb.Append(ex.Message);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("Exception Target Method:\t");
            try
            {
                sb.Append(ex.TargetSite.Name);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("Stack Trace:\t\t");
            sb.Append(Environment.NewLine);
            try
            {
                sb.Append(ex.StackTrace);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            return sb.ToString();
        }

        private void ExceptionToEmail()
        {
            // Send e-mail code
        }

    }

    Step 3:

    The final step is to modify web.config of the web services to include the following section (under <system.web> node)

    <webServices>
      <soapExtensionTypes>
        <add type="TelspaceSoapExtension, ExceptionHandler" priority="1" group="High"/>
      </soapExtensionTypes>
    </webServices>