Manual migration of a non versioned Managed Object Model - iphone

This question title might look duplicate but please read through my problem as it is different but it is related to Core data migration.
I had uploaded the first version of my app on Apple store near about 2 months back and then uploaded second version last month.
Now the problem i am getting is that my newer version is getting crashed due to core data as new version includes addition of some attributes in the existing entities.
At that time i was unaware of such migration process but now i don't know what to do as my app is live.
I have to upload a new version which must have compatible version of core data.
Please guide me through this.
-(void)checkNeedForMigration{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ISB.sqlite"];
NSLog(#"database:%#",storeURL);
NSError *error = nil;
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *sourceMetadata =
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
NSManagedObjectModel *destinationModel=[psc managedObjectModel];
BOOL pscCompatibile = [destinationModel isConfiguration:Nil compatibleWithStoreMetadata:sourceMetadata];
NSManagedObjectModel *sourceModel=[NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata];
NSMappingModel *mappingModel =[NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:destinationModel];
NSMigrationManager *migrationManager=[[NSMigrationManager alloc]initWithSourceModel:sourceModel destinationModel:destinationModel];
NSDictionary *optionDic=[[NSDictionary alloc]initWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption,[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption, nil];
BOOL ok=[migrationManager migrateStoreFromURL:storeURL type:NSSQLiteStoreType options:optionDic withMappingModel:mappingModel toDestinationURL:storeURL destinationType:NSSQLiteStoreType destinationOptions:optionDic error:&error];}
Now getting null mappingModel...
Thanks
Pankaj

Related

How to migrate core data by deleting old one on App Update

Hi I'm going to update my iOS app in appstore and this update contains database change so now how to migrate my existing core data by deleting old database of existing version on App update?
I have referred Core Data Migration tutorial
Core Data Migration Post
Unfortunately of no use. Any help is appreciated in advance
Smith,
I presume you have done some schema changes, in the xcdatamodel
Always, Add a new Model Version (Select name.xcdatamodeld then Editor->Add model Version) before making any changes, if you have an app already submitted to App Store which is using the earlier model version.
Then,
Add a new file from Core Data Tab, as Mapping Model
Select, Source Model (Model Version which the submitted App is using)
Destination Model (Model Version in which you have done the Changes)
And you are done!
Is it possible you haven't created a new version of the DB model before you applied the auto migration?
Select [dbname].xcdatamodeld from project explorer.
Select Editor->Add model Version.
Select base it on your current model.
Make sure you have the automigrate option on like so:
-(NSPersistentStoreCoordinator *)storeCoordinator {
if (storeCoordinator_ != nil) {
return storeCoordinator_;
}
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"[dbname].sqlite"]];
NSDictionary* storeOptions = #{NSMigratePersistentStoresAutomaticallyOption : [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption : [NSNumber numberWithBool:YES]};
NSError *error = nil;
storeCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self objectModel]];
if (![storeCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:storeOptions error:&error]) {
// handle error here and remove abort
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return storeCoordinator_;
}
and away you go.

Why Does CoreData crash when I add an Attribute?

Everytime I add a new Attribute to my CodeData object model I have to clear my database file out otherwise I get the following error:
2010-11-13 15:26:44.580 MyApp[67066:207] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'myApp''
There must be a way of being able to add extra fields without losing the whole database.
What do I need to do to retain my data?
there is a way, and this way is called automatic lightweight migration. It needs a codechange and an extra step when changing your object model.
For the code you have to add two options to the method where you initialize your persistent store coordinator. something like this:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSString *storePath = [AppDelegate_Shared coredataDatabasePath];
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
// important part starts here
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
// and ends here
LogError(#"Unresolved error %#, %#", error, [error userInfo]);
// Do something
}
return persistentStoreCoordinator_;
}
Now if you want to change your model, you have to create a model version before you do any changes.
Select your datamodel, and go into the main menu Design -> Data Model -> Add Model Version. Your "old" model will be renamed and you make your changes in the current model, the one with the green mark.
All the old models are kept and will be put into your application, so your app can perform the 'automatic lightweight migration' and upgrade the existing database to your new model.
In addition to #Matthias Bauch's answer
for Xcode 12.3
Choose from the main menu Editor -> Add Model Version
To add mark the New Model as the current model with a green checkmark
Follow the below image

Possible to use two separate SQLite databases?

I have one sqlite database in which I store both user-defined information and information which is read-only to the user. I feel like I may need to modify the read-only information in the future, and I don't want to have to do a whole data migration. Is there a way that I can use a separate sqlite database, which can easily be replaced, for the read-only information? If so, can you give a little direction as to how this can be done? I am confused since I currently have all entities on the xcdatamodel - would I create two data models? Not sure how that would work. Thanks in advance.
This doesn't work but please feel free to give feedback.
- (NSManagedObjectModel *)managedObjectModel {
NSLog(#"%s", __FUNCTION__);
if (managedObjectModel != nil) {
return managedObjectModel;
}
//managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
NSString *mainPath = [[NSBundle mainBundle] pathForResource:#"MyApp" ofType:#"mom"];
NSURL *mainMomURL = [NSURL fileURLWithPath:mainPath];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:mainMomURL];
[managedObjectModel setEntities:[NSArray arrayWithObjects:
[[managedObjectModel entitiesByName] objectForKey:#"Version"],
[[managedObjectModel entitiesByName] objectForKey:#"Book"],
nil] forConfiguration:#"info"];
[managedObjectModel setEntities:[NSArray arrayWithObjects:
[[managedObjectModel entitiesByName] objectForKey:#"Settings"],
[[managedObjectModel entitiesByName] objectForKey:#"Persons"],
nil] forConfiguration:#"main"];
return managedObjectModel;
}
and
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
NSLog(#"%s", __FUNCTION__);
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Main.sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"Default" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSString *infoStorePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Info.sqlite"];
if (![fileManager fileExistsAtPath:infoStorePath]) {
NSString *defaultInfoStorePath = [[NSBundle mainBundle] pathForResource:#"DefaultInfo" ofType:#"sqlite"];
if (defaultInfoStorePath) {
[fileManager copyItemAtPath:defaultInfoStorePath toPath:infoStorePath error:NULL];
}
}
NSURL *infoStoreUrl = [NSURL fileURLWithPath:infoStorePath];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
//persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] init];
NSPersistentStore *mainStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"main" URL:storeUrl options:options error:&error];
NSPersistentStore *infoStore = [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"verses" URL:infoStoreUrl options:options error:&error];
NSManagedObject *settingsEntity = [[NSManagedObject alloc] initWithEntity:[[managedObjectModel entitiesByName] objectForKey:#"Settings"] insertIntoManagedObjectContext:self.managedObjectContext];
[self.managedObjectContext assignObject:settingsEntity toPersistentStore:mainStore];
NSManagedObject *persons = [[NSManagedObject alloc] initWithEntity:[[managedObjectModel entitiesByName] objectForKey:#"Persons"] insertIntoManagedObjectContext:self.managedObjectContext];
[self.managedObjectContext persons toPersistentStore:mainStore];
NSManagedObject *version = [[NSManagedObject alloc] initWithEntity:[[managedObjectModel entitiesByName] objectForKey:#"Version"] insertIntoManagedObjectContext:self.managedObjectContext];
[self.managedObjectContext assignObject:version toPersistentStore:infoStore];
NSManagedObject *book = [[NSManagedObject alloc] initWithEntity:[[managedObjectModel entitiesByName] objectForKey:#"Book"] insertIntoManagedObjectContext:self.managedObjectContext];
[self.managedObjectContext assignObject:book toPersistentStore:infoStore];
and
- (NSManagedObjectContext *)managedObjectContext {
NSLog(#"%s", __FUNCTION__);
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [NSManagedObjectContext new];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
Partial answer from docs:
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/CoreData/Articles/cdMOM.html
Configurations
A configuration has a name and an
associated set of entities. The sets
may overlap—that is, a given entity
may appear in more than one
configuration. You establish
configurations programmatically using
setEntities:forConfiguration: or using
the Xcode data modeling tool (see
Xcode Tools for Core Data), and
retrieve the entities for a given
configuration name using
entitiesForConfiguration:.
You typically use configurations if
you want to store different entities
in different stores. A persistent
store coordinator can only have one
managed object model, so by default
each store associated with a given
coordinator must contain the same
entities. To work around this
restriction, you can create a model
that contains the union of all the
entities you want to use. You then
create configurations in the model for
each of the subsets of entities that
you want to use. You can then use this
model when you create a coordinator.
When you add stores, you specify the
different store attributes by
configuration. When you are creating
your configurations, though, remember
that you cannot create cross-store
relationships.
Then NSPersistentStoreCoordinator allows you to create multiple stores each with a different configuration.
Anyone have an example of how to do all of this?
You can actually use a single data model to accomplish this, however you'll need to manually (in code) assign entities to different NSPersistentStore instances, a little bit of code:
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] init];
NSPersistentStore *userStore = [coord addPersistentStoreWithType:NSSQLiteStore configuration:nil URL:someFileURL options:someoptions error:&error];
NSPersistentStore *otherStore = [coord addPersistentStoreWithType:NSSQLiteStore configuration:nil URL:someFileURL2 options:someoptions error:&error];
//Now you use the two separate stores through a managed object context that references the coordinator
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coord];
NSManagedObject *userObject = [[NSManagedObject alloc] initWithEntity:entityDescFromModel insertIntoManagedObjectContext:context];
[context assignObject:userObject toPersistentStore:userStore];
NSManagedObject *otherObject = [[NSManagedObject alloc] initWithEntity:entityDescFromModel insertIntoManagedObjectContext:context];
[context assignObject:otherObject toPersistentStore:otherStore];
In this way you can always specify which store the objects are kept in. I don't think you'll have to do any extra work once the objects are in their respective stores, i.e. you should just be able to execute a fetch spec in the context that references the coordinator for both stores.
Well, here's what I ended up doing. Two managedObjectModels, two managedObjectContexts, two persistentStoreCoordinators, and hence, two persistent stores. All totally separate, which is fine, since there is no relationship between the data in the two stores at all. And here is the kicker as to why sqlite files get created with no entities and no data at all: before the entities even get created you need to execute at least one fetch request in the db. Who knew? Well, obviously, not me. Anyway, this works well for me, as I won't even have the second store ready until after the app is launched (it is for an additional feature). Now, when my data file is finally ready, I can just add the sqlite file, uncomment the code pertaining to it, and send the app to the app store. It won't touch the store with the user data in it. And, I am going to keep my read-only store in my bundle so no migration. How's that sound?
Ok, I found out how to add another data model. File>New File>Iphone OS>Resource>Data Model. Moved my Entities to that Data Model. Compiled and seems to run, but with no data. Problem is, still have just one sqlite file. Need to find out how to use two, and associate each with appropriate model. Then, should be able to overwrite default sqlite file for new model on app update. BUT I will still have to do a migration, I think, since it will have created a sqlite file on the iPhone from the default file I specify. It shouldn't be a hard migration I hope since I won't have any user data in the file. Learning, but still, any further assistance appreciated.

CoreData could not fulfill a fault when adding new attribute

I am receiving a "CoreData could not fulfill a fault for ..." error message when trying to access a new attribute in a new data model. If I work with new data I'm ok, but when I attempt to read existing data I get the error. Do I need to handle the entity differently myself if the attribute isn't in my original data? I was under the impression that Core Data could handle this for me. My new attribute is marked as optional with a default value.
I have created a new .xcdatamodel (and set it to be the current version) and updated my NSPersistentStoreCoordinator initialization to take advantage of the lightweight migration as follows:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Any help is appreciated.
UPDATE:
After more digging I've updated my managedObjectModel to:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
//managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
NSString *path = [[NSBundle mainBundle] pathForResource:#"< MyModel >" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}
This still hasn't resolved my problems. I've clean and rebuilt, but still no love.
How are you constructing your NSManagedObjectModel? If you are passing it a specific file that might be causing your issue as you may be loading the older, original mom file that is lingering around your project. Ideally you should be now loading the momd bundle or just loading all compiled models from your bundle using:
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
Which, if you receive an error, indicates that you need to clean your project to get rid of stale compiled models.
Update
Since it is not an old model issue, we move onto the next possibility. That the version is not set correctly in the plist. To check this, use finder or terminal and look inside of the momd bundle and open the plist therein. Check that to confirm that the new model is indeed set as the current version.
Assuming that does not work, next run your app in the simulator and have it save immediately upon creation of the MOC. After that, open the sqlite3 file using the command line tool and check the schema to see if it has updated to the new structure.
Assuming that is set correctly, are you using custom NSManagedObject subclasses?
Turns out that there was no problem with the versioning. I had some rather (too) complex logic which removed my object from the model and then I later tried to access it.
+1 to Marcus for the additional debugging pointers, they will no doubt come in handy at some point.

Problem with core data migration mapping model

I have an iphone app that uses Core Data to do storage. I have successfully deployed it, and now I'm working on the second version. I've run into a problem with the data model that will require a few very simple data transformations at the time that the persistent store gets upgraded, so I can't just use the default inferred mapping model.
My object model is stored in an .xcdatamodeld bundle, with versions 1.0 and 1.1 next to each other. Version 1.1 is set as the active version. Everything works fine when I use the default migration behavior and set NSInferMappingModelAutomaticallyOption to YES. My sqlite storage gets upgraded from the 1.0 version of the model, and everything is good except for, of course, the few transformations I need done.
As an additional experimental step, I added a new Mapping Model to the core data model bundle, and have made no changes to what xcode generated. When I run my app (with an older version of the data store), I get the following
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Object's persistent store is not reachable from this NSManagedObjectContext's coordinator'
What am I doing wrong? Here's my code for to get the managed object model and the persistent store coordinator.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"gti_store.sqlite"]];
NSError *error;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeUrl
options:options
error:&error]) {
NSLog(#"Eror creating persistent store coodinator - %#", [error localizedDescription]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectModel *)managedObjectModel {
if(_managedObjectModel == nil) {
_managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
NSDictionary *entities = [_managedObjectModel entitiesByName];
//add a sort descriptor to the 'Foo' fetched property so that it can have an ordering - you can't add these from the graphical core data modeler
NSEntityDescription *entity = [entities objectForKey:#"Foo"];
NSFetchedPropertyDescription *fetchedProp = [[entity propertiesByName] objectForKey:#"orderedBar"];
NSSortDescriptor* sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"index" ascending:YES] autorelease];
NSArray* sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[[fetchedProp fetchRequest] setSortDescriptors:sortDescriptors];
}
return _managedObjectModel;
}
I haven't thought this out very carefully, it's just an observation as I was having the same problem, and I too have found very few references to this error on the web.
In my case the problem was that I had setup one of my application's objects to observe
NSManagedObjectContextObjectsDidChangeNotification like so
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(observeContextSave:)
name:NSManagedObjectContextDidSaveNotification
object:nil];
- (void) observeContextSave:(NSNotification*) notification {
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];}
Once I moved this code so that is was executed after the migration, the error went away.
Anyway. I'm sure your circumstances are different. But it may help to think about what observations you have setup on notifications from your managedObjectContext.
Update: Having thought about this a bit more, I guess it happens because multiple persistent stores are created during migration, which means that NSManagedObjectContextDidSaveNotification will be sent from a context with a different persistent store to the context that is sent mergeChangesFromContextDidSaveNotification.
First, turn off the automatic migration if you are having a mapping model, fair chance they are colliding. Once you have done that, confirm that the error is gone.
I had similar issue when persistent store initialization was performed from secondary thread. After I forced initialization in primary thread the problem has gone. Weird.
Seems you already got it fixed, but worth to mention. I had this error too, the reason was I had multiple MOCs being notified of mergeChangesFromContextDidSaveNotification while they had different persistent stores (or different schemas). They didn't know how to handle changes that didn't belong to their stores.