Skip to content

Eager Loading

When accessing relationships as properties, query execution is "lazy". This means the relationship data is not actually loaded until you access it. However, eager loading allows you to query many models and then load the relationship data for all of them in a single query.

This significantly reduces the N+1 query problem.

Using withRelations

You can eager load relationships using the withRelations method on the query builder:

dart
final users = await User().query()
    .withRelations(['posts', 'profile'])
    .get();

Constraining Eager Loads

Sometimes you may wish to eager load a relationship, but also specify additional query conditions for the eager loading query. You can accomplish this by passing a Map to withRelations instead of a List.

The keys should be the relationship names, and the values should be closures that modify the query:

dart
final users = await User().query().withRelations({
  'posts': (q) {
        q.where('active', 1)
         .orderBy('created_at', direction: 'DESC');
  },
}).get();

In this example, Bavard will eager load the posts relationship, but only posts where the active column is 1 will be included.

Type Safety

Since the closure receives a generic QueryBuilder, you can cast it to the specific model type if you need type safety or want to use autocompletion:

dart
final users = await User().query().withRelations({
  'posts': (q) {
    (q as QueryBuilder<Post>).where('active', 1);
  },
}).get();

This will execute:

  1. SELECT * FROM users
  2. SELECT * FROM posts WHERE user_id IN (1, 2, 3...)
  3. SELECT * FROM profiles WHERE user_id IN (1, 2, 3...)

Accessing Eager Loaded Data

Once loaded, you can access the relationships without awaiting a new query.

Defining getRelation (Mandatory)

To enable eager loading, your model must implement the getRelation method. This method maps the string names provided to withRelations to the actual relationship definitions.

dart
class User extends Model {
  HasMany<Post> posts() => hasMany(Post.new);
  HasOne<Profile> profile() => hasOne(Profile.new);

  @override
  Relation? getRelation(String name) {
    switch (name) {
      case 'posts':
        return posts();
      case 'profile':
        return profile();
      default:
        // Always fall back to super
        return super.getRelation(name);
    }
  }
}

Retrieving the Data

Use the getRelated (for single) and getRelationList (for lists) helpers:

dart
for (final user in users) {
  // No database query here
  final posts = user.getRelationList<Post>('posts');
  final profile = user.getRelated<Profile>('profile');

  print('User ${user.name} has ${posts.length} posts');
}

Nested Eager Loading

You can load nested relationships using "dot" syntax. For example, to load all of the book's authors and all of the author's personal contacts in one go:

dart
final books = await Book().query()
    .withRelations(['author.contacts'])
    .get();

This works recursively. If you want to load the posts, and for each post load the comments, and for each comment load the author:

dart
final users = await User().query()
    .withRelations(['posts', 'posts.comments.author'])
    .get();

Released under the MIT License. Crafted with ❤️ for the Dart ecosystem.