Monthly Archives: October 2009

Comparing Strings – Performance Options

When you are comparing 2 strings, in most cases the “bog standard” parameters are fine. However, if you are using your comparison in a sort operation which will be iterated many many times, you will get a better performance if you use one of the overloads which takes a StringComparison enumeration (or a CompareOptions enumeration, if you are using a Comparer object).

Take the following example, where I have iterated the Person.Contact table of the AdventureWorks database one million times. (As that table contains 19,972 tuples, the comparison method is hit 19,972 x 1 million times.)

                static void Main(string[] args)
        {
CompareInfo myCompare = CultureInfo.InvariantCulture.CompareInfo;

            AdventureWorksDataSetTableAdapters.ContactTableAdapter da = new AdventureWorksDataSetTableAdapters.ContactTableAdapter();
            AdventureWorksDataSet.ContactDataTable dt = da.GetData();

            List<string> names = new List<string>();

            for (int i = 0; i < dt.Rows.Count; ++i)
            {
                names.Add(dt.Rows[i][5].ToString());
            }

            string[] namesArr = names.ToArray();

            Console.WriteLine("\nData in. Press any key to compare...\n");
            Console.ReadKey();
            Console.WriteLine("\nGo\n");

            Stopwatch watch = new Stopwatch();
            watch.Start();

            for (int l = 0; l < 1000000; ++l)
            {
                for (int i = 0; i < namesArr.Length; ++i)
                {
                    //Console.WriteLine("Rogers".CompareTo(dt.Rows[i][5].ToString()));
                    myCompare.Compare("Rogers", namesArr[i], CompareOptions.Ordinal);
                    //string.Compare("Rogers", namesArr[i],  StringComparison.OrdinalIgnoreCase);
                    //Console.WriteLine(string.Compare("Rogers", namesArr[i]));
                }
            }
            watch.Stop();
            Console.WriteLine(watch.Elapsed);

            Console.WriteLine("\nPress any key to quit...");
            Console.Read();
}

If you pass the CompareOptions.Ordinal enumeration, the strings are compared using their Unicode value (which is much faster than a linguistic comparison). However, you do have to be a bit careful, as that does not take case into account – for example, it would consider “Zappa” to come before “abba”. But have no fear, as StringComparison.OrdinalIgnoreCase does take case into account.

I ran a few tests to see the different kinds of performance times for the various StringComparison options:

CompareOptions.Ordinal – 08:26m
StringComparison.OrdinalIgnoreCase -10:03m
Default linguistic comparison – 31:52m

As you can see, there is a pretty significant performance increase over the linguistic comparison.

Also, note that there is an overload of the constructor for generic Dictionaries which takes a StringComparer enumeration. This will make lookups on a Dictionary, which uses strings as keys, very quick :-

Dictionary<string, bool> someDictObject = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

So think carefully about what you need to achieve when selecting the best overload for string comparisons running in high-iteration scenarios.

Representing a SQL Server Tuple with an Xml Field

In my last post, I gave an example of retrieving XML data from an SQL Server relation. So how can we now present that data? As the saying goes, there is more than one way to skin a cat. I’m going to go through two solutions that I came up with off the top of my head.

The primary key of each row correlates to a fragment of XML data. But such a fragment is not very human readable, unless you remove the XML tags. One very easy way of presenting this would be through using an embedded GridView:

note: I did not put any time into presentation. I just wanted to concentrate on functionality here.

To achieve this, I cheated a little bit by binding the XML field to a Label, which I included in the same template column as the embedded GridView (see line 7 for the embedded GridView and line 16 for the Label):

        
            
                
                
                
                    
                        
                                    
                                    
                                    
                                    
                                    
                                    
                            
                        
                        
                    
                                        
            
            
            
            
            
        

Then, in the code behind, I created a DataSet by reading the Text property of the Label into a StringReader. I was able to use that to read in the XML to the DataSet. There are a couple of other small nuances I included which you will see in the following code:

        protected void GridViewEmployees_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                Label LabelREP = e.Row.FindControl("LabelREP") as Label;
                DataSet dsXml = new DataSet("REP");

                String firstLine = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
                String frag = Server.HtmlDecode(firstLine + LabelREP.Text);
                StringReader readString = new StringReader(frag);

                dsXml.ReadXml(readString);

                GridView GridViewEmployeeDetails = e.Row.FindControl("GridViewEmployeeDetails") as GridView;
                GridViewEmployeeDetails.DataSource = dsXml;
                GridViewEmployeeDetails.DataBind();

                LabelREP.Text = string.Empty;
            }
        }

It is possible to achieve the same thing without using a Label, but I’ll leave that for another day as it is a bit more complicated.

The other approach that I thought of is more interesting than that first approach. With this approach, I am going to use the LINQ object XElement to work with the XML. And then I will dynamically (and manually) build the contents of the “Sales REP” column using a Placeholder, which will replace the embedded GridView that I used in the first example.

So, looking at the template field in the GridView. I now have a placeholder, along with the Label which will initially store the contents of the Xml field:

                <asp:TemplateField HeaderText="Sales Rep">
                    <ItemTemplate>
                        <asp:PlaceHolder ID="PlaceHolderXml" runat="server"></asp:PlaceHolder>
                        <asp:Label ID="LabelREP" runat="server" Text='<%# ((DataRowView)Container.DataItem)["SalesRep"] %>' />
                    </ItemTemplate>
                </asp:TemplateField>

I have included the whole code for the RowDataBound event handler. However, a lot of that code is guarding against situations like when the XML message is empty. The core part to see what I am doing is from line 43. That shows how I parsed the XML fragment into an XElement object and then traversed each of its child nodes to extract the relevant information for display:

        protected void GridViewEmployees_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.RowType == DataControlRowType.DataRow)
            {

                //  Populate PlaceHolder control dynamically with data from the relevent Xml msg.
                PlaceHolder xmlOutput = e.Row.FindControl("PlaceHolderXml") as PlaceHolder;
                Label LabelREP = e.Row.FindControl("LabelREP") as Label;
                string xmlMsg = LabelREP.Text;

                switch (xmlMsg == string.Empty)
                {
                    // if there is no Xml 'Details' field, display that to page;
                    case true:
                        {
                            Literal promptText = new Literal();
                            promptText.Mode = LiteralMode.PassThrough;
                            promptText.Text = "<div class=\"msgContent\" ><div class=\"msgContentHeader\">Sales Rep Details</div> " +
                                "<p> There was no XML field for this blocked item. </p></div><br />";
                            xmlOutput.Controls.Add(promptText);

                            break;
                        }
                    default:
                        {
                            //  Check for empty msg elements
                            switch (xmlMsg.CompareTo("<messages />") == 0)
                            {
                                case true:
                                    {
                                        //  If the msg is empty, write that out to the Page.
                                        Literal promptText = new Literal();
                                        promptText.Mode = LiteralMode.PassThrough;
                                        promptText.Text = "<div class=\"msgContent\" ><div class=\"msgContentHeader\">Sales Rep Details</div> " +
                                            "<p> The xml fragment was an empty element. </p></div><br />";
                                        xmlOutput.Controls.Add(promptText);

                                        break;
                                    }
                                default:
                                    {
                                        //  Get the xml fragment and parse it into an XElement variable 
                                        XElement dbXmlField = XElement.Parse(xmlMsg);

                                        //  Popluate the Placeholder control with content.
                                        Literal wrapperDiv = new Literal();
                                        wrapperDiv.Mode = LiteralMode.PassThrough;
                                        wrapperDiv.Text = "<div class=\"msgContent\" ><div class=\"msgContentHeader\">Sales Rep Details</div>";
                                        xmlOutput.Controls.Add(wrapperDiv);

                                        foreach (XElement el in dbXmlField.Descendants())
                                        {
                                            //  Traverse the descendant nodes, and add them to the 
                                            //  placeholder text where the node itself has only 1 child
                                            //  node i.e. the text node containing the data that we are 
                                            //  interested in.
                                            if (el.DescendantNodes().Count() > 1)
                                                continue;
                                            Literal promptText = new Literal();
                                            promptText.Mode = LiteralMode.PassThrough;
                                            promptText.Text = string.Concat(string.Concat("<div class=\"msgLabel\">", el.Name, ": "), "</div>");
                                            xmlOutput.Controls.Add(promptText);

                                            Literal valueText = new Literal();
                                            valueText.Mode = LiteralMode.PassThrough;
                                            valueText.Text = string.Concat("<div class=\"msgValue\">", el.Value, "</div>");
                                            xmlOutput.Controls.Add(valueText);
                                        }

                                        //  Close out the content being built in the Placeholder.
                                        Literal wrapperDivClose = new Literal();
                                        wrapperDivClose.Mode = LiteralMode.PassThrough;
                                        wrapperDivClose.Text = "</div><br />";
                                        xmlOutput.Controls.Add(wrapperDivClose);
                                        break;
                                    }
                            }
                            break;
                        }
                }
                LabelREP.Visible = false;
            }
        }

And the fruit of my efforts looks something like the following (showing two rows from the result set):

Using LINQ with Dynamic Placeholder