Note: This post is part of a series. Be sure to read the introduction here.
In the post about entity hydration, i mentioned the following:
If we can’t find the referenced entity instance in the first level cache, what should we do? We obviously can’t load it automatically because that could in turn cause referenced entities’ references to be loaded automatically when they are hydrated. Which in turn could cause their referenced entities… Well, i’m sure you get the point. But those properties obviously can’t be set to a null reference either because the column actually does have a valid foreign key value in the database. Explicitly loading referenced properties leads to seriously ugly (and error-prone) code so that’s not an option i’m willing to consider either. The correct way to deal with this is to use lazy loading. To do that in an automated fashion, we need proxy classes. I’m not going to get into these proxy classes and the whole lazy loading thing just yet, since that will be covered in depth in a future post
It's time to go over the implementation of the lazy loading of this DAL. I honestly expected that this would be the part that would take the most time to get working. It actually turned out to be the easiest and quickest part of the DAL to develop.
As i mentioned, we are going to use proxy classes to achieve our goal of lazy loading.
Consider the following simple entity type:
[Table("Customer")]
public class Customer
{
[PrimaryKey("ID")]
public int Id { get; set; }
[Column("Name")]
public string Name { get; set; }
[Reference("AddressID")]
public Address Address { get; set; }
}
If we want to avoid having to put any lazy loading logic within this class, we could inherit from it and add the lazy loading logic in the derived class. First, we would have to make the properties of our entity virtual:
[Table("Customer")]
public class Customer
{
[PrimaryKey("ID")]
public virtual int Id { get; set; }
[Column("Name")]
public virtual string Name { get; set; }
[Reference("AddressID")]
public virtual Address Address { get; set; }
}
Now we could create a proxy class like this:
public class CustomerProxy : Customer
{
private bool needsToBeInitialized = true;
public Session Session { get; set; }
public override string Name
{
get
{
InitializeIfNecessary();
return base.Name;
}
set
{
InitializeIfNecessary();
base.Name = value;
}
}
public override Address Address
{
get
{
InitializeIfNecessary();
return base.Address;
}
set
{
InitializeIfNecessary();
base.Address = value;
}
}
private void InitializeIfNecessary()
{
if (needsToBeInitialized)
{
needsToBeInitialized = false;
Session.InitializeProxy(this, typeof(Customer));
}
}
}
This could definitely work. Whenever we need a proxy to avoid loading an entity, we can simply instantiate a proxy class like the one above, pass it a Session object (again, the Session implementation will be covered in a future post, though i will show the InitializeProxy later on in this post) and once we retrieve any of the overridden properties, the proxy instance will use the Session to initialize itself. The Session would then use a DatabaseAction and the hydration process to make sure the proxy's properties are filled in with the values of its corresponding properties. I didn't override the Id property because accessing the primary key of a proxied object should never result in a database call, so there is no reason to override it.
But we really can't expect people to manually create proxy types like this. For one, it's repetitive and thus, it is error prone. Both are issues that we've aimed to avoid with this DAL from the start. So how can we make this work automagically? The answer is simple: we can use Castle's excellent DynamicProxy library to generate the proxy classes at runtime for us.
DynamicProxy uses a concept known as an Interceptor. The Interceptor basically intercepts method calls on proxied objects and allows you to add custom logic before and after the original method calls. For our lazy loading purposes, we simply need the following LazyLoadingInterceptor class:
public class LazyLoadingInterceptor : IInterceptor
{
private TableInfo tableInfo;
private readonly Session session;
private bool needsToBeInitialized = true;
public LazyLoadingInterceptor(TableInfo tableInfo, Session session)
{
this.tableInfo = tableInfo;
this.session = session;
}
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name.Equals("get_" + tableInfo.PrimaryKey.PropertyInfo.Name) ||
invocation.Method.Name.Equals("set_" + tableInfo.PrimaryKey.PropertyInfo.Name))
{
invocation.Proceed();
return;
}
if (needsToBeInitialized)
{
needsToBeInitialized = false;
session.InitializeProxy(invocation.Proxy, invocation.TargetType);
}
invocation.Proceed();
}
Ok, so what does this class do? Like i said, an interceptor will intercept all method calls on virtual methods of a proxied object. So basically, if we create a proxy through DynamicProxy, and tell DynamicProxy to add a new instance of this LazyLoadingInterceptor so we can add the behavior of this interceptor to our proxy object, and we then access the properties of our proxy object, it will actually show the same behavior as the manually created CustomerProxy class listed earlier. And we get all of this for free, without having to modify our original Customer class, except for marking the properties as virtual obviously.
Now, when the LazyLoadingInterceptor calls the Session's InitializeProxy method, the session will delegate this call to our new InitializeProxyAction class:
public class InitializeProxyAction : DatabaseAction
{
public InitializeProxyAction(SqlConnection connection, SqlTransaction transaction, MetaDataStore metaDataStore,
EntityHydrater hydrater, SessionLevelCache sessionLevelCache)
: base(connection, transaction, metaDataStore, hydrater, sessionLevelCache)
{
}
public void InitializeProxy(object proxy, Type targetType)
{
using (var command = CreateCommand())
{
var tableInfo = MetaDataStore.GetTableInfoFor(targetType);
var query = tableInfo.GetSelectStatementForAllFields();
tableInfo.AddWhereByIdClause(query);
object id = tableInfo.PrimaryKey.PropertyInfo.GetValue(proxy, null);
command.CommandText = query.ToString();
command.CreateAndAddInputParameter(tableInfo.PrimaryKey.DbType, tableInfo.GetPrimaryKeyParameterName(), id);
Hydrater.UpdateEntity(targetType, proxy, command);
}
}
}
As you can see, this is extremely similar to the GetByIdAction. In fact, i should probably put the common logic in another base DatabaseAction class that sits between DatabaseAction and GetByIdAction and InitializeProxyAction. Anyways, when the entity has been retrieved, we ask the EntityHydrater to update this entity instance through its newly added UpdateEntity method:
public void UpdateEntity(Type type, object entity, SqlCommand command)
{
using (var reader = command.ExecuteReader())
{
reader.Read();
var tableInfo = metaDataStore.GetTableInfoFor(type);
Hydrate(tableInfo, entity, GetValuesFromCurrentRow(reader));
}
}
The Hydrate method is already shown in the post that covers the process of entity hydration, so there's no need to go over that again.
And now there's only one more missing piece in our lazy loading puzzle, which is the actual creation of the proxy in the EntityHydrater's CreateProxy method:
private object CreateProxy(TableInfo tableInfo, ReferenceInfo referenceInfo, object foreignKeyValue)
{
var proxy = proxyGenerator.CreateClassProxy(referenceInfo.ReferenceType,
new[] { new LazyLoadingInterceptor(tableInfo, session) });
var referencePrimaryKey = metaDataStore.GetTableInfoFor(referenceInfo.ReferenceType).PrimaryKey;
referencePrimaryKey.PropertyInfo.SetValue(proxy, foreignKeyValue, null);
return proxy;
}
When using an approach like this, it's best to make everything in your entity classes virtual... NHibernate has a similar restriction and i've tried to explain the reasons behind this in this post.
I'm not sure if i succeeded in explaining this topic in a simple and clear manner, but this technique really is pretty easy. If you have any questions, i'd be glad to anser them in the comments ![]()
Pingback: Reflective Perspective - Chris Alcock » The Morning Brew #422