Introduction
Suppose you have a class
Customer
. This Customer
class has collection of 'Orders'. Each Order
contains a collection of OrderLines
.Now, if you retrieve one or more Customers out of the database, it is maybe not such a good idea that you retrieve all the Orders and OrderLines with it. It is probable that the user is not interested in all the orders or all the orderlines of each customer that you have retrieved.
If you do this, it means that a lot of objects will be loaded into memory; objects you might not need.
Therefore, it is sometimes a good idea to load this information only when you need it. In the scenario I've described here, I think it is a good idea that, when you load a Customer out of the database you should also get his Orders.
In most cases, you want to have an overview of the Orders of a Customer. This means that you will need the Orders of the Customer in most of the cases.
However, you're only interested in the
OrderLines
of an Order
, once you decide to view the details of an Order
. Here, you should only retrieve the OrderLines out of the database, when they're needed. This is where the Lazy Loading comes in.The LazyBusinessObjectCollection class
The Lazy Load functionality must be transparant to the user of our
Order
class. This means that, the user must be able to Add, Remove and see the OrderLines of an Order just like he/she can do with a normal collection class.Even if the OrderLines of an Order have not been loaded yet, it must be possible that OrderLines are being added.
So, how can we do that ? The Proxy Design Pattern offers us a way to do that.
Instead of giving the
Order
class a 'real collection' as a member to hold it's orderlines, we will give it a proxy object that acts as a collection.This means that we must create a class that has all functionality of a collection. The easy way to do so, is to create a class that is a wrapper around a Collection object, and acts as a Collection as well.
We can create a class that is a wrapper around the
BusinessObjectCollection
class that we've created earlier. This wrapper class must also implement the ICollection
interface so that it can act as a collection. We also want Undo-support, so this class must implement our IUndoable
interface as well.The skeleton of our
LazyBusinessObjectCollection
will look like this:[Serializable]
public class LazyBusinessObjectCollection<T> : IUndoable, ICollection<T>
where T : BusinessObject
{
}
Earlier, I said that the
LazyBusinessObjectCollection
class would be a wrapper around the BusinessObjectCollection
class. This means that the LazyBusinessObjectCollection
class should have a member of type BusinessObjectCollection
.[Serializable]
public class LazyBusinessObjectCollection<T> : IUndoable, ICollection<T>
where T : BusinessObject
{
private BusinessObjectCollection _collection = null;
private BusinessObjectCollection CollectionObj
{
if( _collection == null )
{
// initialize and load the items.
}
return _collection;
}
}
I've also created a property that accesses the
BusinessObjectCollection
member. This property should be used everytime we access our BusinessObjectCollection
from within the LazyBusinessObjectCollection
class, since this property will take care that the BusinessObjectCollection
is loaded if this is necessary.To be able to populate the collection, we need something that can loads the data for us. We want to decouple the
LazyBusinessObjectCollection
from the code that retrieves the items for us, since, this 'data retrieval code' will be most likely in some kind of Data Access Layer.So, I decided to create an interface
ILazyLoader
This interface looks like this:
public interface ILazyLoader<T> where T : BusinessObject
{
List<T> GetObjects();
}
This interface defines only one method that gives us a list of BusinessObjects. This means that the class that implements this interface is responsible for retrieving and returning the BusinessObjects.
So, we can give an
ILazyLoader
to our LazyBusinessCollection
class. This ILazyLoader
can be provided by the constructor of the LazyBusinessObjectsCollection
class, and the property that we've created earlier, can access the GetObjects
method which will give the Objects that the collection must contain:[Serializable]
public class LazyBusinessObjectCollection<T> : IUndoable, ICollection<T>
where T : BusinessObject
{
private BusinessObjectCollection _collection = null;
private ILazyLoader<T> _loader;
private BusinessObjectCollection CollectionObj
{
if( _collection == null )
{
_collection = new BusinessObjectCollection<T> ();
foreach( T item in _loader.GetObjects () )
{
_collection.Add (item);
}
}
return _collection;
}
public BusinessObjectCollection( ILazyLoader<T> loader )
{
if( loader == null )
{
throw new ArgumentException ("You must provide an ILazyLoader",
"loader");
}
_loader = loader;
}
}
Pretty simple huh ?
Now, an ILazyLoader can look like this:
public OrderLinesLoader : ILazyLoader<OrderLine>
{
private int _orderId;
public OrderLinesLoader( int orderId )
{
_orderId = orderId;
}
public List<OrderLine> GetObjects()
{
// Just for simplicity of the example, normally, you should
// use parametrized queries, etc.... Consider this as pseudo-code
string sql = "SELECT * FROM orderlines " +
"WHERE OrderId = " + _orderId.ToString();
DataReader dr = dbHelper.Execute (sql);
List<OrderLine> lines = new List<OrderLine>();
while( dr.Read() )
{
OrderLine ol = CreateOrderLineFromReader (dr);
lines.Add (ol);
}
return lines;
}
}
So, the repository or factory who gives us an
Order
object, is responsible for initializing the OrderLines
member -which is an LazyBusinessObjectsCollection
of the Order
class.We still have to implement the
ICollection
and IUndoable
interface. I will not put the implementation of the ICollection
interface here to save some space; besides, it is pretty simple code: you can just delegate these calls to the BusinessObjectCollection
property (note: make sure to use the property) of the LazyBusinessObjectCollection
class.The same applies to the implementation of
IUndoable
: just delegate the calls to the BusinessObjectCollection
. However, here we can use the field instead of the property, since we only have to delegate these calls if the collection has been loaded:public void CreateSnapshot()
{
if( _collection != null )
{
_collection.CreateSnapshot ();
}
}
...
5 opmerkingen:
Do you have the complete code available for download that includes all 3 parts?
Hi,
Well, I’ve never had the intention to make the code available for download. This was just a little project for me, and I haven’t used it in a real application yet, so I also do not know how robust it is. Maybe I will make it available in the future…. But I can’t garantuee.
Excellent post. I'm working to implement a similar framework right now (also based on CSLA.NET). One alteration you might make to the "lazy load" mechanism is to use a generic delegate (like LoadCallback<TBusinessObject>) rather than an interface. It might be a little more flexible and you could avoid the code-bloat associated with the load mechanism.
Just a thought!
Just interested in knowing how your framework has faired now that I assume you've had a chance to use it in anger?
Also following on from this, have you thought about posting the code?
Hi, thx for your interest.
To be honest, I haven't looked very much at this framework anymore. I've been looking more at O/R frameworks like NHibernate lately, to see how to integrate it in projects.
The advantage of NHibernate is that it takes care of all the things that I was planning to implement in this framework.
I'll see if I can find something where I can host the code.
Een reactie posten