Relationships

Define relationships between models using the @Relationship macro. SwiftDataServer supports one-to-many and many-to-many relationships with automatic join table management.

One-to-Many Relationships

A common pattern where one model owns many of another:

@Model
final class Author {
    var name: String = ""

    @Relationship(deleteRule: .cascade, inverse: \Book.author)
    var books: [Book] = []
}

@Model
final class Book {
    var title: String = ""

    @Relationship
    var author: Author?
}

Delete Rules

Rule Behavior
.nullify Set the relationship to nil when the parent is deleted (default)
.cascade Delete all related objects when the parent is deleted
.deny Prevent deletion if related objects exist

Many-to-Many Relationships

When models can be related to multiple instances of each other:

@Model
final class Student {
    var name: String = ""

    @Relationship(inverse: \Course.students)
    var courses: [Course] = []
}

@Model
final class Course {
    var title: String = ""

    @Relationship
    var students: [Student] = []
}

SwiftDataServer automatically creates and manages the join table (student_course) for many-to-many relationships.

Working with Relationships

Creating Related Objects

// Create an author with books
let author = Author(name: "Jane Austen")
let book1 = Book(title: "Pride and Prejudice")
let book2 = Book(title: "Sense and Sensibility")

book1.author = author
book2.author = author
// Or: author.books.append(contentsOf: [book1, book2])

context.insert(author)
context.insert(book1)
context.insert(book2)
try context.save()

Querying Related Objects

// Fetch author and access their books
let authors = try context.fetch(FetchDescriptor<Author>())
for author in authors {
    print("\(author.name) wrote \(author.books.count) books")
    for book in author.books {
        print("  - \(book.title)")
    }
}

Modifying Relationships

// Add a book to an author
let newBook = Book(title: "Emma")
newBook.author = author
context.insert(newBook)
try context.save()

// Remove a book from an author
book1.author = nil
try context.save()

// Delete an author (cascades to books if deleteRule is .cascade)
context.delete(author)
try context.save()

Lazy Loading

Relationships are lazy-loaded by default. The related objects are only fetched from the database when you first access them:

let author = try context.fetch(FetchDescriptor<Author>()).first!

// Books are NOT fetched yet
print(author.name)

// Books are fetched NOW when accessed
print(author.books.count)

Prefetching

For better performance when you know you'll need related objects, use prefetching:

let descriptor = FetchDescriptor<Author>()
    .prefetching(\Author.books)  // Fetch books in same query

let authors = try context.fetch(descriptor)
// Books are already loaded for all authors

Inverse Relationships

Always specify the inverse relationship to keep both sides in sync:

// When you set book.author = author
// author.books automatically includes the book

book.author = author
print(author.books.contains(book))  // true

Next Steps