i'm trying to archive a lightweight migration with some handling after that. I already did the lightweight process and i need some help now handling my entities.
On the old model i used to have an entity "Car" and now i added the entity "Person" with the relationship Person has Cars.
So, after the lightweight migration i need to add a default person "John" and add all cars to him.
Does anyone have some idea?
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CoreData.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
You can catch if a lightweight migration is going to occur. See this answer for details. There you can set a flag and based on that execute a method after normal startup in which you insert the desired entities.
Notice, though, that lightweight migration should migrate all your existing entities to the new store version, so there is usually no logically compelling reason to use this hook. Instead, you cold just query your (new or old version) store if it contains "John" and his cars and insert them if not.
If this is a one-time upgrade to the database, it might make sense to use a custom migration policy class rather than lightweight migration. Here's a nice tutorial on customizing that process:
http://9elements.com/io/index.php/customizing-core-data-migrations/
If you want to then mix inferred migration from one pair of models with custom migration between another pair of models, I've written a description of the Core Data methods involved and sample iterative migration class.
Related
I am attempting to create an iOS Today Extension. I would like to connect to the main apps CoreData SQL DB. However i am receiving this error when i attempt to connect.
"The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary"
Accessing Core Data SQL Database in iOS 8 Extension
The creation of the Database happens and I am able to insert records etc.
My extension controller code now is using similar code to the CoreData code in the app delegate.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
// -- Changed for Today Screen --//
//NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
// stringByAppendingPathComponent: #"CoreDB_2014.sqlite"]];
NSURL *storeUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.THISAPP.APPNAME"];
NSLog(#"StoreURL1: %#", storeUrl);
storeUrl = [storeUrl URLByAppendingPathComponent:#"CoreDB_2014A.sqlite"];
NSLog(#"StoreURL2: %#", storeUrl);
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
}
return persistentStoreCoordinator;
}
Any help would be appreciated.
Thanks
That error is pretty self-explanatory: it means that you changed the Core Data model so that it no longer matches the one used to create the persistent store file that you're trying to open. Those have to match. If you change the model, you need to either (a) use more than one model version and perform a migration to the new model, or (b) use a different persistent store file (or delete the existing one and start over).
Core Data models often change while an app is being developed. In most cases, for a pre-release app, the developer will use option (b) and delete previous test data. If that's not feasible, you'll need to do model versioning and migration to update the data store in place.
Is there any delegate method that will be called when the user upgrades to or reinstalls a newer version of the iOS app?
I use Core Data to cache some information from server. When the schema of any entity is changed, I need to manually delete the SQLite database from the simulator, otherwise the app will crash on startup, with an error "The model used to open the store is incompatible with the one used to create the store." If there is any delegate method for app upgrade, the deletion could be automated.
You need to use CoreData versioning:
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreDataVersioning/Articles/Introduction.html
Daniel Smith's answer is the proper one, but I just want to add how my app determines its been updated. I look keep a 'current version' string in the defaults. When the app starts up, I compare it to the current version:
defaults has no string - this is the first run of the app
defaults version is different - the user updated the app
defaults is the same - user just restarted the app
Sometimes its nice to know the above. Make sure to save the defaults immediately after you set the tag and do whatever versioning you want, so a crash doesn't have you do it again.
EDIT: how not to crash if he model changes. I use this now, keep the old repository, and tweaking the model, on every tweak it just removes the old one (if it cannot open it) and creates a new one. This is modeled on Apple's code but not sure about what changes I made. In any case you don't get a crash if the model changes.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
//LTLog(#"_persistentStoreCoordinator = %#", _persistentStoreCoordinator);
if (_persistentStoreCoordinator)
{
return _persistentStoreCoordinator;
}
NSFileManager *manager = [NSFileManager defaultManager];
NSString *path = [[appDelegate applicationAppSupportDirectory] stringByAppendingPathComponent:[_dbName stringByAppendingPathExtension:#"SQLite"]];
storeURL = [NSURL fileURLWithPath:path];
BOOL fileExists = [manager fileExistsAtPath:path];
if(!fileExists) {
_didCreateNewRepository = YES;
}
if(_createNewRepository) {
[manager removeItemAtURL:storeURL error:nil];
if(fileExists) _didDestroyOldRepository = YES;
_didCreateNewRepository = YES;
}
while(YES) {
__autoreleasing NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if ([_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
break;
} else {
_persistentStoreCoordinator = nil;
[manager removeItemAtURL:storeURL error:&error];
if(fileExists) {
_didDestroyOldRepository = YES; // caller didn't want a new one but got a new one anyway (old one corrupt???)
_didCreateNewRepository = YES;
}
#ifndef NDEBUG
LTLog(#"CORE DATA failed to open store %#: error=%#", _dbName, error);
#endif
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
//LTLog(#"Unresolved error %#, %#", error, [error userInfo]);
//abort();
}
}
return _persistentStoreCoordinator;
}
Follow the blog its good:
http://blog.10to1.be/cocoa/2011/11/28/core-data-versioning/
I'm doing operations in a GCD dispatch queue on a NSManagedObjectContext defined like this:
- (NSManagedObjectContext *)backgroundContext
{
if (backgroundContext == nil) {
self.backgroundContext = [NSManagedObjectContext MR_contextThatNotifiesDefaultContextOnMainThread];
}
return backgroundContext;
}
MR_contextThatNotifiesDefaultContextOnMainThread is a method from MagicalRecord:
NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[NSManagedObjectContext MR_defaultContext]];
return context;
After fetching my objects and giving them the correct queue position i log them and the order is correct. However, the second log seems to be completely random, the sort descriptor clearly isn't working.
I have narrowed down the Problem to [self.backgroundContext save:&error]. After saving the background context sort descriptors are broken.
dispatch_group_async(backgroundGroup, backgroundQueue, ^{
// ...
for (FooObject *obj in fetchedObjects) {
// ...
obj.queuePosition = [NSNumber numberWithInteger:newQueuePosition++];
}
NSFetchRequest *f = [NSFetchRequest fetchRequestWithEntityName:[FooObject entityName]];
f.predicate = [NSPredicate predicateWithFormat:#"queuePosition > 0"];
f.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"queuePosition" ascending:YES]];
NSArray *queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
for (FooObject *obj in queuedObjects) {
DLog(#"%# %#", obj.queuePosition, obj.title);
}
if ([self.backgroundContext hasChanges]) {
DLog(#"Changes");
NSError *error = nil;
if ([self.backgroundContext save:&error] == NO) {
DLog(#"Error: %#", error);
}
}
queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
for (FooObject *obj in queuedObjects) {
DLog(#"%# %#", obj.queuePosition, obj.title);
}
});
I've got no idea why the sort descriptor isn't working, any Core Data experts want to help out?
Update:
The problem does not occur on iOS 4. I think the reason is somewhere in the difference between thread isolation and private queue modes. MagicalRecord automatically uses the new concurrency pattern which seems to behave differently.
Update 2:
The problem has been solved by adding a save of the background context:
if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
DLog(#"Changes");
NSError *error = nil;
if ([[NSManagedObjectContext MR_contextForCurrentThread] save:&error] == NO) {
DLog(#"Error: %#", error);
} else {
NSManagedObjectContext *parent = [NSManagedObjectContext MR_contextForCurrentThread].parentContext;
[parent performBlockAndWait:^{
NSError *error = nil;
if ([parent save:&error] == NO) {
DLog(#"Error saving parent context: %#", error);
}
}];
}
}
Update 3:
MagicalRecord offers a method to recursively save a context, now my code looks like this:
if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
DLog(#"Changes");
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveWithErrorHandler:^(NSError *error) {
DLog(#"Error saving context: %#", error);
}];
}
Shame on me for not using it in the first place...
However, I don't know why this helps and would love to get an explanation.
I'll try to comment, since I wrote MagicalRecord.
So, on iOS5, MagicalRecord is set up to try to use the new Private Queue method of multiple managed object contexts. This means that a save in the child context only pushes saves up to the parent. Only when a parent with no more parents saves, does the save persist to its store. This is probably what was happening in your version of MagicalRecord.
MagicalRecord has tried to handle this for you in the later versions. That is, it would try to pick between private queue mode and thread isolation mode. As you found out, that doesn't work too well. The only truly compatible way to write code (without complex preprocessor rules, etc) for iOS4 AND iOS5 is to use the classic thread isolation mode. MagicalRecord from the 1.8.3 tag supports that, and should work for both. From 2.0, it'll be only private queues from here on in.
And, if you look in the MR_save method, you'll see that it's also performing the hasChanges check for you (which may also be unneeded since the Core Data internals can handle that too). Anyhow, less code you should have to write and maintain...
The actual underlying reason why your original setup didn't work is an Apple bug when fetching from a child context with sort descriptors when the parent context is not yet saved to store:
NSSortdescriptor ineffective on fetch result from NSManagedContext
If there is any way you can avoid nested contexts, do avoid them as they are still extremely buggy and you will likely be disappointed with the supposed performance gains, cf. also:
http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains
Since CoreData isnot a safe-thread framework and for each thread(operation queue), core data uses difference contexts. Please refer the following excellent writing
http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/
I've released an app with a Core Data sqlite database. In the new version of my app, I've created a new "Model Version" of my xcdatamodel in XCode. In the new version an entity is removed and some new attributes are added to one of the entities.
When updating to the new app version, I'm getting this sql error:
The model used to open the store is incompatible with the one used to create the store
How can I handle this error? All data in the database are downloaded from the web, so maybe the easiest way is to just delete the current sqlite file when this error occurs and start from scratch -- but what do people do when the database contains data that cannot be regenerated?
SOLUTION:
I've created an Mapping Model in Xcode and changed my persistentStoreCoordinator getter to handle an option dictionary to the addPersistentStoreWithType:configuration:URL:options:error: method with the key NSMigratePersistentStoresAutomaticallyOption.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *cacheURL = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [cacheURL URLByAppendingPathComponent:#"MyDatabase.sqlite"];
NSString *storePath = [storeURL path];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
__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();
}
return __persistentStoreCoordinator;
}
The problem you're having is that you have to migrate the data from the old core data files to the new core data files. This is why you're getting the "incompatible" error in your question. If you change your core data model then you will need to supply the old version and the new version and tell the system how to move the data from the old version to the new version.
To do this you need to use core data versioning (using bundles) and create migration schemes. Its a complicated process that is probably to difficult to explain in this answer. Normally you can create a new version of your core data files and it will migrate the data automatically, but there are times when you could have issues.
Best thing to do would be to look up core data versioning in google. A quick search turns up this quite comprehensive tutorial http://www.timisted.net/blog/archive/core-data-migration/. It looks pretty good.
I've looked through all the class documentation for Core Data and I can't find away to programmatically update values in a core data entity. For example, I have a structure similar to this:
id | title
============
1 | Foo
2 | Bar
3 | FooFoo
Say that I want to update Bar to BarBar, I can't find any way to do this in any of the documentation.
In Core Data, an object is an object is an object - the database isn't a thing you throw commands at.
To update something that is persisted, you recreate it as an object, update it, and save it.
NSError *error = nil;
//This is your NSManagedObject subclass
Books * aBook = nil;
//Set up to get the thing you want to update
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"MyLibrary" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"Title=%#",#"Bar"]];
//Ask for it
aBook = [[context executeFetchRequest:request error:&error] lastObject];
[request release];
if (error) {
//Handle any errors
}
if (!aBook) {
//Nothing there to update
}
//Update the object
aBook.Title = #"BarBar";
//Save it
error = nil;
if (![context save:&error]) {
//Handle any error with the saving of the context
}
The Apple documentation on using managed objects in Core Data likely has your answer. In short, though, you should be able to do something like this:
NSError *saveError;
[bookTwo setTitle:#"BarBar"];
if (![managedObjectContext save:&saveError]) {
NSLog(#"Saving changes to book book two failed: %#", saveError);
} else {
// The changes to bookTwo have been persisted.
}
(Note: bookTwo must be a managed object that is associated with managedObjectContext for this example to work.)
Sounds like you're thinking in terms of an underlying relational database. Core Data's API is built around model objects, not relational databases.
An entity is a Cocoa object—an instance of NSManagedObject or some subclass of that. The entity's attributes are properties of the object. You use key-value coding or, if you implement a subclass, dot syntax or accessor methods to set those properties.
Evan DiBiase's answer shows one correct way to set the property—specifically, an accessor message. Here's dot syntax:
bookTwo.title = #"BarBar";
And KVC (which you can use with plain old NSManagedObject):
[bookTwo setValue:#"BarBar" forKey:#"title"];
If I'm understanding your question correctly, I think that all you need to keep in mind is managed objects are really no different than any other Cocoa class. Attributes have accessors and mutators you can use in code, through key value coding or through bindings, only in this case they're generated by Core Data. The only trick is you need to manually declare the generated accessors in your class file (if you have one) for your entity if you want to avoid having to use setValue:ForKey:. The documentation describes this in more detail, but the short answer is that you can select your attributes in the data model designer, and choose Copy Obj-C 2.0 Method Declarations from the Design menu.
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSArray *temp = [[NSArray alloc]initWithObjects:#"NewWord", nil];
[object setValue:[temp objectAtIndex:0] forKey:#"title"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
I think this piece of code will give you the idea ;)