Docs Menu
Docs Home
/ /
Atlas Device SDKs
/

Model Relationships

On this page

  • Relationship Types
  • To-One Relationship
  • To-Many Relationship
  • Inverse Relationship

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.

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.

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
{
[PrimaryKey]
[MapTo("_id")]
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
{
[PrimaryKey]
[MapTo("_id")]
public ObjectId Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Breed { get; set; } = String.Empty;
}
@RealmModel()
class _Bike {
@PrimaryKey()
late ObjectId id;
late String name;
late _Person? owner;
}
@RealmModel()
class _Person {
@PrimaryKey()
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 {
@PrimaryKey
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 {
@Persisted var name: String = ""
@Persisted var birthdate: Date = Date(timeIntervalSince1970: 1)
// A person can have one dog
@Persisted var dog: Dog?
}
class Dog: Object {
@Persisted var name: String = ""
@Persisted var age: Int = 0
@Persisted 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?",
},
};
}

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
{
[PrimaryKey]
[MapTo("_id")]
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
{
[PrimaryKey]
[MapTo("_id")]
public ObjectId Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Breed { get; set; } = String.Empty;
}
@RealmModel()
class _Scooter {
@PrimaryKey()
late ObjectId id;
late String name;
late _Person? owner;
}
@RealmModel()
class _ScooterShop {
@PrimaryKey()
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 {
@PrimaryKey
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 {
@Persisted var name: String = ""
@Persisted var birthdate: Date = Date(timeIntervalSince1970: 1)
// A person can have many dogs
@Persisted var dogs: List<Dog>
}
class Dog: Object {
@Persisted var name: String = ""
@Persisted var age: Int = 0
@Persisted 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?",
},
};
}

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 a User object.

  • The User object's Items property inverts the relationship and refers to all Item objects that contain this specific User in their Assignee 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> where T 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 {
@PrimaryKey
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> where T 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
{
[PrimaryKey]
[MapTo("_id")]
public ObjectId Id { get; set; } = ObjectId.GenerateNewId();
public string Name { get; set; }
[Backlink(nameof(Item.Assignee))]
public IQueryable<Item> Items { get; }
}
public partial class Item : IRealmObject
{
[PrimaryKey]
[MapTo("_id")]
public ObjectId Id { get; set; } = ObjectId.GenerateNewId();
public string Text { get; set; }
public User? Assignee { get; set; }
}
@RealmModel()
class _User {
@PrimaryKey()
late ObjectId id;
late String username;
// One-to-many relationship that the backlink is created for below.
late List<_Task> tasks;
}
@RealmModel()
class _Task {
@PrimaryKey()
late ObjectId id;
late String description;
late bool isComplete;
// Backlink field. Links back to the `tasks` property in the `_User` model.
@Backlink(#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;
@LinkingObjects("frogFriends")
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
@LinkingObjects("frogFriends")
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 {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var _partition: String = ""
@Persisted var name: String = ""
// A user can have many tasks.
@Persisted var tasks: List<Task>
}
class Task: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var _partition: String = ""
@Persisted 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.
@Persisted(originProperty: "tasks") var assignee: LinkingObjects<User>
}
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.

Back

Define Property Types

Next

Change an Object Model