Links

Links allow you to express relationships between objects, such as a comment's author (User). You can model 1:1, 1:n, and n:n relationships with Isar links. Using links is less ergonomic than using embedded objects, and you should use embedded objects whenever possible.

Think of the link as a separate table that contains the relation. It's similar to SQL relations but has a different feature set and API.

IsarLink<T> can contain no or one related object, and it can be used to express a to-one relationship. IsarLink has a single property called value which holds the linked object.

Links are lazy, so you need to tell the IsarLink to load or save the value explicitly. You can do this by calling linkProperty.load() and linkProperty.save().

Tip

The id property of the source and target collections of a link should be non-final.

For non-web targets, links get loaded automatically when you use them for the first time. Let's start by adding an IsarLink to a collection:

@collection
class Teacher {
  Id? id;

  late String subject;
}

@collection
class Student {
  Id? id;

  late String name;

  final teacher = IsarLink<Teacher>();
}

We defined a link between teachers and students. Every student can have exactly one teacher in this example.

First, we create the teacher and assign it to a student. We have to .put() the teacher and save the link manually.

final mathTeacher = Teacher()..subject = 'Math';

final linda = Student()
  ..name = 'Linda'
  ..teacher.value = mathTeacher;

await isar.writeTxn(() async {
  await isar.students.put(linda);
  await isar.teachers.put(mathTeacher);
  await linda.teachers.save();
});

We can now use the link:

final linda = await isar.students.where().nameEqualTo('Linda').findFirst();

final teacher = linda.teacher.value; // > Teacher(subject: 'Math')

Let's try the same thing with synchronous code. We don't need to save the link manually because .putSync() automatically saves all links. It even creates the teacher for us.

final englishTeacher = Teacher()..subject = 'English';

final david = Student()
  ..name = 'David'
  ..teacher.value = englishTeacher;

isar.writeTxnSync(() {
  isar.students.putSync(david);
});

It would make more sense if the student from the previous example could have multiple teachers. Fortunately, Isar has IsarLinks<T>, which can contain multiple related objects and express a to-many relationship.

IsarLinks<T> extends Set<T> and exposes all the methods that are allowed for sets.

IsarLinks behaves much like IsarLink and is also lazy. To load all linked object call linkProperty.load(). To persist the changes, call linkProperty.save().

Internally both IsarLink and IsarLinks are represented in the same way. We can upgrade the IsarLink<Teacher> from before to an IsarLinks<Teacher> to assign multiple teachers to a single student (without losing data).

@collection
class Student {
  Id? id;

  late String name;

  final teacher = IsarLinks<Teacher>();
}

This works because we did not change the name of the link (teacher), so Isar remembers it from before.

final biologyTeacher = Teacher()..subject = 'Biology';

final linda = isar.students.where()
  .filter()
  .nameEqualTo('Linda')
  .findFirst();

print(linda.teachers); // {Teacher('Math')}

linda.teachers.add(biologyTeacher);

await isar.writeTxn(() async {
  await linda.teachers.save();
});

print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}

I hear you ask, "What if we want to express reverse relationships?". Don't worry; we'll now introduce backlinks.

Backlinks are links in the reverse direction. Each link always has an implicit backlink. You can make it available to your app by annotating an IsarLink or IsarLinks with @Backlink().

Backlinks do not require additional memory or resources; you can freely add, remove and rename them without losing data.

We want to know which students a specific teacher has, so we define a backlink:

@collection
class Teacher {
  Id id;

  late String subject;

  @Backlink(to: 'teacher')
  final student = IsarLinks<Student>();
}

We need to specify the link to which the backlink points. It is possible to have multiple different links between two objects.

IsarLink and IsarLinks have a zero-arg constructor, which should be used to assign the link property when the object is created. It is good practice to make link properties final.

When you put() your object for the first time, the link gets initialized with source and target collection, and you can call methods like load() and save(). A link starts tracking changes immediately after its creation, so you can add and remove relations even before the link is initialized.

Danger

It is illegal to move a link to another object.