Model Relationships
This page describes how to define relationships between objects in your data model. To learn about SDK objects and how to define them, refer to Define an SDK Object Model.
Atlas Device SDK uses explicit relationships between the types of objects in an App. A relationship is an object property that references another SDK object rather than one of the primitive data types. You define relationships by setting the type of an object property to another object type in the object model or object schema.
Relationships are direct references to other objects in a database, which means that you don't need bridge tables or explicit joins to define a relationship like you would in a relational database. Instead you can access related objects by reading and writing to the property that defines the relationship. The SDK executes read operations lazily as they come in, so querying a relationship is just as performant as reading a regular property.
Relationship Types
The SDK supports to-one, to-many, and inverse relationships. The SDK also provides a special type of object, called an embedded object, that is conceptually similar to a relationship but provides additional constraints.
The SDK does not inherently limit object references from other objects within the same database. This means that SDK relationships are implicitly "many-to-one" or "many-to-many." The only way to restrict a relationship to "one to one/one to many" instead of "many to one/many to many" is to use an embedded object, which is a nested object with exactly one parent.
To-One Relationship
A to-one relationship maps one property in an object model to a single instance of another object type. For example, a dog might have a to-one relationship with a favorite toy.
Setting a relationship field to null removes the connection between objects. The SDK does not delete the referenced object, though, unless it is an embedded object.
Important
To-one relationships must be optional
When you declare a to-one relationship in your object model, it must be an optional property. If you try to make a to-one relationship required, the SDK throws an exception at runtime.
struct FavoriteToy { realm::primary_key<realm::uuid> _id; std::string name; }; REALM_SCHEMA(FavoriteToy, _id, name) struct Dog { realm::primary_key<realm::uuid> _id; std::string name; int64_t age; // Define a relationship as a link to another SDK object FavoriteToy* favoriteToy; }; REALM_SCHEMA(Dog, _id, name, age, favoriteToy)
public partial class Person : IRealmObject { [ ] [ ] public ObjectId Id { get; set; } public string Name { get; set; } public DateTimeOffset Birthdate { get; set; } public Dog? Dog { get; set; } } public partial class Dog : IRealmObject { [ ] [ ] public ObjectId Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Breed { get; set; } = String.Empty; }
()class _Bike { () late ObjectId id; late String name; late _Person? owner; } ()class _Person { () late ObjectId id; late String firstName; late String lastName; late int? age; }
import io.realm.RealmObject; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private Frog bestFriend; public Frog(String name, int age, String species, String owner, Frog bestFriend) { this.name = name; this.age = age; this.species = species; this.owner = owner; this.bestFriend = bestFriend; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public Frog getBestFriend() { return bestFriend; } public void setBestFriend(Frog bestFriend) { this.bestFriend = bestFriend; } }
class Manufacturer extends Realm.Object { static schema = { name: "Manufacturer", properties: { _id: "objectId", name: "string", // A manufacturer that may have one car car: "Car?", }, }; } class Car extends Realm.Object { static schema = { name: "Car", properties: { _id: "objectId", model: "string", miles: "int?", }, }; }
// Relationships of Realm objects must be of RealmObject type class Frog : RealmObject { var _id: ObjectId = ObjectId() var name: String = "" var age: Int = 0 // Property of RealmObject type (MUST be null) var favoritePond: Pond? = null var bestFriend: Frog? = null }
import io.realm.RealmObject open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null var bestFriend: Frog? = null constructor( name: String?, age: Int, species: String?, owner: String?, bestFriend: Frog? ) { this.name = name this.age = age this.species = species this.owner = owner this.bestFriend = bestFriend } constructor() {} // RealmObject subclasses must provide an empty constructor }
// Dog.h @interface Dog : RLMObject @property NSString *name; // No backlink to person -- one-directional relationship @end // Define an RLMArray<Dog> type RLM_COLLECTION_TYPE(Dog) // Person.h @interface Person : RLMObject @property NSString *name; // A person can have one dog @property Dog *dog; @end // Dog.m @implementation Dog @end // Person.m @implementation Person @end
class Person: Object { var name: String = "" var birthdate: Date = Date(timeIntervalSince1970: 1) // A person can have one dog var dog: Dog? } class Dog: Object { var name: String = "" var age: Int = 0 var breed: String? // No backlink to person -- one-directional relationship }
class Manufacturer extends Realm.Object { _id!: BSON.ObjectId; name!: string; car?: Car; static schema: ObjectSchema = { name: "Manufacturer", properties: { _id: "objectId", name: "string", // A manufacturer that may have one car car: "Car?", }, }; } class Car extends Realm.Object { _id!: BSON.ObjectId; model!: string; miles?: number; static schema: ObjectSchema = { name: "Car", properties: { _id: "objectId", model: "string", miles: "int?", }, }; }
To-Many Relationship
A to-many relationship means that an object relates to more than one other object. In Atlas Device SDK, a to-many relationship is a list of references to other objects. For example, a person might have many dogs.
You can represent a to-many relationship between two SDK types as a list, map, or a set. Lists, maps, and sets are mutable: within a write transaction, you can add and remove elements to and from these collection types. Lists, maps, and sets are not associated with a query and are declared as a property of the object model.
struct Company { int64_t _id; std::string name; // To-many relationships are a list, represented here as a // vector container whose value type is the SDK object // type that the list field links to. std::vector<Employee*> employees; }; REALM_SCHEMA(Company, _id, name, employees)
public partial class Person : IRealmObject { [ ] [ ] public ObjectId Id { get; set; } public string Name { get; set; } public DateTimeOffset Birthdate { get; set; } public IList<Dog> Dogs { get; } } public partial class Dog : IRealmObject { [ ] [ ] public ObjectId Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Breed { get; set; } = String.Empty; }
()class _Scooter { () late ObjectId id; late String name; late _Person? owner; } ()class _ScooterShop { () late ObjectId id; late String name; late List<_Scooter> scooters; }
import io.realm.RealmList; import io.realm.RealmObject; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private RealmList<Frog> bestFriends; public Frog(String name, int age, String species, String owner, RealmList<Frog> bestFriends) { this.name = name; this.age = age; this.species = species; this.owner = owner; this.bestFriends = bestFriends; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public RealmList<Frog> getBestFriends() { return bestFriends; } public void setBestFriends(RealmList<Frog> bestFriends) { this.bestFriends = bestFriends; } }
class Manufacturer extends Realm.Object { static schema = { name: "Manufacturer", properties: { _id: "objectId", name: "string", // A manufacturer that may have many cars cars: "Car[]", }, }; } class Car extends Realm.Object { static schema = { name: "Car", properties: { _id: "objectId", model: "string", miles: "int?", }, }; }
// Relationships of RealmList<E> or RealmSet<E> must be of RealmObject type class Forest : RealmObject { var _id: ObjectId = ObjectId() var name: String = "" // Set of RealmObject type (CANNOT be null) var frogsThatLiveHere: RealmSet<Frog> = realmSetOf() // List of RealmObject type (CANNOT be null) var nearbyPonds: RealmList<Pond> = realmListOf() }
import io.realm.RealmList import io.realm.RealmObject open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null var bestFriends: RealmList<Frog>? = null constructor( name: String?, age: Int, species: String?, owner: String?, bestFriends: RealmList<Frog>? ) { this.name = name this.age = age this.species = species this.owner = owner this.bestFriends = bestFriends } constructor() {} // RealmObject subclasses must provide an empty constructor }
// Dog.h @interface Dog : RLMObject @property NSString *name; // No backlink to person -- one-directional relationship @end // Define an RLMArray<Dog> type RLM_COLLECTION_TYPE(Dog) // Person.h @interface Person : RLMObject @property NSString *name; // A person can have many dogs @property RLMArray<Dog *><Dog> *dogs; @end // Dog.m @implementation Dog @end // Person.m @implementation Person @end
class Person: Object { var name: String = "" var birthdate: Date = Date(timeIntervalSince1970: 1) // A person can have many dogs var dogs: List<Dog> } class Dog: Object { var name: String = "" var age: Int = 0 var breed: String? // No backlink to person -- one-directional relationship }
class Manufacturer extends Realm.Object { _id!: BSON.ObjectId; name!: string; cars!: Realm.List<Car>; static schema: ObjectSchema = { name: "Manufacturer", properties: { _id: "objectId", name: "string", // A manufacturer that may have many cars cars: "Car[]", }, }; } class Car extends Realm.Object { _id!: BSON.ObjectId; model!: string; miles?: number; static schema: ObjectSchema = { name: "Car", properties: { _id: "objectId", model: "string", miles: "int?", }, }; }
Inverse Relationship
Relationship definitions in the SDK are unidirectional. An inverse relationship links an object back to an object that refers to it.
An inverse relationship property is an automatic backlink relationship. The SDK automatically updates implicit relationships whenever an object is added or removed in the specified relationship. You cannot manually set the value of an inverse relationship property.
Since relationships are many-to-one or many-to-many, following inverse relationships can result in zero, one, or many objects.
To define an inverse relationship, use linking_objects
in your object
model. The linking_objects
definition specifies the object type and
property name of the relationship that it inverts.
In this example, we define a Person
having a to-one relationship with
a Dog
. The Dog
has an inverse relationship to any Person
objects through its owners
property.
To define the inverse relationship, define a getter-only IQueryable<T>
property in your object model, where T
is the source type of the
relationship. Then, annotate this property with a
[Backlink(sourceProperty)]
attribute, where "sourceProperty" is the name of the property on the other
side of the relationship. The following example shows how to do this with the
"User has many Items" scenario.
In this example, note that:
The Item object's
Assignee
property is aUser
object.The
User
object'sItems
property inverts the relationship and refers to allItem
objects that contain this specificUser
in theirAssignee
property.
This, then, allows us to query the Item
collection to get all Items
assigned to a specific User
.
Use the Backlink
property annotation to define an inverse relationship.
Pass a Symbol
of the field name of the to-one or to-many field for which you are creating the
backlink as an argument to Backlink()
. Include an Iterable
of the
object model you are backlinking to in the field below the annotation.
To define an inverse relationship, define a LinkingObjects
property in your
object model. The LinkingObjects
definition specifies the object type and
property name of the relationship that it inverts.
Fields annotated with @LinkingObjects
must be:
marked
final
of type
RealmResults<T>
whereT
is the type at the opposite end of the relationship
To define an inverse relationship, define a linkingObjects
property in your
object model. linkingObjects
specifies the object type and
property name of the relationship that it inverts.
class Manufacturer extends Realm.Object { static schema = { name: "Manufacturer", properties: { _id: "objectId", name: "string", // A manufacturer that may have many cars cars: "Car[]", }, }; } class Car extends Realm.Object { static schema = { name: "Car", properties: { _id: "objectId", model: "string", miles: "int?", manufacturer: { type: "linkingObjects", objectType: "Manufacturer", property: "cars", }, }, }; }
Dynamically Obtain an Inversely Linked Object
You can dynamically retrieve an object with an inverse relationship without
defining a linkingObjects
type in its schema. Remove the
linkingObjects
type from your schema, so your schemas look like a standard
to-many relationship. When you need to retrieve the linked object, call the
Realm.Object.linkingObjects()
query.
In the following continuation from the inverse relationship example, we
have removed the manufacturer
field with type 'linkingObjects' from
the Car
schema. An application developer creates several manufacturers
and car objects, and the application pushes the newly-created cars into a
manufacturer's cars
field.
To find the manufacturer who makes a specific car object, call .linkingObjects()
and pass the "Manufacturer" class name and "cars" field as parameters.
The .linkingObjects()
method returns a Results collection of objects whose
property inverts the relationship. In this example, only one manufacturer makes
the Sentra car model, so we can expect that manufacturer to be named Nissan.
To define an inverse relationship between objects, first define a
collection property in the parent object whose type is a
RealmList<E>
, RealmSet<E>
, or RealmDictionary<E>
, where
<E>
is a RealmObject
object type defined in your data model.
This can be a different Realm object type or the same Realm object
type:
// Parent object must have RealmList<E>, RealmSet<E>, or // RealmDictionary<K,V> property of child type class User : RealmObject { var _id: ObjectId = ObjectId() var name: String = "" // List of child RealmObject type (CANNOT be nullable) var posts: RealmList<Post> = realmListOf() // Set of child RealmObject type (CANNOT be nullable) var favoritePosts: RealmSet<Post> = realmSetOf() // Dictionary of child RealmObject type (value MUST be nullable) var postByYear: RealmDictionary<Post?> = realmDictionaryOf() }
Then, define an immutable backlinks property in the child object of
RealmResults<E>
, where <E>
is the parent object type:
To define an inverse relationship, define a LinkingObjects
property in your
object model. The LinkingObjects
definition specifies the object type and
property name of the relationship that it inverts.
Fields annotated with @LinkingObjects
must be:
marked
final
of type
RealmResults<T>
whereT
is the type at the opposite end of the relationship
To define an inverse relationship, use RLMLinkingObjects in your object model. Override +[RLMObject linkingObjectProperties] method in your class to specify the object type and property name of the relationship that it inverts.
To define an inverse relationship, use LinkingObjects in your object model. The
LinkingObjects
definition specifies the object type and
property name of the relationship that it inverts.
To define an inverse relationship, define a linkingObjects
property in your
object model. linkingObjects
specifies the object type and
property name of the relationship that it inverts.
class Manufacturer extends Realm.Object { _id!: BSON.ObjectId; name!: string; cars!: Realm.List<Car>; static schema: ObjectSchema = { name: "Manufacturer", properties: { _id: "objectId", name: "string", // A manufacturer that may have many cars cars: "Car[]", }, }; } class Car extends Realm.Object { _id!: BSON.ObjectId; model!: string; miles?: number; manufacturer!: Realm.Collection<Manufacturer>; static schema: ObjectSchema = { name: "Car", properties: { _id: "objectId", model: "string", miles: "int?", manufacturer: { type: "linkingObjects", objectType: "Manufacturer", property: "cars", }, }, }; }
Dynamically Obtain an Inversely Linked Object
You can dynamically retrieve an object with an inverse relationship without
defining a linkingObjects
type in its schema. Remove the
linkingObjects
type from your schema, so your schemas look like a standard
to-many relationship. When you need to retrieve the linked object, call the
Realm.Object.linkingObjects()
query.
In the following continuation from the inverse relationship example, we
have removed the manufacturer
field with type 'linkingObjects' from
the Car
schema. An application developer creates several manufacturers
and car objects, and the application pushes the newly-created cars into a
manufacturer's cars
field.
To find the manufacturer who makes a specific car object, call .linkingObjects()
and pass the "Manufacturer" class name and "cars" field as parameters.
The .linkingObjects()
method returns a Results collection of objects whose
property inverts the relationship. In this example, only one manufacturer makes
the Sentra car model, so we can expect that manufacturer to be named Nissan.
struct Dog; struct Person { realm::primary_key<int64_t> _id; std::string name; int64_t age = 0; Dog* dog; }; REALM_SCHEMA(Person, _id, name, age, dog) struct Dog { realm::primary_key<int64_t> _id; std::string name; int64_t age = 0; linking_objects<&Person::dog> owners; }; REALM_SCHEMA(Dog, _id, name, age, owners)
public partial class User : IRealmObject { [ ] [ ] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); public string Name { get; set; } [ ] public IQueryable<Item> Items { get; } } public partial class Item : IRealmObject { [ ] [ ] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); public string Text { get; set; } public User? Assignee { get; set; } }
()class _User { () late ObjectId id; late String username; // One-to-many relationship that the backlink is created for below. late List<_Task> tasks; } ()class _Task { () late ObjectId id; late String description; late bool isComplete; // Backlink field. Links back to the `tasks` property in the `_User` model. (#tasks) late Iterable<_User> linkedUser; }
import io.realm.RealmObject; import io.realm.RealmResults; import io.realm.annotations.LinkingObjects; public class Frog extends RealmObject { private String name; private int age; private String species; private String owner; private final RealmResults<Toad> toadFriends = null; public Frog(String name, int age, String species, String owner) { this.name = name; this.age = age; this.species = species; this.owner = owner; } public Frog(){} // RealmObject subclasses must provide an empty constructor public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } }
const carObjects = realm.objects(Car); // Get the Manufacturer who makes the Car const linkedManufacturer = carObjects[0].linkingObjects( "Manufacturer", "cars" )[0]; expect(linkedManufacturer.name).toBe("Nissan");
// Backlink of RealmObject must be RealmResults<E> of parent object type class Post : RealmObject { var title: String = "" var date: RealmInstant = RealmInstant.now() // Backlink to parent RealmObject type (CANNOT be null & MUST be val) val user: RealmResults<User> by backlinks(User::posts) }
import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.LinkingObjects open class Frog : RealmObject { var name: String? = null var age = 0 var species: String? = null var owner: String? = null private val toadFriends: RealmResults<Toad>? = null constructor(name: String?, age: Int, species: String?, owner: String?) { this.name = name this.age = age this.species = species this.owner = owner } constructor() {} // RealmObject subclasses must provide an empty constructor }
// Task.h @interface Task : RLMObject @property NSString *description; @property (readonly) RLMLinkingObjects *assignees; @end // Define an RLMArray<Task> type RLM_COLLECTION_TYPE(Task) // User.h @interface User : RLMObject @property NSString *name; @property RLMArray<Task *><Task> *tasks; @end // Task.m @implementation Task + (NSDictionary *)linkingObjectsProperties { return @{ @"assignees": [RLMPropertyDescriptor descriptorWithClass:User.class propertyName:@"tasks"], }; } @end // User.m @implementation User @end
class User: Object { true) var _id: ObjectId (primaryKey: var _partition: String = "" var name: String = "" // A user can have many tasks. var tasks: List<Task> } class Task: Object { true) var _id: ObjectId (primaryKey: var _partition: String = "" var text: String = "" // Backlink to the user. This is automatically updated whenever // this task is added to or removed from a user's task list. "tasks") var assignee: LinkingObjects<User> (originProperty: }
const carObjects = realm.objects<Car>(Car); // Get the Manufacturer who makes the Car const linkedManufacturer: Manufacturer = carObjects[0].linkingObjects<Manufacturer>("Manufacturer", "cars")[0]; expect(linkedManufacturer.name).toBe("Nissan");
Note
Inverse Relationships Not Present in Device Sync Schema
If you are using Atlas Device Sync, inverse relationships are not present in the server-side Device Sync schema in your App Services App. Since you can't directly set the value of an inverse relationship, the relationship does not exist in Device Sync schema.
For more information on the server-side Device Sync schema, refer to Configure and Update Your Data Model in the Atlas App Services documentation.