Being a .NET developer, I try to give back to the community as much as I can, but the internet as a whole is rather critical of us .NET developers, and sometimes maintaining a blog can be a rather thankless and frustrating experience.
I decided to take some time off from blogging, to tackle a handful of difficult programming projects and to come back when I was feeling more refreshed.
I have been working on an application framework for some time, and I thought that a small snippet from that would be beneficial to my readers.
A common practice when designing a N-tiered framework is to create custom domain (or business) objects that are responsible for populating themselves from raw datareaders.
For example, you may have a database table that represents a "Customer". This approach would create an object that models the table schema as a strongly typed class. My usual technique to populate such a class is to pass a database row in the form of a IDataReader to the class constructor.
However, you almost always end up in a scenario where the stored procedure that is ultimately responsible for the creation of the IDataReader may only return a portion of the columns that the domain object expects.
Because of this, people advocate "defensive" coding techniques, where you validate the existence of each column before attempting to read and assign it to a class property.
Because IDataReader (and its derivatives, such as SQLDataReader and ODBCDataReader) do not expose a "HasColumn" property, I have witnessed some creative and horrible approaches to this technique:
1 try
2 {
3 this.CustomerName = reader["CustomerName"].ToString();
4 }
5 catch (IndexOutOfRangeException)
6 {
7 this.CustomerName = string.Empty;
8 }
Exceptions are a good thing, but using them in this fashion is a poor design choice. Each time an exception is thrown and caught, the call stack must traversed and a great deal of overhead is added on a per column basis.
Lets see if we can do better.
The first order of business is to refractor the column checking logic into a static helper method. There is no need to repeat this code for each column. I already had a static Utility class in my database namespace, so I placed this method there.
The second order of business is to take a page out of the TryParse notebook, and utilize an output parameter to pass our domain object variable. The new methods signature is quite obvious as to how this method will work:
1 public static void ReadColumn(ref SqlDataReader reader, string columnName, out string column)
I created overloaded versions of this method to handle boolean, integer, string, and DateTime data types, to simplify even more the logic required in the population method.
Here is an example of the string method:
1 public static void ReadColumn(ref SqlDataReader reader, string columnName, out string column)
2 {
3 column = string.Empty;
4 if (checkColumn(ref reader, columnName))
5 {
6 column = reader[columnName].ToString();
7 }
8 }
This leaves just one snippet of code remaining, the private helper method "checkColumn".
1 private static bool checkColumn(ref SqlDataReader reader, string columnName)
2 {
3 reader.GetSchemaTable().DefaultView.RowFilter = "ColumnName='" + columnName + "'";
4 return (reader.GetSchemaTable().DefaultView.Count > 0);
5 }
Finally, jumping back to our domain objects populate code, we can replace all of the heavy duty defensive coding code with a simple and clean method call:
1 SQLServer.Utility.ReadColumn(ref reader, "CustomerName", out this._customerName);
3 comments:
Well the first thing I'd say is something like Subsonic does a whole lot more than that for you.
Also, if you're developing for 3.5, you can use LinqToSql as a foundation for an O/RM. It's more powerful than Subsonic in many respects, but SS creates a better foundation for you with less effort.
Hi Charoco,
In this situation, we are not using an ORM. While we have used them in the past, in high performance applications, it is usually more efficient to write your domain objects by hand.
Thanks for your post - I am trying to initialise a business object through it's constructor and was having problems in handling the DBNulls. I'm just experimenting with your helper functions and it looks as though you could make them more flexible by using generics. You wouldn't have to overload all the different types if you could get it working. I'm still having trouble with casting datetimes and a few others but I'll post my code soon once I get it working if you are interested.
Post a Comment