Data Access Patterns: Lazy load Pattern
Mohamed Ebrahim
System Architect | Software Designer | i help software users get better software by focusing on code quality and Domain-Driven Design with .NET. | Consultant | Lecturer
in the previous articles, we covered the different types of data access patterns and have to apply that in C# applications, We've already been covering the repository pattern as well as a unit of work
now we will discuss the Lazy Load Pattern
Source GitHub
Lazy Load Pattern
Now we're going to improve the application and apply some Lazy loading. We want to make sure that our applications are as fast as they possibly can be. Sometimes this means that we want to avoid load data that are very rarely used, so we don't want to eagerly load data in the application. But rather, we want to apply these Lazy load principles to make sure that we can load things from third parties or a database only when someone requests that particular property.
Lazy load patterns come in four different flavors
Lazy Initialization
The whole idea with lacy initialization is that we only proceed to initialize our backing field to an exposed property the first time someone tries to access that particular property. as an example of that, let's first look at how we can introduce a profile service to load a profile picture for one of our customers based on the customer name to use lazy initialization. We simply check if our profile picture has been initialized. If it's not initialized only once, we're gonna go off, for instance, in this case, to talk to our profile picture service and get that for that particular person. This might go ahead and load that from a database. It might go ahead and load that off the web from a third party, or it might load that from disk so the lazy initialization only loads the data when the value that we're trying to access is not all that we try to load the data, The problem with this approach is that the entity needs to know about how to load that particular data. Imagine if this is a database call. That means that our customer entity needs to know about how to communicate with the database
public class customer
{
?? ?private byte[] profilePicture
?? ?public? byte[] ProfilePicture
?? ?{
?? ??? ?get
?? ??? ?{
?? ??? ??? ?if (profilePicture == null)
?? ??? ??? ??? ?{
?? ??? ??? ??? ??? ?profilePicture = ProfilePictureService.GetFor(Name);
?? ??? ??? ??? ?}
?? ??? ??? ??? ?return profilePicture;
?? ??? ?}
?? ??? ?set
?? ??? ?{
?? ??? ??? ?profilePicture = value;
?? ??? ?}
?? ?};
}
The biggest problem with lazy initialization and coupling inside our customer entity is that our entity now needs to know about things that it really shouldn't have to care about. Our customer entity now needs to know about our profile picture services, and I don't particularly like that coupling so up next, we're going to try and fix that
Value holders
we want to improve the lazy initialization of our profile picture. Right now, our customer is tightly coupled with the way that we load the profile picture for that particular customer. The way that we can fix this and make this better is by introducing a value holder. That's an example of how we can use a value holder. Imagine, in this case here, we were now inside our customer repository. We've proceeded to load a customer from our data context, and now we want to specify how to load a profile picture for that particular customer. So when a consumer of our customer entity asks for the profile picture, that property will go ahead and communicate with his profile picture value holder. So all that we're doing here is that we were now setting the profile picture value holder property to a new instance. Off a value holder. The value holder is injected with a value loader. This loader knows how to retrieve the profile picture for that particular customer. The benefit of this is that the customer entity no longer has to know about how to retrieve the data
so to make that happen we will add class
public interface IValueHolder<T>
??? {
??????? T GetValue(object parameter);
??? }
??? public class ValueHolder<T> : IValueHolder<T>
??? {
??????? private readonly Func<object, T> getValue;
??????? private T value;
??????? public ValueHolder(Func<object, T> getValue)
??????? {
??????????? this.getValue = getValue;
??????? }
??????? public T GetValue(object parameter)
??????? {
??????????? if(value == null)
??????????? {
??????????????? value = getValue(parameter);
??????????? }
??????????? return value;
??????? }
we will modify the customer entity to contain value holder property
public class Customer
??? {
??????? public Guid CustomerId { get; set; }
??????? public virtual string Name { get; set; }
??????? public IValueHolder<byte[]> ProfilePictureValueHodeler { get; set; }
??????? public byte[] ProfilePicture
??????? {
??????????? get
??????????? {
??????????????? return ProfilePictureValueHodeler.GetValue(Name);
??????????? }
??????? }
??????? public Customer()
??????? {
??????????? CustomerId = Guid.NewGuid();
??????? }
??? }
in the customer repository, we will define the customer entity how to get the profile picture
public override IEnumerable<Customer> All(
??????? {
??????????? return base.All().Select(c=> {
?????????????c.ProfilePictureValueHodeler = new ValueHolder<byte[]>((parameter) =>
??????????????? {
??????????????????? return ProfilePictureService.GetFor(parameter.ToString());
??????????????? });
??????????????? return c;
??????????? });
??????? })
now the customer entity decoupled from the business of how to load the data, but the problem here, with each instance of the customer entity there will be an instance of value holder and that does not thread-safe, so to solve this problem we can use Lazy<T> to achieve the singleton lets make some modifications to our code to achieve that you can read my article Design Pattern: Singleton - Class design vs Lazy<T> vs Container
Virtual Proxies
Another approach is to use something called a virtual proxy. This is simply introducing a class that inherits from our base entity. This means that we now need to mark our properties as virtual that we want to be able to override. Because this principle requires us to override the default behavior of how to access that property. In this case here, the repository will map our entity before it's returned to the consumer to this customer proxy. This will allow the proxy to intercept the calls to the property and load the data necessary. This is a commonly applied pattern in things like entity framework. It will apply these proxies to make sure that you can retrieve more data as you try to load that from your different entities. Imagine that you have your order class and the order has a reference to a customer. Then entity framework can automatically be set up so that the customer on that order is a virtual proxy. And only when you retrieve the customer or requests the customer, it will proceed to query the database
So in reality, all that we want is a way for us to retrieve the profile picture, and we can leverage something like the virtual proxy to then return a proxy class from our repository to set this for us. The only change that we're going to make to our customer class now is that we're going to allow proxy classes to override the default behavior of our different properties inside our entity. To do that, we need to mark all of the properties that we want to be able to change the behavior off before they return to the consumer
public class Customer
??? {
??????? public Guid CustomerId { get; set; }
??????? // Virtual because a proxy can now override its behaviour
??????? public virtual string Name { get; set; }
??????? public virtual string ShippingAddress { get; set; }
??????? public virtual string City { get; set; }
??????? public virtual string PostalCode { get; set; }
??????? public virtual string Country { get; set; }
??????? public virtual byte[] ProfilePicture { get; set; }
??????? public Customer()
??????? {
??????????? CustomerId = Guid.NewGuid();
??????? }
??? }
now we will add our customer proxy to apply lazy initializer
public class CustomerProxy : Customer
??? {
??????? public override byte[] ProfilePicture
??????? {
??????????? get
??????????? {
??????????????? if (base.ProfilePicture == null)
??????????????? {
??????????????????? base.ProfilePicture = ProfilePictureService.GetFor(Name);
??????????????? }
??????????????? return base.ProfilePicture;
??????????? }
??????? }
??? }
now we will update our customer repository to change every customer to CustomerProxy
public class CustomerRepository : GenericRepository<Customer>
??? {
??????? public CustomerRepository(ShoppingContext context) : base(context)
??????? {
??????? }
??????? public override IEnumerable<Customer> All()
??????? {
??????????? return base.All().Select(MapToProxy);
??????? }
??????? private CustomerProxy MapToProxy(Customer customer)
??????? {
??????????? return new CustomerProxy
??????????? {
??????????????? CustomerId = customer.CustomerId,
??????????????? Name = customer.Name,
??????????????? ShippingAddress = customer.ShippingAddress,
??????????????? City = customer.City,
??????????????? PostalCode = customer.PostalCode,
??????????????? Country = customer.Country
??????????? };
??????? }
??? }
This means that we can now pass in a customer entity and get the proxy clause returned. Remember that our customer proxy inherits from a customer. So wherever we return a customer, we could just as well return this as a customer proxy, select for getting all up here at the top, and simply map this to a customer proxy. So what we're doing now is that we're simply saying that for all our customers that we retrieve out of our DataContext, we want to select that and map the customer to a customer proxy because customer proxy inherits from the customer.
I mentioned earlier that this is heavily used within things like entity framework. We can head into our data context and we can apply to our Shopping Context using Lazy loading proxies or we're configuring our data context.
public class ShoppingContext : DbContext
??? {
??????? public DbSet<Customer> Customers { get; set; }
??????? public DbSet<Order> Orders { get; set; }
??????? public DbSet<Product> Products { get; set; }
??????? protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
??????? {
??????????? optionsBuilder
??????????????? .UseLazyLoadingProxies()
??????????????? .UseSqlite("Data Source=orders.db");
??????? }
??????? protected override void OnModelCreating(ModelBuilder modelBuilder)
??????? {
??????????? modelBuilder.Entity<Customer>()
??????????????? .Ignore(c => c.ProfilePicture);
??????????? base.OnModelCreating(modelBuilder);
??????? }
??? }
So this here just automatically introduced the behavior for things in the database and entities. Automatically, we didn't have to eagerly load data, but this Lazy load pattern allows us to now get proxies back from our repository,
Ghost Objects
we have something called a ghost object. Now the ghost object is our entire entity in a partial state. And only when you request one of the other properties than the ID, for instance, it will proceed to load all the data from the underlying data structure. This pattern is a little bit more chatty. It's not used as often as the other lazy load principles you can choose with ghost object if you want to load the entire entity once who acts is one of the properties or if you simply want to load that particular property that you're trying to access. So just as with our virtual proxy, the ghost object will inherit from our customer entity, it will do so to be able to overwrite all of the virtual properties on our customer to make sure that we only load those when we're accessing those particular properties So let's proceed to introduce a ghost object so the ghost object will be our customer in a partial state, and in our situation, the partial state is only going to include our customer ID. But it's important, though, that you try to create the database to get the customer ID out of the database, So you make sure that the actual customer exists. So the first thing that will do is that we were going to introduce this ghost object that represents a customer in a partial state. The ghost object is similar, but not necessarily equal to a customer proxy. The ghost object will know if it's a ghost if it's loaded or if it's currently loading the data for that particular entity. But as this will be a customer in a partial state, we can just simply inherit from our customer to make sure that we have access to the different properties of our customer for us to not have to duplicate and code in our application the first thing that we need to keep track of is the state of our ghost object. Let's just at an enum to our application that keeps track of the load status off our ghost object. One of these objects can be a ghost. It can be loading and it can be fully loaded. So now, of course, the ghost customer needs to keep track of its internal status. It's also good practice to introduce a publicly exposed property that allows you to check if this is a ghost object or if it's been successfully loaded. Now, when we act as one of the properties inside our ghost object, we need a way for us to load the rest of the data for this partially loaded entity. So we're gonna leverage a pattern that we've seen previously where we're going to inject the loader into the constructor of our ghost customer. We know that this is gonna be loaded out of the database, so I know that it's going to return the customer entity or potentially a subclass of that. We're going to store this function inside our ghost customer. So whenever we access something that's not loaded, we can proceed to load the rest of the data for this customer. Of course, we need to set the initial state of our ghost customer, which is gonna be that this is in a ghost state. So just by looking at this now, it doesn't do anything in particular. Everything is still going to be loaded from the database, and the ghost customer doesn't override any particular behavior. So we want to make sure that we can override all the properties on our customer. We've already made all the properties on our customer virtual so we can proceed to override the default behavior of accessing and setting the data for these different properties.
let's start with enum of status and the GhostCustomer Class
enum LoadStatus { GHOST, LOADING, LOADED };
public class GhostCustomer : Customer
??? {
??????? private LoadStatus status;
??????? private readonly Func<Customer> load;
??????? public bool IsGhost => status == LoadStatus.GHOST;
??????? public bool IsLoaded => status == LoadStatus.LOADED;
??????? public GhostCustomer(Func<Customer> load) : base()
??????? {
??????????? this.load = load;
??????????? status = LoadStatus.GHOST;
??????? }
??????? public override string Name
??????? {
??????????? get
??????????? {
??????????????? Load();
??????????????? return base.Name;
??????????? }
??????????? set
??????????? {
??????????????? Load();
??????????????? base.Name = value;
??????????? }
??????? }
??????? public override string ShippingAddress
??????? {
??????????? get
??????????? {
??????????????? Load();
??????????????? return base.ShippingAddress;
??????????? }
??????????? set
??????????? {
??????????????? Load();
??????????????? base.ShippingAddress = value;
??????????? }
??????? }
??????? public override string City
??????? {
??????????? get
??????????? {
??????????????? Load();
??????????????? return base.City;
??????????? }
??????????? set
??????????? {
??????????????? Load();
??????????????? base.City = value;
??????????? }
??????? }
??????? public override string PostalCode
??????? {
??????????? get
??????????? {
??????????????? Load();
??????????????? return base.PostalCode;
??????????? }
??????????? set
??????????? {
??????????????? Load();
??????????????? base.PostalCode = value;
??????????? }
??????? }
??????? public override string Country
??????? {
??????????? get
??????????? {
??????????????? Load();
??????????????? return base.Country;
??????????? }
??????????? set
??????????? {
??????????????? Load();
??????????????? base.Country = value;
??????????? }
??????? }
??????? public void Load()
??????? {
??????????? if (IsGhost)
??????????? {
??????????????? status = LoadStatus.LOADING;
??????????????? var customer = load();
??????????????? base.Name = customer.Name;
??????????????? base.ShippingAddress = customer.ShippingAddress;
??????????????? base.City = customer.City;
??????????????? base.PostalCode = customer.PostalCode;
??????????????? base.Country = customer.Country;
??????????????? status = LoadStatus.LOADED;
??????????? }
??????? }
??? }
now we will implement the Repository to load the customer with the id only then we completely load the ghost object
public class CustomerRepository : GenericRepository<Customer>
??? {
??????? public CustomerRepository(ShoppingContext context) : base(context)
??????? {
??????? }
??????? public override Customer Get(Guid id)
??????? {
??????????? var customerId = context.Customers
??????????????? .Where(c => c.CustomerId == id)
??????????????? .Select(c => c.CustomerId)
??????????????? .Single();
??????????? return new GhostCustomer(() => base.Get(id))
??????????? {
??????????????? CustomerId = customerId
??????????? };
??????? }
??? }
Final Words
this article covered how to apply Lazy load principles in your applications. We saw that we can. Make sure that we only load necessary data depending on the situation that you're in. You have different patterns that can be applied in your particular situation We've been covering the different data access patterns and how to apply that in C#. We've looked at introducing the repository pattern, unit of work as well as Lazy loading