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!

Mastodon