Can't find mapping model for migration - UIManagedDocument Core Data Migration
Asked Answered
H

6

13

I have two versions of my model Model001.xcdatamodel and Model002.xcdatamodel. These two are in the Model.xcdatamodeld bundle. I also have a Model001to002.xcmappingmodel which is not part of the Model.xcdatamodeld. I checked: both the xcmappingmodel and the xcdatamodeld get copied into the .app bundle.

My managed object context is initialized like this:

    NSURL *documentModel = [bundle URLForResource:@"Model" 
                                     withExtension:@"momd"]; managedObjectModel = [[NSManagedObjectModel alloc]
    initWithContentsOfURL:documentModel]; return managedObjectModel;

I also set these properties on my overridden initWithFileURL: in my UIManagedObject subclass.

    NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:self.persistentStoreOptions];
    [options setObject:@YES forKey:NSMigratePersistentStoresAutomaticallyOption];
    [options setObject:@YES forKey:NSInferMappingModelAutomaticallyOption];
    self.persistentStoreOptions = [options copy];

But when I try to open a documet, I get the following error: Can't find mapping model for migration

-- UPDATE --

Even if I do a manual migration

     [NSMappingModel mappingModelFromBundles:@[[NSBundle mainBundle]]
                              forSourceModel:sourceObjectModel
                            destinationModel:self.managedObjectModel];

this returns nil. Although I double checked that the Model001to002.cdm is in the app bundle. It has to be in the app bundle right?

Hagiocracy answered 8/10, 2012 at 13:16 Comment(2)
Hey Friend Have you Solved it ?Beggs
oh...ya,can you post method for performing migration..i just want see it.Beggs
H
6

OK, solved the problem by removing all core data files from Xcode, reading them and setting the source and destination of the mapping model again.

Damn you Xcode!

Hagiocracy answered 8/10, 2012 at 16:41 Comment(1)
having the same kind of problem did you just move the references or actually remove the files from the project?Fletcherfletcherism
T
27

A "gotcha" with mapping models is that you are not allowed to make any changes to the models after you created the mapping. If you do, you will also get this error.

Torritorricelli answered 1/2, 2013 at 13:19 Comment(1)
I removed the mapping model file and created a new one. Then the error disappeared.Goodard
H
6

OK, solved the problem by removing all core data files from Xcode, reading them and setting the source and destination of the mapping model again.

Damn you Xcode!

Hagiocracy answered 8/10, 2012 at 16:41 Comment(1)
having the same kind of problem did you just move the references or actually remove the files from the project?Fletcherfletcherism
A
4

You are not allowed to make any changes to the source/destination model after you have created the mapping models.

If you do make some changes,

  • mappingModelFromBundles:forSourceModel:destinationModel: will not be able to find the mapping model file
  • addPersistentStoreWithType:configuration:URL:options:error: with {NSInferMappingModelAutomaticallyOption: @NO} will report an error "Can't find mapping model for migration"
  • migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error: will report an error "Mismatch between mapping and source/destination models"

So, just recreate the mapping model and copy every change you made in the old one.

Alexine answered 17/9, 2014 at 9:34 Comment(0)
R
4

TL;DR

At least as of Xcode 8/9, open the mapping model then from the Editor menu select Refresh data models. Usually it seems you need to restart Xcode. If that doesn't do it you might try re-selecting the destination at the bottom of the model editor.

More Tips

Definitely NEVER change a model after it has been distributed in an app build.

For this example, let's say you have published Data Model 1 (DM1) and are making a migration to DM2. If you set DM2 as the active version then run your app, a migration will run on your persistent store. If you then make another change to DM2, run your app... Boom!

The issue is that your store has already been migrated to "DM2" but the data in the store doesn't fit into the model anymore. And, we can't migrate from DM2 to DM2 again.

It may seem like an obvious solution to go ahead and create DM3. It is usually a good idea though to minimize the number of models and migrations while you are developing.

So... now you have a persistent store that has been migrated to a defunct DM2. How do you test the migration again? You could revert your app and generate some data with DM1 but I prefer to use backups

Creating a backup

Before you run your app with DM2 you can copy the existing store (with DM1) to use for later test migrations. On macOS you can easily do this manually. The code below should do the trick as well. Typically you wouldn't want to ship this, rather you could just put it somewhere before your normal CD stack opens, run the app, then stop the app (maybe place a breakpoint just after then end the run via Xcode).

let fm = FileManager.default
let url = // The store URL you would use in ↓
// try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)

let dir = url.deleteLastPathComponent().appendingPathComponent("Backup", isDirectory: true).appendingPathComponent("DM1", isDirectory: true)
print("Saving DB backup for DM1")
if !fm.fileExists(atPath: dir.path) {
    do {
        // Create a directory    
        try fm.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil)

        let backupURL = dir.appendingPathComponent(url.lastPathComponent)
        try fm.copyItem(at: url, to: backupURL)
    }
    catch {
        print("Failed to save DB backup")
    }
}

Oops, I need to make another change...

If you run your migration to DM2 then realize you need to make another change, you'll want to re-test your migration from DM1 -> DM2. This is where the backup comes in.

Same way you made the backup, run this code.

let fm = FileManager.default
let url = // The store URL you would use to add the store
let dir = url.deleteLastPathComponent().appendingPathComponent("Backup", isDirectory: true).appendingPathComponent("DM1", isDirectory: true)
let backupURL = dir.appendingPathComponent(url.lastPathComponent)

if fm.fileExists(atPath: backupURL.path) {
    do {
        fm.removeItem(at: url.path)
        try fm.copyItem(at: backupURL, to: url)
    }
    catch {
        print("Failed to restore DB backup")
    }
}

You now have a restored DM1 store and have made changes to DM2. If you run the app the migration might succeed but it won't use your custom mapping model.

Remember if you are using a custom mapping, you will still need to use the Refresh Data Models technique before the mapping model will work.

Rowdyish answered 19/9, 2017 at 20:16 Comment(0)
W
0

This can happen if your test device's store is from a version of the data model that no longer exists.

For example I had Data Model Version 7, then I made Data Model Version 8. I made a mapping model to go from 7 to 8. Then I ran it on my test device and everything was happy.

Then I made some more changes to 8.

The thing to realize is that in Core Data, every model has a hash identifier that the system creates by taking a checksum of the xcdatamodel file. So if you make even a slight change, even if you didn't create a new version, it sees it as a different version. These versions' identifiers are NSStoreModelVersionHashes (see documentation here).

So in other words, I ended up with:

Data Model 7 (release) - 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=

Data Model 8 (beta)    - qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=

Data Model 8 (release) - EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=

Instead of making a version 9, and saving the original version 8 in the data model history, I just updated 8, figuring automatic migration could take care of me. Well, it couldn't, and I couldn't make a mapping between the two, because the old (beta) version of 8 was gone.

I did it that way because it was an intermediary internal build (not a release) so it wasn't a big deal, but it did throw me for a loop!

If it wasn't an internal build and I needed to make this work, I could go back to the (beta) commit and pull out that xcdatamodel file for 8 (beta), rename the (release) version to 9, then stick it into the release build and make a mapping model between 8 and 9.

However since it was just an internal beta build, we just erased and reinstalled the app on test devices. We did verify that, when going from 7 (release) to 8 (release), the migration went smoothly.

Wallet answered 6/7, 2016 at 2:55 Comment(0)
T
0

Removing Coredata files from its path an re - run project is worked for me

Tapia answered 1/7, 2022 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.