Originally created by @Devenias on GitHub (Jan 30, 2025).
I have an issue with transactions and publishers.
When I update existing models in a .perform block with it's transaction context transaction.fetchOne the data is not correct after execution, still has the old data and does not trigger the publisher. So my UI never noticed a change, because it did not happen.
When I use DatabaseStack.instance.dataStack.fetchOne instead, I can edit the object and it also triggers the data update on the publisher. I'm I doing something wrong or is that a bug, that the transaction context is not fully merged into the main context?
I'm using CoreStore 9.2.0 because I have to support iOS 15.
I get some new data and update in an async transaction:
DatabaseStack.instance.dataStack.perform{transactioninfordatainnewData{letfetch=From<MyModel>().where(\.id==data.id)// this does NOT trigger updates in the publisherletdbModel_1:MyModel=ifletexisting=try?transaction.fetchOne(fetch),leteditable=transaction.edit(existing){editable}else{transaction.create(Into(MyModel.self))}dbModel_1.updateWith(entity:data)// this does trigger updates in the publisherletdbModel_2:MyModel=ifletexisting=try?DatabaseStack.instance.dataStack.fetchOne(fetch){existing}else{transaction.create(Into(MyModel.self))}dbModel_2.updateWith(entity:data)}}completion:{resultin...}
Originally created by @Devenias on GitHub (Jan 30, 2025).
I have an issue with transactions and publishers.
When I update existing models in a `.perform` block with it's transaction context `transaction.fetchOne` the data is not correct after execution, still has the old data and does not trigger the publisher. So my UI never noticed a change, because it did not happen.
When I use `DatabaseStack.instance.dataStack.fetchOne` instead, I can edit the object and it also triggers the data update on the publisher. I'm I doing something wrong or is that a bug, that the transaction context is not fully merged into the main context?
I'm using CoreStore 9.2.0 because I have to support iOS 15.
I get some new data and update in an async transaction:
```swift
DatabaseStack.instance.dataStack.perform { transaction in
for data in newData {
let fetch = From<MyModel>().where(\.id == data.id)
// this does NOT trigger updates in the publisher
let dbModel_1: MyModel = if
let existing = try? transaction.fetchOne(fetch),
let editable = transaction.edit(existing)
{
editable
} else {
transaction.create(Into(MyModel.self))
}
dbModel_1.updateWith(entity: data)
// this does trigger updates in the publisher
let dbModel_2: MyModel = if
let existing = try? DatabaseStack.instance.dataStack.fetchOne(fetch)
{
existing
} else {
transaction.create(Into(MyModel.self))
}
dbModel_2.updateWith(entity: data)
}
} completion: { result in
...
}
```
and I observe it like that:
```swift
let query = From<MyModel>().orderBy(.ascending(\.id))
return DatabaseStack.instance.dataStack
.publishList(query)
.reactive
.snapshot(emitInitialValue: true)
.tryMap { snapshot in
Mapper.toEntities(snapshot)
}
.eraseToAnyPublisher()
```
dataStack.perform does not wait for completion of dataStack.addStorage() and it's likely you are fetching earlier than the storage has been added to the DataStack
@JohnEstropia commented on GitHub (Feb 3, 2025):
`dataStack.perform` does not wait for completion of `dataStack.addStorage()` and it's likely you are fetching earlier than the storage has been added to the `DataStack`
Thanks for the response, but I use a singleton to make sure the DataStack is initialized properly and use .addStorageAndWait() in the apps func application(didFinishLaunchingWithOptions: ).
funcapplication(_application:UIApplication,didFinishLaunchingWithOptionslaunchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{print("Database version: \(DatabaseStack.instance.dataStack.modelVersion)")// ...}classDatabaseStack{staticletinstance=DatabaseStack()letdataStack=DataStack(xcodeModelName:"DataModel")privateinit(){initialize()}privatefuncinitialize(){do{trydataStack.addStorageAndWait(SQLiteStore(fileName:"MyDatabase.sqlite",localStorageOptions:[.allowSynchronousLightweightMigration,.recreateStoreOnModelMismatch]))}catchleterror{fatalError("Could not initialize database: \(error.localizedDescription)")}CoreStoreDefaults.dataStack=dataStackCoreStoreDefaults.logger=DatabaseLogger()}}
@Devenias commented on GitHub (Feb 3, 2025):
Thanks for the response, but I use a singleton to make sure the `DataStack` is initialized properly and use `.addStorageAndWait()` in the apps `func application(didFinishLaunchingWithOptions: )`.
```swift
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
print("Database version: \(DatabaseStack.instance.dataStack.modelVersion)")
// ...
}
class DatabaseStack {
static let instance = DatabaseStack()
let dataStack = DataStack(xcodeModelName: "DataModel")
private init() { initialize() }
private func initialize() {
do {
try dataStack.addStorageAndWait(
SQLiteStore(
fileName: "MyDatabase.sqlite",
localStorageOptions: [
.allowSynchronousLightweightMigration,
.recreateStoreOnModelMismatch
]
)
)
} catch let error {
fatalError("Could not initialize database: \(error.localizedDescription)")
}
CoreStoreDefaults.dataStack = dataStack
CoreStoreDefaults.logger = DatabaseLogger()
}
}
```
Are you running parallel updates on the same entities? I'd also check the following:
If the perform is not emitting errors
If the updates are actually in store (succeeding fetches reflect new values)
@JohnEstropia commented on GitHub (Feb 4, 2025):
Are you running parallel updates on the same entities? I'd also check the following:
- If the `perform` is not emitting errors
- If the updates are actually in store (succeeding fetches reflect new values)
The perform is not emitting any errors, I get a .success in the completion.
And when I use .fetchOne in the completion, the values are not updated in the result.
And yes, the updates might run in parallel. I started a new test project to isolate the issue and I can reproduce it. Then I tried to fix it using .perform(synchronous:) and a serial DispatchQueue.
serialQueue.async{do{tryDatabaseStack.instance.dataStack.perform(synchronous:{transactionin// ...})// in my existing project I tried to fetch immediately after the perform(synchronous:), but the data has not changed. }catch{// ...}}
It works in my test project and the updates are synchronised, the data is changed and the publisher triggered.
But when I want to apply the same approach to my existing project it does not work anymore (same behaviour as before). I have to figure out what the differences are.
Do you have any other idea how I can prevent parallel updates or synchronise them properly?
@Devenias commented on GitHub (Feb 6, 2025):
The `perform` is not emitting any errors, I get a `.success` in the `completion`.
And when I use `.fetchOne` in the `completion`, the values are not updated in the result.
And yes, the updates might run in parallel. I started a new test project to isolate the issue and I can reproduce it. Then I tried to fix it using `.perform(synchronous:)` and a serial `DispatchQueue`.
```swift
serialQueue.async {
do {
try DatabaseStack.instance.dataStack.perform(synchronous: { transaction in
// ...
})
// in my existing project I tried to fetch immediately after the perform(synchronous:), but the data has not changed.
} catch {
// ...
}
}
```
It works in my test project and the updates are synchronised, the data is changed and the publisher triggered.
But when I want to apply the same approach to my existing project it does not work anymore (same behaviour as before). I have to figure out what the differences are.
Do you have any other idea how I can prevent parallel updates or synchronise them properly?
remove the optional try? here and just call try normally?
try?transaction.fetchOne(fetch)
check if updateWith(entity:) method sets MyModel. id
@JohnEstropia commented on GitHub (Feb 10, 2025):
@Devenias Can you try these?
- remove the optional `try?` here and just call `try` normally?
```swift
try? transaction.fetchOne(fetch)
```
- check if `updateWith(entity:)` method sets `MyModel. id`
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @Devenias on GitHub (Jan 30, 2025).
I have an issue with transactions and publishers.
When I update existing models in a
.performblock with it's transaction contexttransaction.fetchOnethe data is not correct after execution, still has the old data and does not trigger the publisher. So my UI never noticed a change, because it did not happen.When I use
DatabaseStack.instance.dataStack.fetchOneinstead, I can edit the object and it also triggers the data update on the publisher. I'm I doing something wrong or is that a bug, that the transaction context is not fully merged into the main context?I'm using CoreStore 9.2.0 because I have to support iOS 15.
I get some new data and update in an async transaction:
and I observe it like that:
@JohnEstropia commented on GitHub (Feb 3, 2025):
dataStack.performdoes not wait for completion ofdataStack.addStorage()and it's likely you are fetching earlier than the storage has been added to theDataStack@Devenias commented on GitHub (Feb 3, 2025):
Thanks for the response, but I use a singleton to make sure the
DataStackis initialized properly and use.addStorageAndWait()in the appsfunc application(didFinishLaunchingWithOptions: ).@JohnEstropia commented on GitHub (Feb 4, 2025):
Are you running parallel updates on the same entities? I'd also check the following:
performis not emitting errors@Devenias commented on GitHub (Feb 6, 2025):
The
performis not emitting any errors, I get a.successin thecompletion.And when I use
.fetchOnein thecompletion, the values are not updated in the result.And yes, the updates might run in parallel. I started a new test project to isolate the issue and I can reproduce it. Then I tried to fix it using
.perform(synchronous:)and a serialDispatchQueue.It works in my test project and the updates are synchronised, the data is changed and the publisher triggered.
But when I want to apply the same approach to my existing project it does not work anymore (same behaviour as before). I have to figure out what the differences are.
Do you have any other idea how I can prevent parallel updates or synchronise them properly?
@JohnEstropia commented on GitHub (Feb 10, 2025):
@Devenias Can you try these?
try?here and just calltrynormally?updateWith(entity:)method setsMyModel. id