Post

Replies

Boosts

Views

Activity

Reply to SwiftData regression in iOS 27.2: inverse relationship not set on children when an uninserted model graph is attached to a persisted model
import SwiftData // A: DTS forum code — to-many arrays default to [] @Model final class LedgerA { @Relationship(deleteRule: .cascade, inverse: \TransactionA.ledger) var transactions: [TransactionA] = [] init() {} } @Model final class TransactionA { var ledger: LedgerA? @Relationship(deleteRule: .cascade, inverse: \ItemA.transaction) var items: [ItemA] = [] init(items: [ItemA]) { self.items = items } } @Model final class ItemA { var transaction: TransactionA?; init() {} } // B: same, but no `= []` default @Model final class LedgerB { @Relationship(deleteRule: .cascade, inverse: \TransactionB.ledger) var transactions: [TransactionB] init() { transactions = [] } } @Model final class TransactionB { var ledger: LedgerB? @Relationship(deleteRule: .cascade, inverse: \ItemB.transaction) var items: [ItemB] init(items: [ItemB]) { self.items = items } } @Model final class ItemB { var transaction: TransactionB?; init() {} } // C: optional to-many arrays @Model final class LedgerC { @Relationship(deleteRule: .cascade, inverse: \TransactionC.ledger) var transactions: [TransactionC]? init() { transactions = [] } } @Model final class TransactionC { var ledger: LedgerC? @Relationship(deleteRule: .cascade, inverse: \ItemC.transaction) var items: [ItemC]? init(items: [ItemC]) { self.items = items } } @Model final class ItemC { var transaction: TransactionC?; init() {} } @MainActor func runInverseRepro() throws { let config = ModelConfiguration(isStoredInMemoryOnly: true) // A — default [] let contextA = ModelContext(try ModelContainer(for: LedgerA.self, configurations: config)) contextA.insert(LedgerA()) try contextA.save() if let ledger = try contextA.fetch(FetchDescriptor<LedgerA>()).first { let items = [ItemA(), ItemA()] ledger.transactions.append(TransactionA(items: items)) // transaction not inserted print("A (= []) nil inverses before save:", items.filter { $0.transaction == nil }.count) } // B — no default let contextB = ModelContext(try ModelContainer(for: LedgerB.self, configurations: config)) contextB.insert(LedgerB()) try contextB.save() if let ledger = try contextB.fetch(FetchDescriptor<LedgerB>()).first { let items = [ItemB(), ItemB()] ledger.transactions.append(TransactionB(items: items)) print("B (no []) nil inverses before save:", items.filter { $0.transaction == nil }.count) } // C — optional let contextC = ModelContext(try ModelContainer(for: LedgerC.self, configurations: config)) contextC.insert(LedgerC()) try contextC.save() if let ledger = try contextC.fetch(FetchDescriptor<LedgerC>()).first { let items = [ItemC(), ItemC()] ledger.transactions?.append(TransactionC(items: items)) print("C (optional) nil inverses before save:", items.filter { $0.transaction == nil }.count) } }Same code 27.2 A (= []) nil inverses before save: 0 B (no []) nil inverses before save: 2 C (optional) nil inverses before save: 027.0 A (= []) nil inverses before save: 0 B (no []) nil inverses before save: 0 C (optional) nil inverses before save: 0
1d
Reply to SwiftData regression in iOS 27.2: inverse relationship not set on children when an uninserted model graph is attached to a persisted model
Thanks for testing. I found the difference: my forum snippet declared the to-many arrays with a default (= []), which our real models don't. With the default, 27.2 works, matching your result. Without it, the inverse is never set: @Model final class Ledger { @Relationship(deleteRule: .cascade, inverse: \Transaction.ledger) var transactions: [Transaction] // no = [] init(transactions: [Transaction]) { self.transactions = transactions } } @Model final class Transaction { var ledger: Ledger? @Relationship(deleteRule: .cascade, inverse: \Item.transaction) var items: [Item] // no = [] init(items: [Item]) { self.items = items } } @Model final class Item { var transaction: Transaction? init() {} } ```
1d
Reply to SwiftData regression in iOS 27.2: inverse relationship not set on children when an uninserted model graph is attached to a persisted model
Thanks for trying it. Your test differs from mine in two ways that I only mentioned in a code comment (// Inside a @ModelActor; ledger is fetched and already saved), so let me make them explicit: Background context. The write runs inside a @ModelActor, not on the main context. The parent is fetched, not reused. The ledger is saved first, then fetched again in the actor's context before the append. In your test it's the instance you just inserted. On-disk store, checked after reopening. After save() I open the store in a new ModelContainer and count items whose transaction is nil. Feedback: FB25355425 (includes a standalone repro project, reports from iOS 27.0 and 27.2, and a sysdiagnose)
3d
Reply to SwiftData regression in iOS 27.2: inverse relationship not set on children when an uninserted model graph is attached to a persisted model
import SwiftData // A: DTS forum code — to-many arrays default to [] @Model final class LedgerA { @Relationship(deleteRule: .cascade, inverse: \TransactionA.ledger) var transactions: [TransactionA] = [] init() {} } @Model final class TransactionA { var ledger: LedgerA? @Relationship(deleteRule: .cascade, inverse: \ItemA.transaction) var items: [ItemA] = [] init(items: [ItemA]) { self.items = items } } @Model final class ItemA { var transaction: TransactionA?; init() {} } // B: same, but no `= []` default @Model final class LedgerB { @Relationship(deleteRule: .cascade, inverse: \TransactionB.ledger) var transactions: [TransactionB] init() { transactions = [] } } @Model final class TransactionB { var ledger: LedgerB? @Relationship(deleteRule: .cascade, inverse: \ItemB.transaction) var items: [ItemB] init(items: [ItemB]) { self.items = items } } @Model final class ItemB { var transaction: TransactionB?; init() {} } // C: optional to-many arrays @Model final class LedgerC { @Relationship(deleteRule: .cascade, inverse: \TransactionC.ledger) var transactions: [TransactionC]? init() { transactions = [] } } @Model final class TransactionC { var ledger: LedgerC? @Relationship(deleteRule: .cascade, inverse: \ItemC.transaction) var items: [ItemC]? init(items: [ItemC]) { self.items = items } } @Model final class ItemC { var transaction: TransactionC?; init() {} } @MainActor func runInverseRepro() throws { let config = ModelConfiguration(isStoredInMemoryOnly: true) // A — default [] let contextA = ModelContext(try ModelContainer(for: LedgerA.self, configurations: config)) contextA.insert(LedgerA()) try contextA.save() if let ledger = try contextA.fetch(FetchDescriptor<LedgerA>()).first { let items = [ItemA(), ItemA()] ledger.transactions.append(TransactionA(items: items)) // transaction not inserted print("A (= []) nil inverses before save:", items.filter { $0.transaction == nil }.count) } // B — no default let contextB = ModelContext(try ModelContainer(for: LedgerB.self, configurations: config)) contextB.insert(LedgerB()) try contextB.save() if let ledger = try contextB.fetch(FetchDescriptor<LedgerB>()).first { let items = [ItemB(), ItemB()] ledger.transactions.append(TransactionB(items: items)) print("B (no []) nil inverses before save:", items.filter { $0.transaction == nil }.count) } // C — optional let contextC = ModelContext(try ModelContainer(for: LedgerC.self, configurations: config)) contextC.insert(LedgerC()) try contextC.save() if let ledger = try contextC.fetch(FetchDescriptor<LedgerC>()).first { let items = [ItemC(), ItemC()] ledger.transactions?.append(TransactionC(items: items)) print("C (optional) nil inverses before save:", items.filter { $0.transaction == nil }.count) } }Same code 27.2 A (= []) nil inverses before save: 0 B (no []) nil inverses before save: 2 C (optional) nil inverses before save: 027.0 A (= []) nil inverses before save: 0 B (no []) nil inverses before save: 0 C (optional) nil inverses before save: 0
Replies
Boosts
Views
Activity
1d
Reply to SwiftData regression in iOS 27.2: inverse relationship not set on children when an uninserted model graph is attached to a persisted model
Thanks for testing. I found the difference: my forum snippet declared the to-many arrays with a default (= []), which our real models don't. With the default, 27.2 works, matching your result. Without it, the inverse is never set: @Model final class Ledger { @Relationship(deleteRule: .cascade, inverse: \Transaction.ledger) var transactions: [Transaction] // no = [] init(transactions: [Transaction]) { self.transactions = transactions } } @Model final class Transaction { var ledger: Ledger? @Relationship(deleteRule: .cascade, inverse: \Item.transaction) var items: [Item] // no = [] init(items: [Item]) { self.items = items } } @Model final class Item { var transaction: Transaction? init() {} } ```
Replies
Boosts
Views
Activity
1d
Reply to SwiftData regression in iOS 27.2: inverse relationship not set on children when an uninserted model graph is attached to a persisted model
Thanks for trying it. Your test differs from mine in two ways that I only mentioned in a code comment (// Inside a @ModelActor; ledger is fetched and already saved), so let me make them explicit: Background context. The write runs inside a @ModelActor, not on the main context. The parent is fetched, not reused. The ledger is saved first, then fetched again in the actor's context before the append. In your test it's the instance you just inserted. On-disk store, checked after reopening. After save() I open the store in a new ModelContainer and count items whose transaction is nil. Feedback: FB25355425 (includes a standalone repro project, reports from iOS 27.0 and 27.2, and a sysdiagnose)
Replies
Boosts
Views
Activity
3d