Mapping model not found if the attribute has "Preserve after deletion" enabled

I am trying to migrate my Core Data model to a new version with a new attribute added to it. Since my app supports macOS 13 I am not able to use the newly introduced Staged migrations.

After much digging I found that the app is not able to find the Mapping Model when one of the attribute has "Preserve after deletion" enabled.

I have enabled migration debbuging using

com.apple.CoreData.MigrationDebug 1

I am getting following error

error: CoreData: error: (migration) migration failed with error Error Domain=NSCocoaErrorDomain Code=134140 "Persistent store migration failed, missing mapping model."

What is the way out here?

Answered by DTS Engineer in 824976022

You made it clear that you probably couldn't move away from custom mapping models, but for the awareness of other folks, I'd still mention that using custom mapping models is now discouraged, as discussed here.

To to comment your question, every time you change your model version, you need to regenerate the model mapping file so the version hashes are matched. You can do so by executing Xcode File > New > File From Tempdate... menu, pick Core Data > Mapping Model in the new file template sheet and then follow the guide to finish that.

If you do re-create the mapping model after changing the model, but still see the “can't find mapping model for migration" error, it may be that Xcode fails to create the mapping model with the right hashes, which will be an Xcode bug.

In that case, you can probably work around the issue by tweaking the mapping model with your own code. To do that:

  1. Figure out the version hashes of the entities in your source and destination Core Data model versions. You can use Core Data APIs (NSManagedObjectModel > NSEntityDescription > versionHash) to dump all the hashes.

  2. Set sourceEntityVersionHash and destinationEntityVersionHash for the entity mappings in your mapping model.

  3. Use migrateStore(from:type:options:mapping:to:type:options:) to trigger the migration.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Found a similar issue on forums https://developer.apple.com/forums/thread/120013

Accepted Answer

You made it clear that you probably couldn't move away from custom mapping models, but for the awareness of other folks, I'd still mention that using custom mapping models is now discouraged, as discussed here.

To to comment your question, every time you change your model version, you need to regenerate the model mapping file so the version hashes are matched. You can do so by executing Xcode File > New > File From Tempdate... menu, pick Core Data > Mapping Model in the new file template sheet and then follow the guide to finish that.

If you do re-create the mapping model after changing the model, but still see the “can't find mapping model for migration" error, it may be that Xcode fails to create the mapping model with the right hashes, which will be an Xcode bug.

In that case, you can probably work around the issue by tweaking the mapping model with your own code. To do that:

  1. Figure out the version hashes of the entities in your source and destination Core Data model versions. You can use Core Data APIs (NSManagedObjectModel > NSEntityDescription > versionHash) to dump all the hashes.

  2. Set sourceEntityVersionHash and destinationEntityVersionHash for the entity mappings in your mapping model.

  3. Use migrateStore(from:type:options:mapping:to:type:options:) to trigger the migration.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thank you for your response. The workaround you mentioned may work, however, the step #3 of calling the migrateStore function is extremely complicated. The documents also does not explain much.

Some of the points that I can think of is

  1. Should the migration be called on the background context.
  2. Where should be the new temporary destination file be created?
  3. Are there any other functions that I should invoke before and after migration?
  4. Should I explicitly delete the temporary file if migration fails.

It would be great if you can share a resource with code sample that explains how to use the migrateStore method.

I haven't had a chance to really verify my theory in a real app, and so don't have a code example to share. What I tried is like below to tweak the model mapping so it matches the model versions:

guard let fileURL = Bundle.main.url(forResource: "Model", withExtension: "xcmappingmodel"),
      let mappingModel = NSMappingModel(contentsOf: fileURL),
      let newEntityMappings = mappingModel.entityMappings else {
    return
}

for entityMapping in newEntityMappings {
    guard let sourceEntityName =  entityMapping.sourceEntityName,
          let destinationEntityName = entityMapping.destinationEntityName else {
        continue
    }
    entityMapping.sourceEntityVersionHash = sourceModel.entityVersionHashesByName[sourceEntityName]
    entityMapping.destinationEntityVersionHash = destinationModel.entityVersionHashesByName[destinationEntityName]
}
mappingModel.entityMappings = newEntityMappings

do {
    try migrationManager.migrateStore(from: sourceStoreURL,
                                      sourceType: sourceStoreType,
                                      options: nil,
                                      with: mappingModel,
                                      toDestinationURL: destinationStoreURL,
                                      destinationType: destinationStoreType,
                                      destinationOptions: nil
    )
} catch let error {
    print(error) // Error handling.
}

I don't have more specific comment on your following-up questions either, except that you don't need a managed object context to use NSMigrationManager and run migrateStore(from:type:options:mapping:to:type:options:).

I guess this is the point where I'd suggest that you go ahead to play with the API. If you see anything strange related to the APIs and don't mind to share here, I may be able to take a closer look.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thank you for sharing the code snippet.

  • Can you please update the code that demonstrates initialization of following variables for the sake of completeness of the code

    • sourceModel
    • destinationModel
    • sourceStoreURL
    • destinationStoreURL
  • Also, if you could show how to replace the newly created destinationStore with sourceStore. Are there any edge cases that are to be handled? Should I manually delete the wal and shm files?

guard let fileURL = Bundle.main.url(forResource: "Model", withExtension: "xcmappingmodel")

  • For the snippet above, should I use xcmappingmodel or cdm extension? After compiling, a cdm file is created from a MappingModel

Is it necessary to manually checkpoint the WAL journal before starting the migration or does the NSMigrationManager handles it internally?

Ref: https://developer.apple.com/library/archive/qa/qa1809/_index.html

Mapping model not found if the attribute has "Preserve after deletion" enabled
 
 
Q