Friday, October 7, 2011

Working with a dynamic RadioButtonList

ASP.NET provides a RadioButtonList control, which has the useful ability to bind text and values (perhaps, for example, retrieved from a database) to a set of radio buttons at runtime. You might find the need to examine and manipulate attributes of the radio buttons in the list, even though you don't know in advance what those radio buttons may be. Here's an example of how to do that.

In my example, there's a list of radio buttons. The list may (or may not) include a radio button labeled "Countdown". If the list includes a "Countdown" radio button, then I want to disable it. Furthermore, if "Countdown" was selected, I want to instead select the first radio button in the list. Here's the code:

foreach (ListItem listItem in rblSelectionType.Items)
{
if (listItem.Text == "Countdown")
{
if (listItem.Selected)
rblSelectionType.SelectedIndex = 0;
listItem.Enabled = false;
break;
}
}

Thanks to this tutorial for setting me on the right track.

Monday, August 29, 2011

How a small typo caused an infinite loop and wasted half a day

I recently made a few rather minor changes to my .NET website. One of these changes introduced a bug, a "request has timed out" error that could be reliably replicated. Finding the cause took a whole afternoon.

One of the changes consisted of adding a column to one table in a SQL Server database and a few lines of C# code to read and display the value of that column. Here's the class where I added the new field, displayName:

public class MenuSize
{
private int menuSizeID;
private string displayName;
private int displayOrder;
private string name;
private int sizeID;
private bool showName;
private int menuCategoryID;

public string DisplayName { get { return displayName; } set { displayName = value; } }
public int MenuSizeID { get { return menuSizeID; } set { menuSizeID = value; } }
public int DisplayOrder { get { return displayOrder; } set { displayOrder = value; } }
public int SizeID { get { return sizeID; } set { sizeID = value; } }
public string Name { get { return name; } set { name = value; } }
public bool ShowName { get { return showName; } set { showName = value; } }
public int MenuCategoryID { get { return menuCategoryID; } set { menuCategoryID = value; } }
}

Note the line private string displayName;. I typed it wrong, putting diplayName instead of displayName.

Then when I added the line public string DisplayName { get { return displayName; } set { displayName = value; } }, I relied on Visual Studio's IntelliSense to fill in the variable name displayName. Because of my typo, displayName with a lowercase "d" didn't exist, and IntelliSense put in DisplayName with a capital "D". As a result (which I didn't notice), the line read public string DisplayName { get { return DisplayName; } set { DisplayName = value; } }.
Since this property sets DisplayName to itself, an infinite loop results! Just to make it really hard to find this bug, the method that instantiates MenuSize is inside a WSDL web service, and my web site calls the web service. So all I could tell was the call to the web service method was timing out. It took me about 6 hours of troubleshooting to figure out why. All because of a stupid missing letter "s" in displayName!

Friday, July 15, 2011

Good old-fashioned SQL optimzation using an index

Sometimes simple, basic stuff really works.

This query was taking too long to execute:
SELECT ISNULL(SUM(Quantity),0) as 'count'
FROM dbo.AllOrderItems WITH (NOLOCK)
WHERE CustomerID=@customerID AND ItemID=@itemID

AllOrderItems has nearly 5 million rows. I executed this query with a customerID and itemID that corresponding to 3 records. It took 23 seconds to run.

I created an index on the CustomerID and ItemID columns like this:
CREATE NONCLUSTERED INDEX [Customer_Item] ON [dbo].[ArcOrderItems]
(
[CustomerID] ASC,
[ItemID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

With this index, the execution time was reduced from 23 seconds to less than one second.

Moral of the story: sometimes the obvious fix is the right one when it comes to database optimization.

Bonus tip: When testing the speed of a query, it will often run faster the second and subsequent times, because SQL caches the results. These two commands clear the cache, ensuring a realistic measurement of the execution time:
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;

Interesting book about engineering

When I attended June's Google NYC Tech Talk on performance bugs, speaker Jon Bentley recommended the book To Engineer Is Human by Henry Petroski. I enjoyed Jon's presentation so much, I read the book.

This book is not about software. It's primarily about civil and aeronautical engineering. But the lessons it imparts about how to learn from failure and create more reliable products apply nicely to software engineering.

The writing style was a bit formal, and some of the examples -- this book is from the 1980s -- are dated. But overall I enjoyed it, and it made me think about how to be a better software developer.

Sunday, June 5, 2011

Using SQL to concatenate values from multiple rows into a single string

Lots of SQL programmers probably know this trick already, but it was a new one for me and seems worth sharing. In just a few lines of code, without using a cursor, you can concatenate values from multiple rows into a single string. For example, if you have a table with a FirstName column, and a SELECT statement returns the FirstName values 'Aaron', 'Betty' and 'Carol', it's easy to form a string like this: 'Aaron, Betty, Carol'. The comments in the code snippet below explain how.

-- create a test table and insert some rows of test data
CREATE TABLE Test (
FirstName VARCHAR(10),
LastName VARCHAR(10)
)
INSERT INTO Test VALUES ('Aaron', 'Aardvark')
INSERT INTO Test VALUES ('Betty', 'Baboon')
INSERT INTO Test VALUES ('Carol', 'Condor')

-- form a comma-delimited list of the FirstName values from all records
DECLARE @myList VARCHAR(100) -- this works with a VARCHAR or NVARCHAR variable, but _not_ with a CHAR variable
SET @myList = '' -- initialize the variable; if you don't, the output string will be blank

-- The next SELECT statement is the key. It appends the FirstName from each record to whatever is in the string so far.
-- The CASE statement is just for putting commas between the names, but not in front of the first one.
SELECT @myList = @myList + CASE @myList WHEN '' THEN '' ELSE ', ' END + FirstName FROM Test
SELECT @myList AS MyList -- output the string, which should say 'Aaron, Betty, Carol'

-- delete the test table
DROP TABLE Test

Monday, May 16, 2011

Fun book about AI - "Final Jeopardy"

An interesting computer science read: "Final Jeopardy" by Stephen Baker. It's an account of IBM's massive project to build Watson, the computer system that defeated two human champions on the game show "Jeopardy." My key takeaway is that there are two approaches to artificial intelligence, and they're both hard.

You can simulate human-like reasoning with something like a neural network -- hard because it requires vast processing power

Or you can "teach" a computer system myriad rules and bits of information -- hard because it requires lots of people to spend lots of time.

Tuesday, May 10, 2011

Console app to send a SOAP request

For troubleshooting purposes, it can be useful to invoke a web service method by sending an XML-formatted SOAP request and receiving the response. Here's a C# console application to do that. Just replace the URL and the XML request string with your own values.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;

namespace post
{
class Program
{
static void Main(string[] args)
{
// Replace with the URL of your web service.
string strUrl = "http://oosapi/OosApiService.asmx";

// Replace with the XML-formatted SOAP request for the web service method you wish to call.
// Don't forget to escape any double quotes.
string strRequest = @"b5a3e821-6f7d-4ad0-b5d0-b723222bc319";

// Create the Request object.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(strUrl);
req.Method = "POST";
req.ContentType = "text/xml";
req.ContentLength = strRequest.Length;

// Send the request.
StreamWriter swRequest = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII);
swRequest.Write(strRequest);
swRequest.Close();

// Receive the response.
StreamReader srResponse = new StreamReader(req.GetResponse().GetResponseStream());
string strResponse = srResponse.ReadToEnd();
srResponse.Close();

// Output the response.
Console.WriteLine(strResponse);
Console.ReadKey();
}
}
}