Archives for : November2010

String comparisons

At one of the blogs I most frequent there was a post asking, “Where is the bug?”. In the comments section it was mentioned,

Use ToUpperInvariant rather than ToLowerInvariant when normalizing strings for comparison.

Sure enough if you go to the Microsoft page on it, it says

Use the String.ToUpperInvariant method instead of the String.ToLowerInvariant method when you normalize strings for comparison

Not wanting to blindly trust “Teh MAN” I threw together a quick testbed for testing many of the variants on doing string comparisons.

Here are the results (running 1 million comparisons) comparing
Guid.NewGuid().ToString() to “Now is the time for all good men to come to the aid of their country.”:

Method Time
equals operator 10 milliseconds
equals method 8 milliseconds
CompareOrdinal(ToUpper) 436 milliseconds
CompareOrdinal(ToUpperInvariant) 876 milliseconds
CompareOrdinal(ToLower) 418 milliseconds
CompareOrdinal(ToLowerInvariant) 878 milliseconds
Compare(OrdinalIgnoreCase) 29 milliseconds
Compare(InvariantCultureIgnoreCase) 113 milliseconds
Compare(CurrentCultureIgnoreCase) 135 milliseconds
Compare(ToUpperInvariant, Ordinal) 889 milliseconds
Compare(ToLowerInvariant, Ordinal) 889 milliseconds

If you read through Microsoft’s whole article you’ll see for case-insensitive ordinal comparisons they recommend

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

As you can see in the results this is borne out. It’s not inconceivable that these results could be significant, especially when doing a significant number of string comparisons.

The problem arises when I change what’s being compared to a string that is almost identical to the original.

Here are the results (running 1 million comparisons) comparing
“Now is the time for all good men to come to the aid of their countrt.” to
“Now is the time for all good men to come to the aid of their country.”:

Method Time
equals operator 37 milliseconds
equals method 36 milliseconds
CompareOrdinal(ToUpper) 502 milliseconds
CompareOrdinal(ToUpperInvariant) 1111 milliseconds
CompareOrdinal(ToLower) 494 milliseconds
CompareOrdinal(ToLowerInvariant) 1120 milliseconds
Compare(OrdinalIgnoreCase) 369 milliseconds
Compare(InvariantCultureIgnoreCase) 176 milliseconds
Compare(CurrentCultureIgnoreCase) 191 milliseconds
Compare(ToUpperInvariant, Ordinal) 1096 milliseconds
Compare(ToLowerInvariant, Ordinal) 1099 milliseconds

In both cases I ran the test (with code below this) several times and each time it gave similar results.

So what does this all mean? If you’re only doing a few thousand comparisons where case is an issue I wouldn’t worry about anything and just keep doing what you’re doing.
Other then that I can only recommend staying with Microsoft’s suggestion. While there was significant differences between the results, in general Microsoft’s suggestion would be more applicable without some sort of explicit knowledge of the data. I think if you’re going to analyze much beyond this you risk attempting to over-optimize.

Thanks,
Brian

string myStringToCompare = "Now is the time for all good men to come to the aid of their countrt.";// Guid.NewGuid().ToString();
string originalString = "Now is the time for all good men to come to the aid of their country.";
string results = "";

Stopwatch watch = new Stopwatch();

int numComparisons = 1000000;
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (myStringToCompare == originalString) { }
}
watch.Stop();
results += "equals operator:" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (myStringToCompare.Equals(originalString)) { }
}
watch.Stop();
results += "nequals method:" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.CompareOrdinal(myStringToCompare.ToUpper(), originalString.ToUpper()) == 0) { }
}
watch.Stop();
results += "nCompareOrdinal(ToUpper):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.CompareOrdinal(myStringToCompare.ToUpperInvariant(), originalString.ToUpperInvariant()) == 0) { }
}
watch.Stop();
results += "nCompareOrdinal(ToUpperInvariant):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.CompareOrdinal(myStringToCompare.ToLower(), originalString.ToLower()) == 0) { }
}
watch.Stop();
results += "nCompareOrdinal(ToLower):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.CompareOrdinal(myStringToCompare.ToLowerInvariant(), originalString.ToLowerInvariant()) == 0) { }
}
watch.Stop();
results += "nCompareOrdinal(ToLowerInvariant):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.Compare(myStringToCompare, originalString, StringComparison.OrdinalIgnoreCase) == 0) { }
}
watch.Stop();
results += "nCompare(OrdinalIgnoreCase):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.Compare(myStringToCompare, originalString, StringComparison.InvariantCultureIgnoreCase) == 0) { }
}
watch.Stop();
results += "nCompare(InvariantCultureIgnoreCase):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.Compare(myStringToCompare, originalString, StringComparison.CurrentCultureIgnoreCase) == 0) { }
}
watch.Stop();
results += "nCompare(CurrentCultureIgnoreCase):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.Compare(myStringToCompare.ToUpperInvariant(), originalString.ToUpperInvariant(), StringComparison.Ordinal) == 0) { }
}
watch.Stop();
results += "nCompare(ToUpperInvariant, Ordinal):" + (watch.ElapsedMilliseconds) + " milliseconds";

watch.Reset();
watch.Start();
for (int i = 0; i < numComparisons; i++)
{
    if (string.Compare(myStringToCompare.ToLowerInvariant(), originalString.ToLowerInvariant(), StringComparison.Ordinal) == 0) { }
}
watch.Stop();
results += "nCompare(ToLowerInvariant, Ordinal):" + (watch.ElapsedMilliseconds) + " milliseconds";

txtResults.Text = results;

netTiers and stored procedures

Want to see what netTiers sees when it determines what return type should be for a stored procedure?

It uses FMTONLY ON which returns the metadata on the results if the stored procedure was run.

netTiers bases return type from stored procedures based on the results of running FMTONLY ON with the stored procedure passing null to all values. The problem with this approach is that all branches are run. This means if you have an if/else where two selects are run you will get a DataSet return type, even if both branches return all the fields needed for the base type. This is because two sets of data is returned.
So, if you want to see what netTiers sees try:

SET FMTONLY ON;
EXEC [dbo].[_Plan_GetByUserId] null
SET FMTONLY OFF;

where _Plan_GetByUserId is your stored procedure and all parameters that are passed in are null.

Some of the other problems with this is that when the stored proc is run between FMTONLY the permissions it has to run are severely restricted because it is run as a user with the membership public. You can’t create temp tables due to this permissions restriction as well as other issues. Additionally you’ll get a void method for the get if your stored proc errors out on one of the parameters being null.

Just wanted to throw in a quick write up about common table expressions (CTE) in sql server.

There are plenty of good and complete write-ups on ctes floating around teh tubes these days but I wanted to address my paticular issue, if nothing else so I don’t forget about them.

Imagine you have a hiearchy of units (like military units) though this same thing can apply to other cases like attributes or types. Working with some made-up data you may have:

5th Division
    9th Brigade
        17th Regiment
        21st Regiment
    12 Brigade
        54th Regiment
        88th Regiment

I have a case where a user will be assigned to just one level of these units but being assigned to one level means that all child units also apply to the user.

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[_Unit_GetByUserId]
(
    @UserId [uniqueidentifier]
)
AS

    with c as (
        select b.* from Unit b, UserUnit u 
            where u.UserId = @UserId 
            and b.Id = u.UnitId
        union all
        select b.* from Unit b 
            join c on b.ParentId = c.Id 
            where b.Id != b.ParentId
    )
	
    select * from c

This allows me to get all the units that apply to the user. In this case, if a user is assigned to the 9th Brigade then the stored proc will return: 9th Brigade, 17th Regiment, 21st Regiment

The key is in the union where the join to the CTE is and the ParentId is being set to the Id coming from the CTE.

I realize there isn’t much explanation here as to what is happening but if you follow the links above you’ll get a better explanation then I could give.

WP7, ApplicationBar, ImageUri and the Icon not showing up

I know this may sound stupid but when working with the WP7 ApplicationBar make sure the images you are using have the following properties:

Build Action: Content
Build Action: Content
Copy to Output: Copy always (or ‘Copy if newer’, either should work)

I spent a half an hour trying to figure out why my application bar was showing an “X” instead of the image I was putting in IconUri. When first adding images the properties default to:

Build Action: Resource
Copy to Output: Do not copy

Since the application bar is referencing the image by Uri it can’t seem to find it embedded in the application.

My xaml looks like:

<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton
            IconUri="/Images/appbar.feature.settings.rest.png"
            Text="Settings"/>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>