Skip to main content
Relationships let one root collection point to objects in another root collection. Unlike embedded objects — which are stored inside their parent — linked objects keep their own collection, their own lifecycle, and their own typed query API. Cindel provides CindelLink<T> for to-one relationships, CindelLinks<T> for to-many relationships, and @Backlink for read-only inverse access.

Model Overview

The examples throughout this page use Artist and Song collections.
This defines three relationship fields:
  • Song.primaryArtist — one song points to one artist.
  • Song.featuredArtists — one song points to many artists.
  • Artist.songs — read-only inverse: one artist can load songs whose featuredArtists link includes that artist.
Link containers must be declared as final fields. The target type must be another root Cindel collection — embedded objects cannot be relationship targets.

To-One Relationships: CindelLink<T>

Use CindelLink<T> when one object points to zero or one object in another collection.

Set and Save

Assign the target through .value, then persist the relationship:

Load

Reading a Song from the database does not automatically load its link. Load it explicitly after fetching the object:

Clear

Set .value to null and save to remove the relationship from storage:

Reset In-Memory State

.reset() discards the currently loaded value in memory without touching the stored relationship. Use it when you want to force a fresh load later:

To-Many Relationships: CindelLinks<T>

Use CindelLinks<T> when one object points to a set of objects in another collection.

Add, Remove, and Save

Modify the in-memory set, then persist it with a single save call:
save() replaces the stored ID set with the current in-memory contents. If you add one artist and remove another and then call save(), the stored relationship reflects exactly what is in the container at that moment.

Load and Iterate

Load the relationship explicitly after fetching the object:
CindelLinks<T> is iterable after loading. You can also convert it to a list:

Reset In-Memory State

.reset() clears the in-memory set without changing what is stored:

A backlink is a read-only inverse of a forward relationship. It lets an object load the collection objects that point to it, without requiring a separate forward field on both sides.
The to value is the Dart field name of the forward link on the other collection — in this case featuredArtists on Song. It is not the collection name. Loading works exactly like a to-many:
Calling .save() on a backlink throws. To change what appears in a backlink, update and save the forward link:

Saving Relationships

Always store the target object and the source object before saving the relationship. Cindel validates that both IDs exist when you call save(). If either object has not been stored yet, saving the relationship fails.
The correct write order inside a transaction:
1

Store the target

Put the object being pointed to first so its ID is assigned.
2

Store the source

Put the object that holds the link field.
3

Update in-memory containers

Set .value or call .add() / .remove() on the link containers.
4

Save the forward link

Call .save() on the CindelLink or CindelLinks field. Do not call .save() on a backlink.

Loading Relationships

Reading an object from the database does not automatically load its link values. You must load each relationship explicitly.
The object you load from must be database-backed (i.e. previously stored). A newly constructed object that has never been put cannot load persisted relationships.

Common Mistakes