Build Your Own Data Access Layer: Lazy Loading

16 commentsWritten on August 27th, 2009 by
Categories: Build Your Own DAL

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 :)

  • Ryan Scott

    Davy, I thought the reason you were building a custom DAL was to circumvent NIH attitudes. Doesn’t using DynamicProxy raise the same concerns? Or are they only concerned with owning/writing the DAL as a whole?

  • http://davybrion.com Davy Brion

    I think their biggest fear is simply that of not being able to ‘change’ things in case something doesn’t work. We’ve suggested NHibernate, Linq To SQL, Entity Framework, … you name it. They didn’t want any of it… the only thing they have no problem with is either stored procedures, or straight ADO.NET.

    The fact that this DAL is somewhat reminiscent of NHibernate (in terms of usage, not in terms of features) is either something they won’t even realize, or won’t even care about because the code is right there and it’s easy to modify in case things ‘go wrong’.

    I’m not even gonna get into their realistic abilities when it comes to changing things for the better, but that is unfortunately their attitude. And it’s only when it comes to data access. We can use Windsor, we can use DynamicProxy, we can use the Request/Response service layer… they don’t care about any of that.

    I’ve been having discussions like that for about a year now, and after a while i simply stopped caring. It’s their problem and if they’re willing to pay the extra cost that comes with such a mindset, then so be it. But repetitive and error-prone data-access code is something i can’t stand, whether you write it manually or even if it’s generated… that’s why i finally wrote this :)

  • http://weblogs.asp.net/bleroy Bertrand Le Roy

    I was going to say the same thing as Ryan but he beat me to it. Thanks to your answer I understand why Castle is OK on that specific project, but what would you do if it wasn’t and if the constraint was no external dependencies at all?

  • http://davybrion.com Davy Brion

    that’s a good question… i certainly wouldn’t try to write a proxy generator, that’s for sure :P

    if i wasn’t allowed to use DynamicProxy in this DAL, i’d probably try to enable lazy-loading by wrapping reference properties in something which only provides a .Value property and do the lazy loading when the Value property is accessed (like how IFutureValue in NHibernate works). The only way to access the reference property would be through the Value property so the calling code would be oblivious to the fact, though it would be uglier than just using a property.

    you’d basically get something like invoice.Customer.Value in your code, instead of just invoice.Customer

  • Pingback: Reflective Perspective - Chris Alcock » The Morning Brew #422

  • http://blogger.forgottenskies.com Steve

    Thanks. I’m looking forward to Session and UnitOfWork :)

  • kilfour

    Most custom DAL’s are something that have me waking up screaming in the middle of the night, during the period I’m confronted with them. Still a lot of developers insist on writing one, instead of using a mature solution that’s allready out there. Great job with this one though, and an even better job of putting the effort into perspective.
    Just one of many examples why you better not write your own DAL, and if you do, sit down and think about it first.
    Introducing a new concept Immutable Lazy Loading :

    public Product.Product Product
    {
    get
    {
    if (_Product == null)
    {
    _Product = DAOFactory.ProductFactory.RetrieveProductByProductID(ProductID);
    }
    else
    {
    if (!(_Product.Id.Equals(ProductID)))
    {
    _Product = DAOFactory.ProductFactory.RetrieveProductByProductID(ProductID);
    }
    }
    return _Product;
    }
    set
    {
    _Product = value;
    }
    }

    Millions more where that came from.

  • http://kozmic.pl Krzysztof Koźmic

    Davy,

    from the DP perspective, you could clean things up a little bit (and get some performance improvement) if you used IProxyGenerationHook and/or IInterceptorSelector to control which members to override/intercept.

    Krzysztof

  • Gareth

    Great series of posts Davy!

    Question regarding the LazyLoadingInterceptor – is there a specific reason why you check for the primary key using the property name rather than looking for the primary key attribute on the invocation method?

    Gareth

  • http://davybrion.com Davy Brion

    not really a specific reason for that, other than that was the first thing that came to my mind ;)

  • Gareth

    Often the best reason I find ;)

    I’m building something using a similar model and was concerned in case there were issues accessing the attributes in that scenario.

    BTW how is the code behaving in the wild?

  • http://davybrion.com Davy Brion

    it hasn’t been deployed in production yet, but so far we haven’t had problems with it

    i’m sure some problems will come up sooner or later though :p

  • Nagarajan

    Hi,

    In class EntityHydrater, there is a method called CreateProxy and it uses “session” object in LazyLoadingInterceptor. where it comes from?.

    Regards,
    Nagarajan.

  • http://davybrion.com Davy Brion
  • http://pulse.yahoo.com/_GML3CACBSP55MXYSWMP5TF7WWY upul

    var proxy = new ProxyGenerator().CreateClassProxy(referenceInfo.ReferenceType, new[] { new LazyLoadingInterceptor(tableInfo, session) });

  • http://pulse.yahoo.com/_GML3CACBSP55MXYSWMP5TF7WWY upul

    var proxy = new ProxyGenerator().CreateClassProxy(referenceInfo.ReferenceType, new[] { new LazyLoadingInterceptor(tableInfo, session) });