Skip to content

Commit

Permalink
DOC Eager loading
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed May 30, 2023
1 parent de1a71f commit cffa34d
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 0 deletions.
94 changes: 94 additions & 0 deletions en/02_Developer_Guides/00_Model/02_Relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,100 @@ the best way to think about it is that the object where the relationship will be
`Product` => `Category`, the `Product` model should contain the `many_many` side of the relationship, because it is much
more likely that the user will select categories for a product than vice-versa.

## Eager loading

Querying nested relationships inside a loop using the ORM is prone to the N + 1 query problem. To illustrate the N + 1 query problem, imagine a scenario where there are Teams with many child Players

```php
class Team extends DataObject
{
private static $has_many = [
'Players' => Player::class,
];
}
```

To retrieve teams and their players:

```php
$teams = Team::get();
foreach ($teams as $team) {
foreach ($team->Players() as $player) {
echo $player->FirstName;
}
}
```

In this case the loop will execute one query to retrieve all the teams and then an additional query for each team to retrieve its players. If we have 20 teams this loop would run 21 queries - one to get all the teams and then 20 more queries to get the players for each team.

```sql
# Retrieve all the teams
# Note this is not the exact SQL that would generated it is just for demonstration
SELECT * FROM Team;
# Retrieve the players for all the teams in 20 separate queries:
SELECT * FROM Player WHERE TeamID = 1
SELECT * FROM Player WHERE TeamID = 2
SELECT * FROM Player WHERE TeamID = 3
SELECT * FROM Player WHERE TeamID = ...
```

The N + 1 query problem can be alleviated using eager loading which in this example will reduce this down to just two queries. We do this by passing the relationships that should be eagerly loaded to the `eagerLoad()` method on [DataList](api:SilverStripe\ORM\DataList):

```php
$teams = Team::get()->eagerLoad('Players')
```

With eager loading now only two queries will be executed:

```sql
# Retrieve all the teams
SELECT * FROM Team
# Retrieve all the players for the teams in a single query:
SELECT * FROM Player WHERE TeamID IN (1, 2, 3, ...)
```

Eager load multiple relationships by passing additional arguments to the ->eagerLoad() method:

```php
$teams = Team::get()->eagerLoad('Players', 'Fans');
```

Eager load nested relationships up to **three** levels deep using dot syntax:

```php
$teams = Team::get()->eagerLoad('Players.Games.Officials')
foreach ($teams as $team) {
foreach ($team->Players() as $player) {
foreach ($player->Games() as $game) {
foreach ($game->Officials() as $official) {
// Everything will have been eager loaded at this point
echo $official->FirstName;
}
}
}
}
```

Eager loading can be used in templates. The following example assumes that `$MyTeams` is an available DataList which could be provided via a `getMyTeams()` method on `PageController`:

```ss
<% loop $MyTeams.eagerLoad('Players') %>
<% loop $Players %>
<p>Player first name is $FirstName</p>
<% end_loop %>
<% end_loop %>
```

Eager loading supports all relationships - `has_one`, `belongs_to`, `has_many`, `many_many`, `many_many_through`, `belongs_many_many`.

[notice]
Eager loading is only intended to be used in read-only scenarios such as when outputting data on the front-end of a website. When using default lazy-loading, relationship methods will return a subclass of [DataList](api:SilverStripe\ORM\DataList) such as [DataList](api:SilverStripe\ORM\HasManyList). However when using eager-loading [ArrayList](api:SilverStripe\ORM\ArrayList) will be returned instead. [ArrayList](api:SilverStripe\ORM\ArrayList) still has common methods such as `filter()`, `sort()`, `limit()` and `reverse()` available to manipulate its data.
[/notice]

## Cascading deletions

Relationships between objects can cause cascading deletions, if necessary, through configuration of the
Expand Down
25 changes: 25 additions & 0 deletions en/04_Changelogs/5.1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,36 @@ title: 5.1.0 (unreleased)
## Overview

- [Features and enhancements](#features-and-enhancements)
- [Eager loading](#eager-loading)
- [Other new features](#other-features)
- [Bug fixes](#bug-fixes)

## Features and enhancements

### Eager loading

When looping over nested relationships the ORM is prone to the N + 1 query problem where excessive database calls are made. Eager loading has been introduced via the new `DataList::eagerLoad($relationship)` method which alleviates the N + 1 problem by querying the nested relationship tables before they are needed using a single large `WHERE ID in ($ids)` SQL query instead of many `WHERE RelationID = $id` queries.

Imagine the following example where there is a Team model with 20 records, which has_many Players

```php
// Regular ORM usage without eager loading
// This would result in 21 SQL SELECT queries, 1 for Teams and 20 for Players
$teams = Team::get();

// Using the `eagerLoad()` method to eager load data from nested models -up to 3 relations deep
// This will result is only 2 SQL SELECT queries, 1 for Teams and 1 for Players
$teams = Team::get()->eagerLoad('Players');

foreach ($teams as $team) {
foreach ($team->Players() as $player) {
echo $player->FirstName;
}
}
```

Read more about [eager loading](/developer_guides/model/relations/#eager-loading) in the developer docs.

### Other new features

- You can now exclude specific `DataObject` models from the check and repair step of `dev/build` - see [ORM Performance](/developer_guides/performance/orm/#skip-check-and-repair) for more information.
Expand Down

0 comments on commit cffa34d

Please sign in to comment.