NSMigrationManager.migrateStore with NSPersistentHistoryTrackingKey - core-data-migration

I have a core data implementation. The stack is loaded using a NSPersistentContainer. During setup, I set the NSPersistentHistoryTrackingKey on the NSPersistentStoreDescription.
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
I'm trying to implement progressive migrations along this line https://williamboles.me/progressive-core-data-migration/ (fantastic article, by the way!)
The first problem I ran into was forcing checkpointing in the WAL. The code is pretty straightforward:
func forceWALCheckpointingForStore(at storeURL: URL) {
guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL), let currentModel = NSManagedObjectModel.compatibleModelForStoreMetadata(metadata) else {
return
}
do {
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: currentModel)
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
let store = persistentStoreCoordinator.addPersistentStore(at: storeURL, options: options)
try persistentStoreCoordinator.remove(store)
} catch let error {
fatalError("failed to force WAL checkpointing, error: \(error)")
}
}
The problem occurs when the NSPersistentStoreCoordinator runs addPersistentStore. I get the following error:
Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store at url...
This makes perfect sense. The Core Data framework created additional tables (NSPersistentHistoryToken, NSPersistentHistoryTransaction etc..) that you have access to to manage changes in history. If you "open" or "access" the database without the history tracking option, the Core Data framework puts the database in Read Only mode to avoid data integrity issues.
As far as I can see in the documentation, the NSPersistentHistoryTrackingKey can only be set on the container, and not on the NSPersistentStoreCoordinator directly (via options).
Trying to stay "within the boundaries of the Container", I decided to call addPersistentStore (with the Pragmas Option) on the "persistentStoreCoordinator" property of the Container. The problem with this approach is that the container is instantiated with the latest version NSManagedObjectModel. Because we're smack bang in the middle of a migration process, the migration hasn't happened yet. Attempting to manipulate the store via the store coordinator inside the container in any way results in this error:
The model used to open the store is incompatible with the one used to create the store.
I had to therefore instantiate a container with the existing version of the model. Further research also revealed that I can force a WAL checkpoint via the NSPersistent container (and avoid having to use the coordinator) by setting the following option on the container description:
description.setValue("DELETE" as NSObject, forPragmaNamed: "journal_mode")
I created a temporary pre-migration container, instantiated it with the current (old) version of the managed object model, and set the above pragmas option to force a WAL checkpoint. It worked like a charm! The existing database was now ready to be migrated to the new model version(s).
The migration process kicks off and I hit a wall here:
NSMigrationManager.migrateStore(from: currentURL, sourceType: NSSQLiteStoreType, options: nil, with: mappingModel, toDestinationURL: destinationURL, destinationType: NSSQLiteStoreType, destinationOptions: nil)
Once again, we're back to the original problem:
Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store at url...
I want to migrate my database WITH the history tracking option enabled. After all, the history tracking tables created by the Core Data framework must also be migrated to the new version of the database. But, I don't know how to achieve this with the available Core Data classes. It is always best to stay as close to the vendor recommended implementations as possible and not do weird workarounds.
Here's what I know:
With lightweight migration options set on my Container, I can create new model versions to my heart's content!
With the NSPersistentHistoryTrackingKey also set on the same container, the Core Data framework automatically migrates my store from one model version to the next without missing a beat!
Therefore, if I now want to migrate my database manually with all the options set on the container, I should be able to do it because if Core Data can do it, I should be able to do it.... yes?
The documentation on these issues is a bit light. One of two things is happening here. Either the documentation is not updated OR ... I'm trying to do the weirdest thing known to man and it should never be done .... ever...

Related

Easy Core Data migration during early development, when Data Loss is expected and acceptable

When a Core Data app is in early development, its Managed Object Model is often in constant flux. For each build, new Managed Objects and properties are added or deleted from the model.
When the Managed Object Model changes, the app will crash on next run with the error:
The model used to open the store is incompatible with the one used to create the store
The common advice in this situation is to delete the app from your device/simulator and re-run.
This works fine for developers using Xcode, but is annoying for non-technical stakeholders involved in the release process. It would be much preferable to not explain to the CEO or QA team that they must delete the app before installing that update from TestFlight. Or to field crashing bugs caused by this issue.
Once the models have been finalized a bit, we'll implement a real Core Data migration strategy.
In this dev phase, data loss is acceptable and expected.
This method will be removed before the app is released.
What's the easiest, lightweight, removable, debug way to "migrate" changes to the Managed Object Model between releases? This will likely be equivalent to "delete the app and re-run", but without needing to manually delete the app.
This should handle any and all changes to the Core Data stack, including adding and deleting Managed Objects and Properties.
In this scenario, I would check compatibility against the current model and then delete the SQLite database if a migration would have been required.
Consider using (in Objective-C)
// error, sourceStoreURL, theManagedObjectModel are valid
NSDictionary *storeMetadata=[NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType
URL: sourceStoreURL error: &error];
BOOL storeIsCurrent=[theManagedObjectModel isConfiguration: nil
compatibleWithStoreMetadata: storeMetadata];
if (!storeIsCurrent)
{
// Alert user
// Delete on-disk store via sourceStoreURL
// (including -wal and -shm files if journaling enabled)
}
You can change the store URL when you change the model.
You can also do model versioning even for early development and then delete them all before you ship. This can also help your team learn the ins and outs of model versioning.

Command Line Tool Core Data Tutorial

I'm playing with Xcode and Objective-C/Cocoa again. This time I want to start with the bare minimum. I would like to setup Core Data in something as simple as a Command Line Tool (if it's even possible.) I need some practice saving and retrieving data without the all the View Controllers and AppDelegate stuff. Any ideas or maybe I'm going about this all wrong?
Your task will be the same as for any other project, except that the Command Line Tool template probably doesn't contain a Core Data option. No matter, you're doing this to learn, so setting up Core Data yourself won't hurt a bit.
Just create a new command line tool project and add a new Core Data model to the project. Next, you'll add the code that gets the model, creates a persistent store coordinator, uses those to create a managed object context. Then you can add objects, fetch objects, whatever you like.

MagicalRecord delete core data store when rebooting the application

I don't know why but MagicalRecord delete my store every time I reboot the application, I saw in the documentation :
When using the default sqlite data store with the DEBUG flag set, if you change your model without creating a new model version, Magical Record will delete the old store and create a new one automatically. No more uninstall/reinstall every time you make a change! (see magicalRecord github page
Removed the DEBUG flag,Tried to create a new model version, MR is still deleting my store...
Help me please :-)
This may be a bug. Please add an issue to the github repo. Or, if you need to get going faster, you can peek into the code, and comment out the line that checks to see if your model is compatible with your persistent store.

MVC3 code first: migrations and generating the database

I'm a bit lost how I should get the entity framework to work with automatic migration. I want:
The database to be created automatically when it doesnt exist
The database to be updated automatically when the model changed
For the latter I'm using DbMigrator. It is rather slow so I don't want to run it every request, and also I have multiple databases in the same application so it cant go in Application_Start which is why I put it in Session_Start like this:
if (Session["started"] == null)
{
// this takes care of any database updates that might be necessary.
MigrationConfiguration configuration = new MigrationConfiguration();
DbMigrator migrator = new DbMigrator(configuration);
List<string> pm = migrator.GetPendingMigrations().ToList();
if (pm.Count > 0)
{
migrator.Update();
}
}
else
{
Session["started"] = "started";
}
Not sure if this is the right way to do it but it seems to work, however it doesnt actually generate the database when it doesnt exist. It gives me a "Cannot open database "db" requested by the login"
I had this working before with the following:
Database.SetInitializer<DbContext>(new InitializerIfModelChange());
This drops the database and generates seed data which is fine for when the database doesnt exist but it also is triggers when the database is changed (in which case I would like DbMigrator to handle it) This was in Application_Start before but I'm not sure what to do with it. I'm afraid that it will conflict with the DbMigrator. How would I set this all up in order to achieve the two things described earlier?
I manually run the Update-Database in the package manager whenever the database needs to be changed. Initially I used the Database.SetInitializer like you did to create the database but have it commented out now.
Checkout Entity Framework 4.3 Automatic Migrations Walkthrough for more advanced help.
This should work for what you want, then if you need to create a new database just add Database.SetInitializer<NewDBContext>(new NewDBInitializer()); like you had, build and run. Then comment it out so it doesn't run in the future on a model change and instead use the Update-Database command in the package manager.

How to preserve compatibility when loading NSPersistentDocument(s) saved using a modified entity model?

I've created an OSX app that uses the entity model builder and its related stuff, for simplicity consider the entity with only two NSString.
The app is in production and works fine, now I need to add new attributes to the existing entity (only one entity exists), but with new attributes the old saved files are not open, silently the app does't open them and the console doesn't contain any error/warning message.
I need to load old saved files, consider all new attributes are optionals and have defaults (also in code not only in model design)
All existing attributes continue to be present I've only added the new attrs.
How can I design applications able to work when the entity model change?
From OS X 10.5 onwards, there is data migration functionality to help you, as long as you make changes to the data model in a new version.
There is a good basic explanation and example here: http://www.timisted.net/blog/archive/core-data-migration/
and the Apple documentation is here: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreDataVersioning/Introduction/Introduction.html

Resources