SwiftData crashes with @Query using major/minor sort descriptors

Using Xcode Version 15.0 beta (15A5160n) steps to reproduce:

  • Insert the code below into a new project
  • Build and run
  • First time, tap the “Setup” button to populate the SwiftData container.
  • All of the Attribute objects will be shown together sorted by
  • ‘Attribute.order’
  • Comment out the @Query with the single SortDescriptor and then uncomment the @Query with two SortDescriptor's.
  • Run the project again and it will crash with the following console message:

SwiftData/DataUtilities.swift:1179: Fatal error: Unexpected type for Expansion: Item

The expectation was for the Attributes to be listed in order within groups for each Item object, also in order by their Item.order property:

Item[0] Attribute[0]
Item[0] Attribute[1]
Item[0] Attribute[2]
Item[1] Attribute[0]
Item[1] Attribute[1]
Item[1] Attribute[2]
Item[2] Attribute[0]
Item[2] Attribute[1]
Item[2] Attribute[2]

I have filed a Feedback for this crash, which occurs when running this project on both the macOS Sonoma beta and the iOS 17.0 iPhone 14 Pro simulator.

Should this work? Is there a workaround? Is there an alternate method to accomplish the same results?

**** BTW, this same sorting pattern has worked well with Core Data in the past.

import SwiftUI
import SwiftData

@Model
final class Item {
    
    var name: String
    var order: Int
    @Relationship(.cascade) var attributes: [Attribute] = []
    
    init(name: String, order: Int) {
        self.name = name
        self.order = order
    }
}

@Model
final class Attribute {
    
    var name: String
    var order: Int
    @Relationship var item: Item

    init(item: Item, name: String, order: Int) {
        self.item = item
        self.name = name
        self.order = order
    }
}

struct ContentView: View {
    
    @Environment(\.modelContext) private var modelContext
    
    @Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.order, order: .forward)]))
    //    @Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.item.order, order: .forward),
    //                                               SortDescriptor(\Attribute.order, order: .forward)]))
    private var attributes: [Attribute]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(self.attributes) { attribute in
                    Text("Item[\(attribute.item.order)] Attribute[\(attribute.order)]")
                        .monospaced()
                }
            }
            .toolbar {
                ToolbarItem {
                    Button("Setup", action: self.setup)
                }
            }
        }
    }

    private func setup() {
        withAnimation {
            do {
                
                let attributeDescriptor = FetchDescriptor<Attribute>()
                let attributes = try self.modelContext.fetch(attributeDescriptor)
                for attribute in attributes {
                    self.modelContext.delete(attribute)
                }
                
                let itemDescriptor = FetchDescriptor<Item>()
                let items = try self.modelContext.fetch(itemDescriptor)
                for item in items {
                    self.modelContext.delete(item)
                }
                try self.modelContext.save()

                let item1 = Item(name: "Z", order: 0)
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).2", order: 2))
                self.modelContext.insert(item1)

                let item2 = Item(name: "Y", order: 1)
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).2", order: 2))
                self.modelContext.insert(item2)

                let item3 = Item(name: "X", order: 2)
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).2", order: 2))
                self.modelContext.insert(item2)

                try self.modelContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

}

Hey!

I've had the same exact problem causing my app to crash. The reason of it was because my relationship property (in your case "Item") was nil at the stage of fetch. Try inserting object with type "Item" to model context before inserting Attribute object. Also, there is no need to call "save()" on a Model Context if you have autosave enabled. (it is by default). Data is saved and UI is refreshed right after inserting an object to modelContext.

For example:

                let item1 = Item(name: "Z", order: 0)
                self.modelContext.insert(item1)
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).2", order: 2))

I can confirm that my original issue was fixed in beta 2 and continues to work in beta 3. Thank you Apple!

Hey Guys, I think I'm experiencing a similar sort of crash in my app. Here is my situation, I have a list of records and in one of my views I define the query like this: @Query(sort: \Record.bestScore, order: .reverse) private var records: [Record]

and down in the view I have a list that shows the records,

List(records, id: \.title) { rec in
...
}

In my case I have a relation in the Record that is defined as below,

@Relationship(deleteRule: .nullify, inverse: \RecordManager.relevantRecords)
        var topMatches: [RecordManager] = []

however, as you see, in my case the variable topMatches is not nil, it is initially set to an empty array (not sure if the .nullify deleteRule could be causing the error?). The problem is my code sometimes crash and when I check the stack trace it is on the line of List(records, id:.title). Any thoughts why this is happening?

SwiftData crashes with @Query using major/minor sort descriptors
 
 
Q