Using extension methods to keep things DRY - Part 1
I'm a big fan of DRY (Don't Repeat Yourself). If you find yourself writing the same code again and again, you should see if you can extract that code out to a method to prevent you from repeating the same code often.
A pet peeve of mine is your typical DAL. Let's say we have defined a Person object in our database, which has an identifier, a name, a date of birth and an email address. We'll define the C# object like so:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public string EmailAddress { get; set; }
}
For simplicity's sake, let's say we have two methods, one to get a single person and one to get all persons. More often than not these methods will look like this:
public Person GetPerson(int id)
{
using (var connection = new SqlConnection("SomeConnectionString"))
{
using (var command = new SqlCommand("dbo.PersonGetSingle", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@id", SqlDbType.Int).Value = id;
connection.Open();
using (var reader = command.ExecuteReader())
{
if (reader.Read())
{
var person = new Person();
person.Id = id;
person.Name = reader.GetString(reader.GetOrdinal("name"));
person.DateOfBirth = reader.GetDateTime(reader.GetOrdinal("dateOfBirth"));
person.EmailAddress = reader.GetString(reader.GetOrdinal("emailAddress"));
return person;
}
// No results, return null
return null;
}
}
}
}
public List<Person> GetPersonsAll()
{
var persons = new List<Person>();
using (var connection = new SqlConnection("SomeConnectionString"))
{
using (var command = new SqlCommand("dbo.PersonGetAll", connection))
{
command.CommandType = CommandType.StoredProcedure;
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var person = new Person();
person.Id = reader.GetInt32(reader.GetOrdinal("id"));
person.Name = reader.GetString(reader.GetOrdinal("name"));
person.DateOfBirth = reader.GetDateTime(reader.GetOrdinal("dateOfBirth"));
person.EmailAddress = reader.GetString(reader.GetOrdinal("emailAddress"));
persons.Add(person);
}
}
}
}
return persons;
}
You should notice immediately that there is a lot of repeated code in there. Let's start with something simple. For example, wouldn't it be nice if the DbDataReader contained a method called GetString which accepts a string instead of an integer and just wrapped the call to the GetOrdinal method for us. That's an excellent candidate for an extension method, so let's write a few to help us make things more simple.
public static class DataReaderExtensions
{
public static DateTime GetDateTime(this DbDataReader reader, string name)
{
return reader.GetDateTime(reader.GetOrdinal(name));
}
public static int GetInt32(this DbDataReader reader, string name)
{
return reader.GetInt32(reader.GetOrdinal(name));
}
public static string GetString(this DbDataReader reader, string name)
{
return reader.GetString(reader.GetOrdinal(name));
}
}
Now we are able to change our DAL code to look like this:
public Person GetPerson(int id)
{
using (var connection = new SqlConnection("SomeConnectionString"))
{
using (var command = new SqlCommand("dbo.PersonGetSingle", connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@id", SqlDbType.Int).Value = id;
connection.Open();
using (var reader = command.ExecuteReader())
{
if (reader.Read())
{
var person = new Person();
person.Id = id;
person.Name = reader.GetString("name");
person.DateOfBirth = reader.GetDateTime("dateOfBirth");
person.EmailAddress = reader.GetString("emailAddress");
return person;
}
// No results, return null
return null;
}
}
}
}
public List<Person> GetPersonsAll()
{
var persons = new List<Person>();
using (var connection = new SqlConnection("SomeConnectionString"))
{
using (var command = new SqlCommand("dbo.PersonGetAll", connection))
{
command.CommandType = CommandType.StoredProcedure;
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var person = new Person();
person.Id = reader.GetInt32("id");
person.Name = reader.GetString("name");
person.DateOfBirth = reader.GetDateTime("dateOfBirth");
person.EmailAddress = reader.GetString("emailAddress");
persons.Add(person);
}
}
}
}
return persons;
}
Not the biggest change in the world but we're already making progress with very little effort. Keep tuned for the next part :)
Until next time, happy coding!