Data Access Extension Method

This is a follow-up to my previous post on Exploring Extension Methods. As you can see, it was dated many years back and I'm glad that I finally had the time to look into it now. I took one of my layered application samples and converted it to test the concept.

Just a brief recap, I use Entities (classes with just properties) in my architecture to represent data and they don't have any methods. To validate or make use of the data for processing logic, I use a Business Component and to persist their data I use Data Access Components (DAC). So, if I have a Leave object, it will look something like this in the business component to save it to the database:

// Instantiate data access component.
var leaveDAC = new LeaveDAC();

// Create leave.
leaveDAC.Create(leave);

But it would be nice if I can just do:

// Create leave. 
leave.Create();

The code will look much cleaner and I do not need to worry about instantiating which DACs to persist the Entities. I will only need to focus on the Entities instead. This is where extension methods can come into play. By converting the DAC into a "Data Access Extension" class, the above syntactic experience can be achieved.

Let's look at the original DAC.

public partial class LeaveDAC : DataAccessComponent
{
    public void Create(Leaveleave)
    public void UpdateStatus(Leave leave)
    public void SelectById(long leaveID)
    public void Select(int maximumRows, int startRowIndex, 
                       string sortExpression, string employee, 
                       LeaveCategories? category, LeaveStatuses? status)
    public int Count(stringemployee, LeaveCategories? category, 
                     LeaveStatuses? status)
    public bool IsOverlap()
}

After conversion to Extension Methods, the DAC will look like the following:

public static partial class LeaveDAC
{
    public static void Create(this Leave leave)
    public static void UpdateStatus(this Leave leave)
    public static void SelectById(this Leave leave, longleaveID)
    public static void Select(this List<Leave> leaves, int maximumRows,
                              int startRowIndex, string sortExpression,
                              string employee, LeaveCategoriescategory, 
                              LeaveStatuses? status)
    public static int Count(this List<Leave> leaves, string employee, 
                            LeaveCategories? category, LeaveStatuses? status)
    public static bool IsOverlap(this Leave leave)
}

Noticed that the DAC don't inherit the DataAccessComponent anymore? It is actually a limitation and I will discuss about that later.

Here's how a method in my business component looked like originally:

private void UpdateStatus(Leave leave)
{
    LeaveStatusLog log = CreateLog(leave);

    // Data access component declarations.
    var leaveDAC = new LeaveDAC();
    var leaveStatusLogDAC = new LeaveStatusLogDAC();

    using (TransactionScope ts =
        new TransactionScope(TransactionScopeOption.Required))
    {
        // Step 1 - Calling UpdateById on LeaveDAC.
        leaveDAC.UpdateStatus(leave);

        // Step 2 - Calling Create on LeaveStatusLogDAC.
        leaveStatusLogDAC.Create(log);

        ts.Complete();
    }
}

Here's the UpdateStatus method after conversion:

private void UpdateStatus(Leave leave)
{
    LeaveStatusLog log = CreateLog(leave);

    using (TransactionScope ts =
        new TransactionScope(TransactionScopeOption.Required))
    {
        // Step 1 - Calling Update status.
        leave.UpdateStatus();

        // Step 2 - Calling Create on log.
        log.Create();

        ts.Complete();
    }
}

Pretty clean right? Let's look at another method that does data retrieval. Here's the original version:

public List<Leave> ListLeavesByEmployee(int maximumRows, int startRowIndex,
    string sortExpression, string employee, LeaveCategories? category,
    LeaveStatuses? status, out int totalRowCount)
{
    List<Leave> result = default(List<Leave>);

    if (string.IsNullOrWhiteSpace(sortExpression))
        sortExpression = "DateSubmitted DESC";

    // Data access component declarations.
    var leaveDAC = new LeaveDAC();

    // Step 1 - Calling Select on LeaveDAC.
    result = leaveDAC.Select(maximumRows, startRowIndex, sortExpression,
        employee, category, status);

    // Step 2 - Get count.
    totalRowCount = leaveDAC.Count(employee, category, status);

    return result;
}

And here's the ListLeavesByEmployee method using data extensions:

public List<Leave> ListLeavesByEmployee(int maximumRows, int startRowIndex,
    string sortExpression, string employee, LeaveCategories? category,
    LeaveStatuses? status, out int totalRowCount)
{
    var result = new List<Leave>();

    if (string.IsNullOrWhiteSpace(sortExpression))
        sortExpression = "DateSubmitted DESC";

    // Step 1 - Calling Select.
    result.Select(maximumRows, startRowIndex, sortExpression,
        employee, category, status);

    // Step 2 - Get count.
    totalRowCount = result.Count(employee, category, status);

    return result;
}

Up till now, the results have been quite satisfying but there are some limitations that needs to be dealt with. A static class can only inherit from object and not other classes, which leads to the previous highlighted problem that prevents any reusable methods to be encapsulated in the abstract base DataAccessComponent class. It has to be converted into a normal class with its properties and methods also converted to static.

Original DataAccessComponent base class:

public abstract class DataAccessComponent
{
    protected const string CONNECTION_NAME = "default";

    protected T GetDataValue(IDataReader dr, string columnName)
    {
        int i = dr.GetOrdinal(columnName);

        if (!dr.IsDBNull(i))
            return (T)dr.GetValue(i);
        else
            return default(T);
    }
}

Converted to function like a utility class:

public sealed class DataAccessComponent
{
    public const string CONNECTION_NAME = "default";

    public static T GetDataValue(IDataReader dr, string columnName)
    {
        int i = dr.GetOrdinal(columnName);

        if (!dr.IsDBNull(i))
            return (T)dr.GetValue(i);
        else
            return default(T);
    }
}

This makes DAC methods like the following

private Leave LoadLeave(IDataReader dr)
{
    // Create a new Leave
    Leave leave = new Leave();

    // Read values.
    leave.LeaveID = base.GetDataValue<long>(dr, "LeaveID");
    leave.CorrelationID = base.GetDataValue<Guid>(dr, "CorrelationID");
    leave.Category = base.GetDataValue<LeaveCategories>(dr, "Category");
    
    // other fields...

    return leave;
}

to become a little expanded:

private static Leave LoadLeave(IDataReader dr, Leave leave)
{
    // Read values.
    leave.LeaveID = DataAccessComponent.GetDataValue<long>(dr, "LeaveID");
    leave.CorrelationID = DataAccessComponent.GetDataValue<Guid>(dr, "CorrelationID");
    leave.Category = DataAccessComponent.GetDataValue<LeaveCategories>(dr, "Category");

    // other fields...
    return leave;
}

Now the limitation of not being able to do inheritance has somewhat make me feel that this feat might not be a good idea. Furthermore, I also discovered that reflecting the extension methods may somewhat be a challenge if I want to apply this in LASG.

Most people will treat extension methods as 'syntactic sugar'. In this experiment, it does show that other than its original purpose of just purely extending class functionalities, extension methods can also make code look a bit more readable and easier to understand.

In terms of performance, there isn't seem to be any impact (or improvements). You can check-out Sylvester Lee's post to get some in-depth details on the performance benchmark he did for me in this research.

At this point in time, I have slight reluctance in using extension methods for data access. What do you think? Will you consider this method?

No comments:

Post a Comment

Popular Post