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.
- Snippets focus on relation APIs and omit full setup.
- Unless shown otherwise, assume you already have a
DataSourcenameddataSourceand your relations were generated viabuild_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');
}
}
}
5. Mutate Relationship Links
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.