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
- Predicates - Query related objects
- Fetch Descriptors - Configure prefetching
- Defining Models - Back to model basics