Relations

OT
Last updated 13 days ago

Entity relations are declared using wrapper types. Learn how to form to-one and to-many relations here.

The ObjectBox Swift Alpha does not yet expose all relation types ObjectBox internally offers. Relations are a work in progress.

To-One Relations

To-One Relation

You define a to-one relation using the ToOne class, a smart proxy to the target object. It gets and caches the target object transparently.

For example, an order is typically made by one customer. Thus, we could model the Order class to have a to-one relation to the Customer like this:

class Customer: Entity {
var id: Id<Customer> = 0
// ...
}
class Order: Entity {
var id: Id<Order> = 0
var customer: ToOne<Customer> = nil
// ...
}

Given these entities and their to-one relation, you can create a relation and persist it:

let store = ...
// Illustrate that initially, nothing did exist
assert(store.box(for: Customer.self).isEmpty)
assert(store.box(for: Order.self).isEmpty)
let customer = Customer()
let order = Order()
order.customer.target = customer
let orderId = store.boxFor(Order.self).put(order) // puts order and customer
// Verify the `put` was called for the relation target as well
assert(store.box(for: Customer.self).count == 1)
assert(store.box(for: Order.self).count == 1)

You can persist whole trees of object relations at once: If the customer object does not yet exist in the database, the ToOne will put() it. If it already exists, the ToOne will only create the relation (but not put() it).

You must specify your conventional properties first, and their relations below them. Do not interleave properties and relations. If you do, you will currently receive an EXC_BAD_ACCESS at runtime.

For a comprehensive documentation of its features, visit the ToOne API docs.

Removing Relations

You have to set the target of a relation to nil and persist the changes (via Box.put) to remove a relation permanently. Which one you reset doesn't matter, though. You have these options:

anOrder.customer.target = nil
// ... or ...
anOrder.customer.targetId = nil
// ... are both equivalent to:
anOrder.customer = nil
// ... whis is a short version of:
anOrder.customer = ToOne<Customer>(target: nil)

ToOne is a Lazy Relation Proxy

Initially, we mentioned that ToOne is actually a proxy cache. The target object is not eagerly loaded from the store; it is loaded lazily. Until you request the ToOne.target, it will not be read into main memory.

To-Many Relations

One-To-Many Relation

The only supported to-many relation of ObjectBox Swift Alpha is backlinks.

Backlinks are a ToMany relation, only it doesn't get persisted.

To use a Swift metaphor: When a ToOne relation is a stored property, a ToMany backlink is a lazily stored property that is computed once.

The lazy evaluation is not a live computation. Once you force the evaluation of the underlying stored collection, like when you ask for the count of objects, further calls to count will return the same result even when you remove the object from the store. You have to fetch the source object again and ask for its relations.

Let's extend the example from above to get the backlinks from Customer to Order:

class Customer: Entity {
var id: Id<Customer> = 0
var orders: ToMany<Order, Customer> = nil // Backlink
// ...
}
class Order: Entity {
var id: Id<Order> = 0
var customer: ToOne<Customer> = nil
// ...
}

Once the backlink is set up, you can traverse the relation in both directions:

let store = ...
// Illustrate that initially, nothing did exist
assert(store.box(for: Customer.self).isEmpty)
assert(store.box(for: Order.self).isEmpty)
let customer = Customer()
store.boxFor(Order.self)
.put([Order(customer: customer), Order(customer: customer)])
// Box contents
assert(store.box(for: Order.self).count == 2)
assert(store.box(for: Customer.self).count == 1)
// Backlink
assert(store.box(for: Customer.self).all[0].orders.count == 2)

Removing ToMany Relations

Since this type only supports backlinks at the moment, you cannot modify relations from this end. You have to remove the source ToOne relation instead:

// You cannot set `aCustomer.orders = nil`, so:
let order = Array(aCustomer.orders)
orders.forEach { order in
order.customer = nil
}
store.box(for: Order.self).put(orders)

Collection Nature of ToMany

ToMany conforms to Swift's RandomAccessCollection protocol. You can use it just like an array or similar collections. Pass it around as an array like this:

let orders = Array(customer.orders)

See the ToMany API docs for details.

If you have several ToOne relationships to the same entity type in your entity you need to tell ObjectBox which of these you are back-linking to. You do this using an annotation:

class Order: Entity {
var id: Id<Customer> = 0
var owner: ToOne<Customer, Order> = nil
var recipient: ToOne<Customer, Order> = nil
// ...
}
class Customer: Entity {
var id: Id<Customer> = 0
// sourcery: backlink = "owner"
var orders: ToMany<Order, Customer> = nil
// ...
}

This will tell ObjectBox which ones of the fields go together.