Now This is Something Worth Looking Into

When you see something like this in the call stack, you have to know more:

[InvalidOperationException: This SqlTransaction has completed; it is no longer usable.]
   System.Data.SqlClient.SqlTransaction.ZombieCheck() +1623536
   System.Data.SqlClient.SqlTransaction.Rollback() +172
   Framework.Common.Database.RollbackTransaction() in C:\Framework\Common\Database.vb:413 
   Test.uxCreate_Click(Object sender, EventArgs e) in C:\Test.aspx.vb:47
   System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +154
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3691

And the error message is interesting as well: “This SqlTransaction has completed; it is no longer usable”.

A little background on what is going on: this is a database class that is managing a SQL transaction to be used by multiple objects.  The database object instance gets passed along to the different objects and they use it, participating in the transaction.  Somewhere along the way, maybe a commit is occurring and then the transaction becomes invalid.  That’s how it seems.

Tossing in a bunch of debug.writelines to see what what happening inside the methods popped up a message about a SQLException being raised because of a non-existent column name.  Fixing the schema problem fixed the zombie problem.  But what was the reason for the original error?

Let’s say you bring a sandwich into work.  You give that sandwich to someone and tell them to put it in the refrigerator for you.  This person (the fridgemaster) puts it in the fridge and comes back to the refrigerator a little later to find your sandwich moldy and spoiled, so he throws the sandwich out.  Later on yet, you go to the fridgemaster and ask for your sandwich.  He says “this sandwich has spoiled; it is no longer usable.”  In response to your puzzled look, the fridgemaster says “I did a ZombieCheck and it was moldy.”

In coding terms, the order of events was: TX started, SQL error occurred, TX rolled back (by SQL Server), SQLException raised and caught, TX rolled back in catch block (by user code) and unable to because the TX already was rolled back by SQL Server.  This rollback behavior is dependent on the severity of the error. In the case of the schema error, which presumably was interpreted as “this will NEVER work”, this equated to being severe enough to roll back the transaction.

Too Many Items In Combo Box: When One Is Just One Too Many

I got to troubleshoot a dumb error message today.  The error was "Too many items in combo box."  The situation was anything but.  I was only adding one item.

So I got it working and I wanted to find out why it happened in the first place.  The error it should have returned was "Value cannot be NULL" because that was the root of the problem.  So here’s a distilled piece of code to illustrate the problem.  Create a form and put a combo box on it.  The code for the form should look like:

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _ 
        Handles MyBase.Load

        Dim d As New DisplayItem
        ComboBox1.Items.Add(d)

    End Sub

End Class

Public Class DisplayItem
    Public Name As String

    Public Overrides Function ToString() As String
        Return Name
    End Function

End Class

The problem is the combo box is trying to display DisplayItem.Name, because that is what the ToString says to do, but the value of Name is Nothing.  You can fix this by setting the value of Name to String.Empty or something else.  The odd thing is that you can also fix the problem by commenting out the ToString override.  To figure this out, I fired up Reflector and went to see what was going on behind the scenes.

This particular situation is basically bypassing all the safe value checks done when adding an item to a list control.  I suppose Microsoft should add a test case for this scenario, but really, if the programmer is attentive, this shouldn’t happen.  I, naturally, happen to be inattentive.

Behind the scenes of the Add method of the Items collection, the first check is in the AddInternal method.  Since we’re passing in an instance of DisplayItem, it passes that check.  The next step is in the NativeAdd method.  At this point, we’ve done our NULL checks and it is assumed we can convert the object to a string.  This method now calls GetItemText.

GetItemText parses the properties of the object passed in and gets the string value.  If the DisplayMember property is not set, the control uses the ToString value of the object itself.  Because we overrode ToString, the control trusts us and returns the value from ToString, the Name value.  This turns out to be Nothing – Oops!  We’ve already passed the check for Nothing, so this sends bad data to the Win32 API, bubbling a failure error code back to NativeAdd.  If NativeAdd gets anything but a success, it always returns the message “Too Many Items In Combo Box”.  But the real reason is that you snuck a Nothing past the initial validation.

Interestingly, if the DisplayMember is set, and the value of the property is Nothing, it is handled properly in GetItemText.  If we converted Name to a private field and made a public property, then set the DisplayMember of  ComboBox1, it would work.  If your display member is another object that overrides the ToString function, you can get around that check as well and return Nothing, causing a failure.

The simple solution for this error message is to avoid NULL values.  The bottom line is to have .ToString always return a string, never Nothing.