Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change Core Data Attribute Type from Integer32 to String

I've dealt with lightweight migration before as well as mapping basic changes, but I've googled and overflowed and haven't found a similar case where the entity name is staying the same, but the attribute type is changing from int to string (something I'd think would be easily done)

I thought I was on the right track by subclassing NSEntityMigrationPolicy, then I set the custom policy field in the mappingmodel to this subclass (it didn't autocomplete even after an Xcode restart...)

enter image description here

but I see that createDestinationInstancesForSourceInstance is not getting called enter image description here

Now, because I'm dealing with a custom mapping model and policy should I still have it inferring the mapping model but having Migrate Automatically off in the persistent store?

NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption:@NO,
                          NSInferMappingModelAutomaticallyOption:@YES};

Any help is greatly appreciated!

I was really hoping in the Attribute Mapping expression I'd be able to do something like $source.incrementer.string :)

Current Mapping

like image 706
Stu P. Avatar asked Apr 04 '14 18:04

Stu P.


2 Answers

You cannot use lightweight migration for this so it is a little bit harder (thanks Apple), but not impossible

step by step in Xcode 7.1:

1. Create the new model version:

  1. Select your .xcdatamodeld model -> Editor -> Add model version ...
  2. Edit the new model's entity's attribute type
  3. Change current model version to the new one (File inspector). You should see the green tick mark moved.

Current model

2. Custom Core Data mapping model

  1. New file -> Mapping Model (Core Data -> Mapping Model)
  2. Choose the source (from model) and target (to model) version of your model
  3. The custom mapping model contains entities mappings called this way: NameToName. Change value expression of the changed attribute in target entity in this file reflect the one you need to: FUNCTION($entityPolicy, "<*transformingMethodName*>" , $source.<*attributeName*>)

- transformingMethodName: Your custom method that will be called to transform attribute type. (Will define it in the next step - hold on)

- attributeName: your changed attribute name

  1. Create <*EntityName*>TransformationPolicy class as a subclass of NSEntityMigrationPolicy
  2. Implement transformingMethodName you defined above. (Do what you need there to change the attribute type). Make sure you added this method to your header file as well
  3. Register this class as custom entity migration policy in the mapping model (Model.xcmappingmodel -> File inspector -> third column -> Custom policy -> Enter the name of your TransformationPolicy class.)

Register migration policy class

like image 80
Jakub Truhlář Avatar answered Oct 17 '22 10:10

Jakub Truhlář


First, did you try a lightweight migration to see if it would solve this? As far as SQLite is concerned, it doesn't really care that you are changing a int to a varchar and should be able to do it painlessly.

As far as your mapping model, the options in the persistent store will automatically get overridden when Core Data finds a mapping model for that migration. Therefore you do not need to turn off those options.

As for heavy-weight migration, you need to implement several of the lifecycle methods to get it to work properly, implementing just -createDestinationInstancesForSourceInstance... is probably not sufficient for the class to be properly recognized and used. I would suggest stubbing out all of the methods with breakpoints and follow which ones get called. I have not done a heavy migration in a while so my memory of which methods are required is hazy.

Having said that, doing a heavy migration for this is a VERY expensive way to solve this issue. While it is the right way, it is really not the best way. I would consider doing something else (assuming a lightweight migration doesn't "just work"):

  • Give the string property a new name
    • Create a convenience override in your subclass that pulls the old int and sets the new string as it is being used; or
    • If you need them all, consider doing a migration test to determine if a migration is needed and then after the lightweight migration (which adds the new property) walk through the entities and set the int to string conversion manually.

I suggest these options because heavy weight migration is VERY heavy. It will create memory problems, especially if your data store is even remotely large. It will load up two copies of your data model into memory for the migration. Many iOS applications cannot handle that. It is also very slow and can cause launch issues.

like image 43
Marcus S. Zarra Avatar answered Oct 17 '22 11:10

Marcus S. Zarra