In one of the first few episodes of the MVC Storefront, Ayende decided to be brilliant and came up with a really cool way to handle lazy loading with Linq To Sql: The LazyList. If you're not familiar with the term, "Lazy Loading" applies to the concept of an object that has a property which is a collection of child objects - like a Category would have Products.
When using an ORM (object-relational mapper) tool, the question of whether/when to load these child objects comes up a lot, as it has some serous performance implications.
IQueryable As Game-changer
IQueryable is the cornerstone of Linq To Sql, and has (what I think) is a killer feature: delayed execution. IQueryable essentially creates an Expression for you that you can treat as an enumerable list - and only when you iterate or ask for a value will the query be executed.
This has a lot of implications - especially for how you think of your Data Access Layer. It led me to make some tweaks to the Repository Pattern (which made a lot of people cranky) and also enabled a really nice way to do Lazy Loading for the objects in my model.
The Idea
... Was Ayende's. We were going over Data Access strategy and he got really excited and said "GIMME CONTROL!" (we were using SharedView at the time) and then started banging out some code. What he created was an implementation of IList<> that he called LazyList (this pattern is used in Java, Ruby, and other languages too) that took an IQueryable definition in the constructor. When you iterated the LazyList (or asked for a value), the query would get fired, populating the list.
This is a great pattern - it's almost a step between Lazy and Eager Loading - if you need it, it's there. Otherwise it's not.
The original thought here was that I could define a child collection (let's use Products on a Category) as an IList, and in the mapping code I could set that property to be a LazyList. Seems like it should work, but it didn't.
In summary there were some bugs in the Linq To Sql butter that cause the list to iterate, and I had a show to do so I just moved on, hoping some smart guy would figure it out. And lo and behold he did! I love community development...
Fooling Linq To Sql
K. Scott Allen came up with the answer (I never trust people who use an initial as their first name - I never know what to call them! K? K. Scott? What's the "K" for anyway? Was is that bad that you had to shorten it and go with an initial? If so, now I'm really curious... or perhaps that's what he wants... see this kind of thing is social quicksand... I bet his name is Karl and he hated telling people "no, with a K!").
The answer is two-fold:
If you want to read more - see Scott's entry.
Implementing Lazy List - Again
The good news is I didn't need to change any code in the LazyList. All I needed to do was to "fool" Linq to Sql when mapping my object so that it couldn't try to write a query for the relationship (I did this with a "let" statement), and I reset my Products child list from an IList<Product> to a LazyList<Product>. And it worked.
To illustrate, here's my changed Product with it's child collections (Images, Reviews, etc) set to LazyList:
public class Product { //... public LazyList<ProductReview> Reviews { get; set; } public LazyList<ProductImage> Images { get; set; } public LazyList<Product> RelatedProducts { get; set; } public LazyList<Product> CrossSells { get; set; } //... }
Here's the changed mapping code, which sets those lists:
var result = from p in _db.Products join detail in cultureDetail on p.ProductID equals detail.ProductID let images = GetImages(p.ProductID) let crosses = GetCrossSells(p.ProductID) let related = GetRelated(p.ProductID) let reviews = GetReviews(p.ProductID) select new Product { ID = p.ProductID, Name = p.ProductName, Description = detail.Description, ShortDescription = detail.ShortDescription, Price = detail.UnitPrice ?? p.BaseUnitPrice, Manufacturer = p.Manufacturer, ProductCode = p.ProductCode, Images = new LazyList<ProductImage>(images), CrossSells = new LazyList<Product>(crosses), RelatedProducts = new LazyList<Product>(related), Reviews = new LazyList<ProductReview>(reviews), Delivery = (DeliveryMethod) p.DeliveryMethodID };
You'll notice here that the "let" statements point to some methods that take a Product ID. This is part of fooling Linq To Sql so it doesn't try to introspect this relationship and build some weird SQL statement. I had to create these methods and they are simply filter statements that return IQueryable<Product> (you can see these in the checked in Storefront code).
The code looks a lot cleaner too!
And finally, for reference, here's the LazyList code:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; namespace Commerce.MVC.Data { /// <summary> /// An IList implementation that flexes IQueryable's delayed loading /// </summary> /// <typeparam name="T">IList of T</typeparam> public class LazyList<T> : IList<T> { public LazyList() { } public LazyList(IQueryable<T> query) { this.query = query; } private IQueryable<T> query; private IList<T> inner; public int IndexOf(T item) { return Inner.IndexOf(item); } public void Insert(int index, T item) { Inner.Insert(index, item); } public void RemoveAt(int index) { Inner.RemoveAt(index); } public T this[int index] { get { return Inner[index]; } set { Inner[index] = value; } } public void Add(T item) { inner = inner ?? new List<T>(); Inner.Add(item); } public void Clear() { if(inner!=null) Inner.Clear(); } public bool Contains(T item) { return Inner.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { Inner.CopyTo(array, arrayIndex); } public bool Remove(T item) { return Inner.Remove(item); } public int Count { get { return Inner.Count; } } public bool IsReadOnly { get { return Inner.IsReadOnly; } } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return Inner.GetEnumerator(); } public IEnumerator GetEnumerator() { return ((IEnumerable)Inner).GetEnumerator(); } public IList<T> Inner { get { if (inner == null) inner = query.ToList(); return inner; } } } }
Rob this is great I was looking at the code and was wondering if the Let statements allowed you to use LazyList.
Using your pattern how would you go about lazy loading a property. Like lets say an Image property?
Am looking forward to your next webcast.
Thanks Joel
Is it possible to use chained relations like Product.Reviews[0].Product.Reviews[1].Product?
It doesn't seem to work in my code. Did you try this?
Did you measure the time to see if there is any significant performance penalty? and what are the timings?
Hi Joel - IQueryable is a 1 to n kind of thing - in other words there isn't a notion of lazy loading a single element. You could do something like a DefaultImage, though, that would return Images[0[ - this would fire the list.
Paco if I understand your code correctly, why can't you just use "Product"? It's sort of the "my dad's brother's only nephew" kind of thing, if that makes sense. Your example is referring to the parent yes?
Either way - it's doable, but the minute you ask for an indexer the list will be fire off. If you set each of those elements to LazyList, they will execute.
Rob,
IQueryable can return a single object using .Single() or .SingleOrDefault(). I guess I would have to create a LazyObject<T> and that wouldn't be very POCO of me. I just wasn't sure with this type of Repository how best to lazy load a property which is not a list. Let me know if you have any more ideas. Thanks.
Hi Joel - I know about SingleOrDefault() but I'm wondering, I spose, what the benefit is of having an object with an IQueryable property? In essence, if you have an instance then by definition its data should be set. Loading a single object on-delay is quite a perf hit.
this all sounds great, but i guess that i will need to go back to the earlier screancasts and see what the original problem is. For some reason I thought that the delayed execution basicaly was lazy loading. for example i thought by default linq to sql wouldn't go get say the product reviews in your above example unless you specifically asked for it. Just make it an IQueryable or for that matter doesn't IEnumerable work. I am probably showing my noob nature here so I need to go back to my Linq in action book to see what is really happening.
Hey Scott - no noobness here :). Linq To Sql has all kinds of plumbing for this - the thing is that I'm not using it's generated objects; I'm using my own model for the store and as such I lost it's ORM tools.
There's a reason to my madness, which I hope to show in a month or so as I finish this thing up.
Rob,
I am sorry if I made it seem like you didn't know what .Single or .SingleOrDefault was, as I knew you did. I was trying to explain my problem, and terribly I might add. :-)
I guess I am simply trying to delay loading certain properties of an object like say a persons Avatar, because I may not always want to get the Avatar. I may just be pulling the user's First and Last name. In this case the cost of always pulling the avatar is too high when I simply want to access the first and last name. However, maybe when I display the users profile, I do want to bring back their Avatar. Is that example any better? I am sorry that I am having a hard explaining the problem. I hope this is better.
Apologies for my incomplete sentences above.
How come the source is gone? I can't download it anymore?
Hey guys.. this lazy list is awesome.. so simple and very effective..
I saw the Pagination Class in the MVC Store Front and modified it inherit from a lazy list rather than a standard list.
I'm sorry about the comment above, I was doing something wrong.
Hi Rob,
I tried to get at the MVC Storefront code, but I can't download anything. Is the code going closed source?
LazyList? This is what LINQ to SQL's EntitySet<T> does. It implements IList<T>. Once you construct it you can give it an IEnumerable<T> source with a call to SetSource(). Then if you enumerate the list or examine its elements in some way it will enumerate the source to populate the list. This source could be a query, and is when initialized via L2S's materializer.
For singleton values, that's what EntityRef<T> does. It also holds onto an IEnumerable<T> and enumerates it to get the 'value' whenever you probe the Entity property.
Link<T> is also used for delayed loading of field values. It is very similar to EntityRef<T> but also works for non entity values.
EntitySet also has the ability to add/remove items w/o triggering the lazy load. You can re-use all these types w/o using L2S or needing a DataContext.
Rob, if you create a new LazyList<ProductImage>(images) and images==null, doesn't inner = query.ToList(); generates an null reference exception? Don't you want to check that before hand?
@Rob,
Matt brings up a good point. What does LazyList give you that EntitySet doesn't?
@Matt Warren: thanks for the tip - I hadn't thought of using EntitySet<T>. So, how would I use this in the scope of the application here? In other words - here's a sample loading of data:
var result = from p in _db.Products
join detail in cultureDetail on p.ProductID equals detail.ProductID
let images = GetImages(p.ProductID)
let crosses = GetCrossSells(p.ProductID)
let related = GetRelated(p.ProductID)
let reviews = GetReviews(p.ProductID)
select new Product
{
//...
Images = new LazyList<ProductImage>(images),
CrossSells = new LazyList<Product>(crosses),
RelatedProducts = new LazyList<Product>(related),
Reviews = new LazyList<ProductReview>(reviews),
};
I'm all for not re-inventing the wheel!
Anyone tried to implement a LazyDictionary?
Images = new LazyDictionary<ProductImage>(images, i => i.Path);
I implemented such a class, but when I use it I get some Linq exceptions.