Skip to main content

Loading Relations

Ormed supports eager loading, lazy loading, and aggregate relation loading so you can control query count and response shape explicitly.

Prerequisites

What You’ll Learn

  • How to eager-load and lazy-load relation graphs
  • How to avoid N+1 query patterns
  • How nested relations and constraints are expressed

Step Outcome

By the end of this page, you should be able to load relation trees intentionally, detect accidental lazy loads, and use aggregate relation helpers.

Snippet context
  • Snippets focus on relation APIs and omit full setup.
  • Unless shown otherwise, assume you already have a DataSource named dataSource and your relations were generated via build_runner.

1. Eager Load First

Load relations with the main query to avoid per-row follow-up calls.

Basic Eager Loading

Future<void> basicEagerLoading(DataSource dataSource) async {
// Load a single relation
final posts = await dataSource.query<$Post>().with_(['author']).get();

for (final post in posts) {
print(post.author?.name); // Already loaded, no additional query
}
}

Multiple Relations

Future<void> multipleRelationsLoading(DataSource dataSource) async {
final posts = await dataSource.query<$Post>().with_([
'author',
'tags',
'comments',
]).get();
}

Nested Relations

Load multiple levels of relationships using dot notation. Ormed will recursively load each segment of the path.

Future<void> nestedRelationsLoading(DataSource dataSource) async {
// Load author's profile along with author
final posts = await dataSource.query<$Post>().with_(['author.profile']).get();

// Multiple levels
final deepPosts = await dataSource.query<$Post>().with_([
'author.profile',
'comments.user.profile',
]).get();
}

You can constrain nested segments when needed:

final posts = await dataSource.query<$Post>()
.withRelation('comments.user', (q) => q.where('active', true))
.get();

2. Lazy Load When Needed

Lazy loading is useful for conditional paths, but prefer eager loading in list endpoints.

Load Relations

Future<void> lazyLoading(DataSource dataSource) async {
final post = await dataSource.query<$Post>().find(1);

if (post != null) {
// Load after fetching
await post.load(['author', 'tags']);

print(post.author?.name);
}
}

Load Missing Only

Only loads relations that haven't been loaded yet:

Future<void> loadMissingExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().find(1);

if (post != null) {
// Only loads relations that haven't been loaded yet
await post.loadMissing(['author', 'comments']);
}
}

Check If Loaded

Future<void> checkLoadedExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().find(1);

if (post != null) {
if (post.relationLoaded('author')) {
print(post.author?.name);
} else {
await post.load(['author']);
}
}
}

3. Access and Manage Loaded Relations

Future<void> relationAccessExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().with_([
'author',
'comments',
]).first();

if (post != null) {
// Returns the relation value (throws if not loaded)
final author = post.getRelation<$User>('author');

// For has-many relations
final comments = post.getRelationList<$Comment>('comments');

// Manually set a relation
// post.setRelation('author', user);

// Unset a relation
post.unsetRelation('author');

// Clear all loaded relations
post.clearRelations();
}
}

4. Load Relation Aggregates

Aggregate helpers avoid loading entire related collections.

Count

Future<void> countAggregateExample(DataSource dataSource) async {
final user = await dataSource.query<$User>().first();

if (user != null) {
await user.loadCount(['posts', 'comments']);
// Access via getAttribute
print('Posts: ${user.getAttribute<int>('posts_count')}');
print('Comments: ${user.getAttribute<int>('comments_count')}');
}
}

Sum

Future<void> sumAggregateExample(DataSource dataSource) async {
final user = await dataSource.query<$User>().first();

if (user != null) {
await user.loadSum(['posts'], 'views');
print('Total views: ${user.getAttribute<num>('posts_views_sum')}');
}
}

Other Aggregates

Future<void> otherAggregatesExample(DataSource dataSource) async {
final user = await dataSource.query<$User>().first();

if (user != null) {
await user.loadAvg(['posts'], 'rating');
await user.loadMax(['posts'], 'views');
await user.loadMin(['posts'], 'views');
}
}

Exists

Future<void> existsAggregateExample(DataSource dataSource) async {
final user = await dataSource.query<$User>().first();

if (user != null) {
await user.loadExists(['posts']);
if (user.getAttribute<bool>('posts_exists') ?? false) {
print('User has posts');
}
}
}

Attach & Detach

Future<void> attachDetachExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().first();

if (post != null) {
// Attach related models to a many-to-many relationship
await post.attach('tags', [1, 2]);

// With pivot data
await post.attach('tags', [3], pivot: {'added_by': 1});

// Detach related models
await post.detach('tags', [1]);

// Detach all
await post.detach('tags');
}
}

Sync & Toggle

Future<void> syncToggleExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().first();

if (post != null) {
// Sync: replaces all related models with these
await post.sync('tags', [1, 2, 3]);

// Toggle: add if not present, remove if present
await post.toggle('tags', [1, 2]);
}
}

Belongs To Operations

Future<void> associateExample(DataSource dataSource) async {
final postRepo = dataSource.repo<$Post>();
final post = await dataSource.query<$Post>().first();
final user = await dataSource.query<$User>().first();

if (post != null && user != null) {
// Set a belongs-to relationship
post.associate('author', user);
await postRepo.update(post);

// Remove a belongs-to relationship
post.dissociate('author');
await postRepo.update(post);
}
}

6. Prevent N+1 Queries in Development

Enable lazy-loading prevention to fail fast during development:

void preventNPlusOneExample() {
// Enable lazy loading prevention in development
// void main() {
// if (kDebugMode) {
// Model.preventLazyLoading();
// }
// runApp(MyApp());
// }
//
// This throws an exception when you try to access a relation
// that hasn't been eager-loaded, helping catch N+1 issues.
}

This throws when code accesses an unloaded relation, which surfaces N+1 issues early.

Future<void> nPlusOneBadExample(DataSource dataSource) async {
// BAD: This causes N+1 queries
final posts = await dataSource.query<$Post>().get();

for (final post in posts) {
// This throws LazyLoadingException in debug mode
// because 'author' wasn't eager-loaded
print(post.author?.name);
}
}

Fix by eager loading:

Future<void> nPlusOneGoodExample(DataSource dataSource) async {
// GOOD: Eager load author
final posts = await dataSource
.query<$Post>()
.with_(['author']) // Eager load author
.get();

for (final post in posts) {
print(post.author?.name); // Works!
}
}

Verify Your Setup

  • List endpoints eager-load the relations they render.
  • Development mode catches accidental lazy loads.
  • Aggregate fields (*_count, *_sum, *_exists) are used when full relation data is unnecessary.

Read This Next