Fetch Descriptors

FetchDescriptor configures how data is retrieved from the database, including filtering, sorting, and pagination.

Basic Usage

// Fetch all users
let descriptor = FetchDescriptor<User>()
let users = try context.fetch(descriptor)

// Or use the static property
let users = try context.fetch(FetchDescriptor<User>.all)

Filtering

// Initialize with predicate
let adults = #Predicate<User> { $0.age >= 18 }
let descriptor = FetchDescriptor<User>(predicate: adults)

// Or use builder pattern
let descriptor = FetchDescriptor<User>()
    .filter(adults)

Sorting

// Initialize with sort descriptors
let descriptor = FetchDescriptor<User>(
    sortBy: [SortDescriptor(propertyName: "name")]
)

// Or use builder pattern
let descriptor = FetchDescriptor<User>()
    .sorted(by: SortDescriptor(propertyName: "name"))

// Multiple sort criteria
let descriptor = FetchDescriptor<User>()
    .sorted(by: SortDescriptor(propertyName: "lastName"))
    .sorted(by: SortDescriptor(propertyName: "firstName"))

Pagination

// Manual limit and offset
let descriptor = FetchDescriptor<User>()
    .limit(20)
    .offset(40)  // Skip first 40

// Using pagination helper (1-indexed pages)
let page3 = FetchDescriptor<User>.all
    .paginated(page: 3, pageSize: 20)  // Items 41-60

Partial Fetching

Fetch only specific properties for better performance:

// Only fetch name and email columns
let descriptor = FetchDescriptor<User>()
    .fetching(\User.name, \User.email)

let users = try context.fetch(descriptor)
// user.name and user.email are populated
// Other properties have default values

Tip: Use partial fetching when displaying lists where you don't need all properties. This reduces data transfer and memory usage.

Relationship Prefetching

Eagerly load relationships to avoid N+1 queries:

// Fetch authors with their books in a single query
let descriptor = FetchDescriptor<Author>()
    .prefetching(\Author.books)

let authors = try context.fetch(descriptor)
for author in authors {
    // Books are already loaded - no additional query
    print("\(author.name): \(author.books.count) books")
}

Including Pending Changes

Control whether unsaved changes are included in results:

// Include unsaved insertions/modifications (default: true)
let descriptor = FetchDescriptor<User>()
    .includingPendingChanges(true)

// Only fetch from database, ignore pending changes
let descriptor = FetchDescriptor<User>()
    .includingPendingChanges(false)

Complete Example

// Complex query combining all features
let activeAdults = #Predicate<User> { user in
    user.age >= 18 && user.isActive == true
}

let descriptor = FetchDescriptor<User>()
    .filter(activeAdults)
    .sorted(by: SortDescriptor(propertyName: "createdAt", order: .reverse))
    .fetching(\User.name, \User.email, \User.createdAt)
    .paginated(page: 1, pageSize: 50)

let users = try context.fetch(descriptor)

Counting Results

// Count matching records without fetching them
let adults = #Predicate<User> { $0.age >= 18 }
let count = try context.fetchCount(
    FetchDescriptor<User>(predicate: adults)
)

Fetching Identifiers

// Fetch only IDs for lightweight operations
let ids = try context.fetchIdentifiers(
    FetchDescriptor<User>()
)

// Later, fetch full object when needed
let user: User? = try context.model(for: ids.first!)

Properties Summary

Property/Method Description
.filter(_:) Add a predicate filter
.sorted(by:) Add sort criteria
.limit(_:) Maximum number of results
.offset(_:) Number of results to skip
.paginated(page:pageSize:) Convenience for pagination
.fetching(_:) Partial fetch (specific properties)
.prefetching(_:) Eager load relationships
.includingPendingChanges(_:) Include unsaved changes

Next Steps