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 29, 2023
1 parent de1a71f commit 333623a
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 0 deletions.
93 changes: 93 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,99 @@ 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

Related data is lazy loaded by default which means the data is not fetched from the database until you call a method that "uses" the data, such as when the `getIterator()` method is called on a [DataList](api:SilverStripe\ORM\DataList) in a `foreach()` loop. Lazy-loading is very useful because it allows the fluent programming style of chaining methods such as `filter()` and `sort()` together without unecessary database calls. However this approach 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 for to get the players for each team.

```sql
# Retrieve all the teams (note this is not the exact SQL that used it is just example purposes)
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 = ...
```

Silverstripe ORM provides a feature called "eager loading" which makes this much efficient by reducing this to just two queries. When calling the database, we can specify which relationships should be eagerly loaded using the `eagerLoad()` method on [DataList](api:SilverStripe\ORM\DataList):

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

By using eager loading now only two queries will be executed:Retrieve all the teams:

```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 $offical) {
// everything will have been eager loaded
echo $offical->FirstName;
}
}
}
}
```

Eager loading can be used in templates as follows, assuming that `$MyTeams` is a DataList available e.g. as a `getMyTeams()` method on your projects `PageController`:

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

todo: confirm this on https://docs.silverstripe.org/en/4/developer_guides/templates/syntax/#altering-the-list still work and confirm that $Odd $Even etc still work tooAll relationships types support eager loading i.e. `has_one`, `belongs_to`, `has_many`, `many_many`, `many_many_through`, `belongs_many_many`

[notice]
Eager loading is intended to used exclusively in read-only scenarios, for instance when outputting data on the front-end of a website. When using default lazy-loading relationship methods will return a subclass of DataList e.g. HasManyList , however when using eager-loading they will return an ArrayList instead. 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

By default the Silverstripe ORM uses lazy loading which means data is only fetched from the database when it needs to be fetched, which allows chaining of method calls such as `filter()` and `sort()`. This mostly avoid unecessary database calls though it is prone to the N + 1 query problem when querying nested database relationships inside loops which has a negative impact on performance. Eager loading has been introduced via `DataList::eagerLoad($relationship)` which alleviates the N + 1 problem by querying the nested relationship tables before they are needed using single large `WHERE ID in ($ids)` SQL query.

```php
// Example where there is a Team model with 20 records, which has_many Players

// 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 333623a

Please sign in to comment.