Writing evolving C# code (post 02) – Using injection and inheritance

Reading Time: 6 minutes

Starting from the existing code of the previous article, we experimented the caching feature by adding an outsider behavior into the existing data access layer. This strategy is very fast and it helps us to determine if the problem is addressed correctly and the technologies used are appropriate. Now, we should refactor it to fit elegantly the pre-existing design, without coupling too much the “caching” scope with the “data access” one.

Let’s start by decoupling a little bit the previous code. Instead of creating a concrete class DAL, let’s define an interface like this:

public interface IDataService
{
    Order[] GetOrdersFor(int customer);
} 

Consequently, the previous DAL class, becomes an implementation of the IDataService that goes to the Database to materialize data:

public class DAL:IDataService
{
    public Order[] GetOrdersFor(int customer)
    {
        var db = new MyEntities();
        return db.Orders.Where(p => p.CustomerID == customer).ToArray();
    }
}

And we are at the starting point, where the consumer actor can now use the DAL implementation through the IDataService interface:

IDataService dal = new DAL();
var orders = dal.GetOrdersFor(10);

Now we are in front of two options:

  • Writing another implementation of IDataService, to perform both the Data Access logic and the Caching one. This option, however, leads to a duplication of code (the Data Access part).
  • Implementing something related only to caching and attaching it to the Data Access implementation in some way, keeping the two scope un-related.

It can be better first to implement something which relates to Caching and, second, the proper glue to attach it to the existing DAL implementation.

Here we have an example of caching implementation through the IDataService interface:

public class DataCaching : IDataService
{
    private IDataService service;
    private MemoryCache cache = MemoryCache.Default;
    public DataCaching(IDataService service)
    {
        this.service = service;
    }
    public Order[] GetOrdersFor(int customer)
    {
        var key = $"GetOrdersFor_{customer}";
        var cachedItem = cache.Get(key);
        if (cachedItem != null) return (Order[])cachedItem;
        else
        {
            var result = service.GetOrdersFor(customer);
            cache.Set($"GetOrdersFor_{customer}", result,
                new CacheItemPolicy()
                {
                    SlidingExpiration = TimeSpan.FromSeconds(10)
                });
            return result;
        }
    }
}

The DataCaching class is an implementation of the IDataService interface, providing a Caching Layer not on the specific DAL implementation but on a generic IDataService underlying implementation, as a decorator.

This line:

var result = service.GetOrdersFor(customer);

is the chained call to an implementation which will return the Order[] object.

Let’s think about this for a minute. We are not telling the DataCaching class which would be the actual implementation of the service reference.

The typical usage in the scenario we discussed, can be the following:

var dal = new DataCaching(new DAL());
var orders = dal.GetOrdersFor(10);

But we can, also, chain the same implementation two times:

var dal2 = new DataCaching(new DataCaching(new DAL()));

The result works too but, it’s useless. In case of a cache miss, it results in a two consecutive lookup calls to the Caching layer plus two consecutive write calls to it. It could make sense with different implementations, for instance where a first-level in-process cache can call an underlying remote, distributed, second-level cache service:

var dal3 = new DataCaching(new RedisCaching(new DAL()));

Finally, we can even prevent the DataCaching implementation to receive in the constructor another decorator, but only a final implementation, using C# generics contraints:

public class DataCaching<T> : IDataService where T:IDataService,new()
{
        private T service;
        private MemoryCache cache = MemoryCache.Default;
        public DataCaching(T service)
        {
            this.service = service;
        }
	[…]
}

This new definition requires, during the object construction, to determine an actual implementation of IDataService which has a parameter less constructor. We also need to change the instantiation code into this:

var dal = new DataCaching<DAL>(new DAL());

While the other one won’t be allowed anymore:

var dal2 = new DataCaching<DataCaching<DAL>>
    (new DataCaching<DAL>(new DAL()));  //Invalid

While this approach works, it can be defined as a workaround. We can later implement something more elegant in order to avoid unwanted behaviors while using these implementations.

Now we want to justify a little bit of refactoring, so let’s take a new member into the IDataService interface:

public interface IDataService
{
    Order[] GetOrdersFor(int customer);
    Order GetOrder(int id);
}

We added the Order GetOrder(int id) method definition that leads to a compilation error, until we’ve implemented it into all the non-abstract implementations currently using the interface.

This is a powerful feature of statically-typed languages, since we are embraced by the compiler who don’t let us go too far away without the proper code updates. In case of several implementations, we could be tempted to write a quick-and-dirty empty implementation in all the concrete classes, to avoid compilation errors and proceed working/debugging. However, it is better to isolate implementations or implement the methods by throwing the NotImplementedException:

public Order GetOrder(int id)
{
    throw new NotImplementedException();
}

This, as a last resort, will help you to identify (during runtime, however) the origin of the exception to consequently fix it with real code.

Refining and reducing duplicated code

Since an additional IDataService method can generate duplicate code in the method bodies, we can now introduce further optimizations, as follows:

public class DAL : IDataService
{
    private Func<MyEntities> contextFactory = null;
    public DAL(Func<MyEntities> contextFactory)
    {
        this.contextFactory = contextFactory;
    }
    public Order GetOrder(int id)
    {
        return contextFactory().Orders
            .FirstOrDefault(p => p.ID == id);
    }
    public Order[] GetOrdersFor(int customer)
    {
        return contextFactory().Orders
            .Where(p => p.CustomerID == customer).ToArray();
    }
}

Why a generator function instead of the actual instance?
The reader may notice we required a generator function in the constructor of the DAL class, instead of requiring directly the MyEntities instance. We did this because we assumed MyEntities is a DbContext-derived class of Entity Framework. In Entity Framework, the DbContext object may have a long lifetime and can be shared across different queries. However, it is not recommended since the underlying implementation of EF already performs optimization with connection pools and clients. In addition, if we pass an instance we can face several potential issues due to code that disposes it after its “local” use. In the case, for example, the GetOrder method disposes the MyEntities object after retrieving the data, no other member of the instance can use it anymore. Worse, it would be a runtime exception.

We can also take advantage of the “using” keyword to get a Dispose() on the Entity Framework object:

using (var ctx = contextFactory())
{
    return ctx.Orders
        .FirstOrDefault(p => p.ID == id);
}

And we finally inject the factory function in the instantiating code:

var dal = new DataCaching(new DAL(()=>new MyEntities()));

DataCaching is not compiling, since we added a member in the IDataService interface without the proper implementation in the concrete class. By injecting the lambda which executes the actual factory code, we can also define a generalization step that wraps all the caching-related code stripping it away from the single method implementation, as follows:

private K GetOrAdd<K>(string key,Func<K> eval)
{
    var cachedItem = cache.Get(key);
    if (cachedItem != null) return (K)cachedItem;
    else
    {
        var result = eval();
        cache.Set(key, result,
            new CacheItemPolicy()
            {
                SlidingExpiration = TimeSpan.FromSeconds(10)
            });
        return result;
    }
}

This is a trivial implementation since, for instance, the CacheItemPolicy is the same for every method call. But the advantages in the methods implementation are great:

public Order[] GetOrdersFor(int customer)
{
    var key = $"GetOrdersFor_{customer}";
    return GetOrAdd<Order[]>(key, 
        () => service.GetOrdersFor(customer));                
}
 
public Order GetOrder(int id)
{
    var key = $"GetOrder_{id}";
    return GetOrAdd<Order>(key, 
        () => service.GetOrder(id));
}

We increased the features of the application (by adding the new method definitions plus its implementations) while, at the same time, reducing the overall code, using a bit of generalization.

Introducing a bit of reflection

We started from a prototype which tested the code flow and the technology. In the previous section, we see instead how to apply some experiments to working code, to reduce duplicated code mainly. We continue of such trend even further, with another intervention which can reduce again the code written. In this case, however, the approach introduces reflection, which is discussed later in other chapters.

Let’s say our IDataService interface will have tens of methods and, for each one, we need to implement:

  • The EF high-valued code (in the DAL class): every Entity Framework related method to materialize data will be unique and potentially complex. It is normal to implement it manually.
  • The low-valued code of the DataCaching class: where every method is a trivial repetition (or, worse, a copy/paste) of the same steps.

To be precise, it is a replicated pattern of:

  • Define a key:
    • we can assume the key will always be a string with the pattern “[MethodName]_{p1}_{p2}….{pn}”.
  • Call the GetOrAdd method:
    • we assume the cached type is the same as the return type of the method.
  • Specify an evaluating function:
    • we know the underlying method to call has the same signature as the running method.

With the assumptions above, we can write code which reflects the IDataService interface to get information about methods and properties useful to prepare a generated implementation.

var methods = typeof(IDataService).GetMethods()
	.Select(p => new
	{
		Name=p.Name,
		ReturnType=p.ReturnType.FullName==
			"System.Void"?"void":p.ReturnType.FullName,
		Parameters=p.GetParameters()
			.Select(q => new
			{
				q.ParameterType,
				q.Name
			})						
			.ToArray()
	})
	.ToArray();

Here a bit of explanation:

  • The typeof(IDataService) returns a System.Type object, containing useful information about it. System.Type is the starting point to reflect almost everything.
  • The GetMethods() method returns all the public methods of the IDataService type (and, generally, of any System.Type object)
  • Since the GetMethods() method returns an array of MethodInfo, we can iterate on that through LINQ (lambda syntax) to have a projection
  • ReturnType is a System.Type too: FullName is good except in some cases (System.Void is one). We used the conditional operator to fix that case.
  • For every method, we ask GetParameters and iterate to map them in Name and ParameterType.
  • The two ToArray() calls are needed to materialize the results immediately (the default behavior of the iterator is to lazily materialize result upon request).

The listing above will produce an array of anonymous objects with all the relevant information we need to produce the code dynamically, through a final generation step, in the next article.


Companion code here:
https://github.com/childotg/iSolutionsLabs