Controlling How Entities Load
By default, LightSpeed loads entities on demand. That is, if an entity has an association to another entity or a collection of other entities, the association is loaded the first time an application accesses it. This requires another query to the database, to retrieve the associated entity or the members of the collection. (Once the association is loaded, LightSpeed remembers that it’s loaded and doesn’t perform another database query.) For example:
var order = unitOfWork.FindById<Order>(1); // queries the database for an Order |
This is a safe, conservative strategy which avoids superfluous loads. It is referred to as lazy loading because it lazily doesn’t do any work it doesn’t need to.
However, when a unit of work needs a number of related entities, it can be imperfectly efficient. In the code fragment above, LightSpeed issued three database queries. If we knew ahead of time that the application was going to need the Customer entity and the Lines collection, it would have been more performant to perform all three queries in a single go.
The problem gets worse when dealing with collections of entities. For example:
var overdues = unitOfWork.Orders.Where(o => o.DueDate < today); |
This code fragment results in one query for the orders, then, for each order, another query for the customer. If the first query returned 1000 orders, then we would do 1000 queries for customers – a total of 1001 queries. This problem is called the N+1 problem because it means you need N+1 queries to process N objects.
LightSpeed makes it easy to avoid these problems using eager loading.
Eager Loading
Eager loading means loading associated entities before they are needed. In LightSpeed, eager loading specifically means loading associated entities using the same database query as the source entity, avoiding an extra round trip. Here is how our N+1 code fragment works with eager loading.
var overdues = unitOfWork.Orders.Where(o => o.DueDate < today); |
By using eager loading we replace 1001 trips to the database to a single trip! Of course, that one trip now has to do more work, so you should eager load only when you know you are going to need the associations.
To implement eager loading, select an association that you want to be eager loaded, and:
· If you want parent entities to eager load their children, set Eager Load Collection to True.
· If you want child entities to eager load their parents, set Eager Load Backreference to True.
In the example above, because you want the Order entity to eager load its associated Customer, you would select the Order to Customer association and set Eager Load Backreference to True. If you also wanted the Customer entity to eager load its collection of Orders, you would also set Eager Load Collection to True. (An association can be eager loaded in both directions.)
For hand-coded associations, you implement eager loading by applying the EagerLoad attribute to the association field:
Implementing eager loading on hand-coded associations |
public class Order : Entity<int> |
Eager loading cascades, allowing you to load a whole object graph in a single database round-trip. For example, if Customer.Orders is eager loaded, and Order.Lines is eager loaded, then when you load one or more Customers, LightSpeed will automatically fetch not only the orders for all those customers but also the lines for all those orders.
Fine Grained Control Using Named Aggregates
It often happens that some parts of an application want to eager load an association, but other parts do not. For example, a page which displays the details of an order knows that it will be displaying the order lines, so would want to eager load the Lines collection. But a page which displays a list of overdue orders doesn’t need to display the individual order lines, so it doesn’t want to eager load the Lines collection – but it might want to eager load the customers so that it could show the user who each overdue order belonged to.
To support conditionally eager loading an association, LightSpeed uses named aggregates. ‘Aggregate’ is a term from domain-driven design that refers to a bounded object graph that we need to satisfy a particular application use case. (In the example above, there were two aggregates. The order details page was interested in an “order plus lines” aggregate. The overdue orders page was interested in an “order plus customer” aggregate.) Named aggregates allow you to name particular object graphs, and tell LightSpeed to eager load different aggregates in different situations.
To specify that an association is part of a named aggregate, select the association arrow, and enter the aggregate name in the Collection Aggregates or Backreference Aggregates box (or both). An association can be part of multiple named aggregates – separate the aggregate names with semicolons.
For hand-coded associations, specify the AggregateName property on the EagerLoad attribute. You can specify the attribute multiple times.
Specifying that a hand-coded association is part of a named aggregate |
public class Order : Entity<int> |
To eager load a particular aggregate using LINQ, specify it in your query using the WithAggregate() method:
Eager loading a named aggregate using LINQ |
// Loads orders and associated lines, but not associated customers |
In LightSpeed 5 a new feature was added where if a model defines named aggregates, constants based on their names are emitted. This allows you to write code such as WithAggregate(StoreAggregates.WithProductInfo), so you don’t have to specify the named aggregate as a string.
To eager load a particular aggregate using query objects, specify it in using the Query.AggregateName property:
Eager loading a named aggregate using query objects |
// Loads orders and associated lines, but not associated customers |
In both of the above samples, changing “WithLines” to “WithAllDetails” would eager load both the lines and the customers. Omitting the aggregate would mean both the lines and the customers were lazy loaded.
The aggregate name is an attribute of a query, and affects all associations in the aggregate, not just the associations on the starting object. For example, if both Customer.Orders and Order.Lines are marked with the “WithAllDetails” aggregate, then loading a Customer with the WithAllDetails aggregate will load the customer’s orders, and all the lines of those orders. This allows you to load deep object graphs in a single database access if required.