I have two model versions - 12 and 13. Then I created a xcmappingmodel-File with source 12 and destination 13.
I subclassed NSEntityMigrationPolicy and added my class to the mappingmodel-File to the entity desired.
#interface EndDateMigrationPolicy : NSEntityMigrationPolicy
After installing and older version (11) on my device, I install the current status with model version 13 - the app runs, but my migration methods aren't called. Am I missing something?
EDIT: is it correct to use these options?
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #YES,
NSInferMappingModelAutomaticallyOption: #YES};
I'm going to try to answer you as best I can, I have suffered through core data migrations quite a few times so I know how painful it is. For one you can not hope to have to have your migration work with those options because what you are trying to do is actually not a lightweight migration and yet you are telling it to do a lightweight migration.
Basically let's say you need for some reason to have a non lightweight migration between 2 versions, in your case 11 and 12. What you will need to do is do:
1->12 lightweight
12->13 custom migration
13->(future version) lightweight migration
There might be a better solution but I have not yet found it.
Here is some code to help you out (the hardest parts, I can't remember everything offhand), before this code I copy the database to a backup, so basically the back up database is your old one and the store is your new one (which is empty)
NSString *path = [[NSBundle mainBundle] pathForResource:<YOUR MODEL NAME> ofType:#"cdm"];
NSURL *backUpURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"<YOUR MODEL NAME>MigrationBackUp.sqlite"]; //or whatever you want to call your backup
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"<YOUR MODEL NAME>.sqlite"];
NSError *err2 = nil;
NSDictionary *sourceMetadata2 =
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:backUpURL
error:&err2];
NSManagedObjectModel *sourceModel2 = [NSManagedObjectModel mergedModelFromBundles:[NSArray arrayWithObject:[NSBundle mainBundle]]
forStoreMetadata:sourceMetadata2];
NSManagedObjectModel *destinationModel2 = [self managedObjectModelForVersion:#"1.4"]; //Yeah your gonna have to get your mapping model , I'll give you this code too later
NSString *oldModel = [[sourceModel2 versionIdentifiers] anyObject];
NSLog(#"Source Model : %#",oldModel);
NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]];
if (mappingModel != nil) {
for (NSString * identifier in [mappingModel entityMappings]) {
NSLog(#"Mapping > %#",identifier);
}
}
Then just make a migrator with your source and destination.
This is also a difficult part afterwards:
BOOL success = [migrator migrateStoreFromURL:backUpURL
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:storeURL
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:&err2];
Last but not least (I said I'd give it to you before):
- (NSManagedObjectModel *)managedObjectModelForVersion:(NSString*)version {
NSString *modelPath = [[NSBundle mainBundle] pathForResource:#"Model" ofType:#"momd"];
if (BETWEEN_INEX(version, #"1.0", #"1.4")) {
modelPath = [modelPath stringByAppendingPathComponent:#"Model"];
modelPath = [modelPath stringByAppendingPathExtension:#"mom"];
} else if (BETWEEN_INEX(version, #"1.4", #"1.5")) {
modelPath = [modelPath stringByAppendingPathComponent:#"Model 2"];
modelPath = [modelPath stringByAppendingPathExtension:#"mom"];
} else if (BETWEEN_INEX(version, #"1.5", #"1.6")) {
modelPath = [modelPath stringByAppendingPathComponent:#"Model 3"];
modelPath = [modelPath stringByAppendingPathExtension:#"mom"];
} else if (BETWEEN_INEX(version, #"1.6", #"1.7")) {
modelPath = [modelPath stringByAppendingPathComponent:#"Model 4"];
modelPath = [modelPath stringByAppendingPathExtension:#"mom"];
}
NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
NSManagedObjectModel * oldManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSSet *vIdentifiers = [oldManagedObjectModel versionIdentifiers];
for (NSString * identifier in vIdentifiers) {
NSLog(#"Old Model : %#",identifier);
}
return [oldManagedObjectModel autorelease];
}
I know these are just snippets of code, but I really hope it helps you solve your problem, if you need anything else just ask.
Your NSEntityMigrationPolicy is not being called because you set NSInferMappingModelAutomaticallyOption to #(YES) - it should be #(NO)
Related
I have an application that uses Core Data. I have two data models in the project and I create the ManagedObjectContext by merging the two models. Here the code where I do that:
- (NSManagedObjectModel *)managedObjectModel {
if (__managedObjectModel != nil) {
return __managedObjectModel;
}
NSURL* entityURL = [[NSBundle mainBundle] URLForResource:#"User_data" withExtension:#"momd"];
NSManagedObjectModel* entityModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:entityURL];
NSURL* whoURL = [[NSBundle mainBundle] URLForResource:#"WHO_data" withExtension:#"momd"];
NSManagedObjectModel* whoModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:whoURL];
NSArray* models = [NSArray arrayWithObjects:entityModel, whoModel, nil];
__managedObjectModel = [NSManagedObjectModel modelByMergingModels:models];
return __managedObjectModel;
}
None of the attributes in my entities are optional and my app crashes when I try to save my managedObjectContext. I believe this is because some of the attributes are not being set. I have overridden awakeFromInsert: for the parent entity:
- (void) awakeFromInsert
{
[super awakeFromInsert];
NSString* userCFUUID = [[NSUserDefaults standardUserDefaults]objectForKey:#"device_identifier"];
if ( userCFUUID ) {
[self cfuuid:userCFUUID];
} else {
[NSException raise:NSInvalidArgumentException format:#"Entry: awakeFromInsert: cannot find CFUUID"];
}
[self setCreationDate:[NSDate date]]; // the time since Jan 1st 1970 in seconds
[self setEventDate:[NSDate date]];
}
But awakeFromInsert: is never called. I've set a breakpoint and stepped through from the statement where I create the NSManagedObject:
LengthEntry *length1 = [NSEntityDescription insertNewObjectForEntityForName:#"LengthEntry" inManagedObjectContext:moc];
Additional fact that may or may not be relavent:
After creating the datamodel containing the problem entity, I used the Xcode feature to automatically create the classes. I then realised that since I had not specified to do otherwise in the model, xcode named the classes in the plural sense (because that's what I had called them in the model). So, I ended up with "Entries.h" instead of "Entry.h". I went back and manually changed all the classes and specified in the model the name of the classes.
So, I need to figure out why awakeFromInsert is never called.
awakeFromInsert is called exactly once for each object when it is first created.
What you want is awakeFromFetch in order to have it called every time that it is loaded into memory from the store.
Many times, you want the same or similar code in both places.
Out of desperation, I deleted the datamodel and the NSManagedObject classes. I then recreated the model and the classes.
Now, it works. Something screwy must have happened when I manually changed the names of the classes.
I have enabled versioning of my Core Data model and have been using light weight migration. My code always tries to do lightweight migration and then if that fails because the model are incompatible it falls back to deleting all existing data and refetching from the server.
So lightweight migration is just used for efficiency and isn't required for correctness.
What I want to do now is make a change to my model which in theory lightweight migration could handle but in fact I need new data from the server. I want to somehow flag the model and not upgradeable via lightweight migration. For example if a field name has not changed but the meaning of that field has changed in such a way that the old code is incompatible with the new code base. (This is just an example.)
Has anyone found a way to flag a two models as incompatible so lightweight migration won't upgrade them?
I've struggled with the same problem before.
I have a method that will attempt to migrate data using Mapping Models which is what you should use if you're going to turn off lightweight migration.
If you aren't going to do a lot of fancy data mapping, xcode will automatically create a mapping model that will work exactly like lightweight migration. All you have to do is create a new "Mapping Model" file each time you add a new version to Core Data. Just go to "File -> New -> New File" and under Core Data there should be a Mapping Model template. Select it and choose the source and destination versions.
I don't have my code openly available on github so I'll just post the migration method here.
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type toModel:(NSManagedObjectModel*)finalModel
{
NSError *error = nil;
// if store dosen't exist skip migration
NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
if(![NSBundle pathForResource:#"YongoPal" ofType:#"sqlite" inDirectory:documentDir])
{
migrationProgress = 1.0;
[self performSelectorOnMainThread:#selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
// remove migration view
[self.migrationView performSelectorOnMainThread:#selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
[self.migrationView performSelectorOnMainThread:#selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
self.migrationView = nil;
self.migrationProgressLabel = nil;
self.migrationProgressView = nil;
self.migrationSpinner = nil;
return YES;
}
//START:progressivelyMigrateURLHappyCheck
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type URL:sourceStoreURL error:&error];
if (!sourceMetadata)
{
return NO;
}
if ([finalModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
{
migrationProgress = 1.0;
[self performSelectorOnMainThread:#selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
// remove migration view
[self.migrationView performSelectorOnMainThread:#selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
[self.migrationView performSelectorOnMainThread:#selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
self.migrationView = nil;
self.migrationProgressLabel = nil;
self.migrationProgressView = nil;
self.migrationSpinner = nil;
error = nil;
return YES;
}
else
{
migrationProgress = 0.0;
[self.migrationView performSelectorOnMainThread:#selector(setHidden:) withObject:NO waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
}
//END:progressivelyMigrateURLHappyCheck
//START:progressivelyMigrateURLFindModels
//Find the source model
NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata];
if(sourceModel == nil)
{
NSLog(#"%#", [NSString stringWithFormat:#"Failed to find source model\n%#", [sourceMetadata description]]);
return NO;
}
//Find all of the mom and momd files in the Resources directory
NSMutableArray *modelPaths = [NSMutableArray array];
NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:#"momd" inDirectory:nil];
for (NSString *momdPath in momdArray)
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *resourceSubpath = [momdPath lastPathComponent];
NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:#"mom" inDirectory:resourceSubpath];
[modelPaths addObjectsFromArray:array];
[pool drain];
}
NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:#"mom" inDirectory:nil];
[modelPaths addObjectsFromArray:otherModels];
if (!modelPaths || ![modelPaths count])
{
//Throw an error if there are no models
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:#"No models found in bundle" forKey:NSLocalizedDescriptionKey];
//Populate the error
error = [NSError errorWithDomain:#"com.yongopal.coredata" code:500 userInfo:dict];
if([[self.prefs valueForKey:#"debugMode"] isEqualToString:#"Y"])
{
NSLog(#"error: %#", error);
}
return NO;
}
//END:progressivelyMigrateURLFindModels
//See if we can find a matching destination model
//START:progressivelyMigrateURLFindMap
NSMappingModel *mappingModel = nil;
NSManagedObjectModel *targetModel = nil;
NSString *modelPath = nil;
for(modelPath in modelPaths)
{
targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:targetModel];
//If we found a mapping model then proceed
if(mappingModel)
{
break;
}
else
{
//Release the target model and keep looking
[targetModel release];
targetModel = nil;
}
}
//We have tested every model, if nil here we failed
if (!mappingModel)
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:#"No mapping models found in bundle" forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:#"com.yongopal.coredata" code:500 userInfo:dict];
if([[self.prefs valueForKey:#"debugMode"] isEqualToString:#"Y"])
{
NSLog(#"error: %#", error);
}
return NO;
}
//END:progressivelyMigrateURLFindMap
//We have a mapping model and a destination model. Time to migrate
//START:progressivelyMigrateURLMigrate
NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel];
// reg KVO for migration progress
[manager addObserver:self forKeyPath:#"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL];
NSString *modelName = [[modelPath lastPathComponent] stringByDeletingPathExtension];
NSString *storeExtension = [[sourceStoreURL path] pathExtension];
NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];
//Build a path to write the new store
storePath = [NSString stringWithFormat:#"%#.%#.%#", storePath, modelName, storeExtension];
NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath];
if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error])
{
if([[self.prefs valueForKey:#"debugMode"] isEqualToString:#"Y"])
{
NSLog(#"error: %#", error);
}
[targetModel release];
[manager removeObserver:self forKeyPath:#"migrationProgress"];
[manager release];
return NO;
}
[targetModel release];
[manager removeObserver:self forKeyPath:#"migrationProgress"];
[manager release];
//END:progressivelyMigrateURLMigrate
//Migration was successful, move the files around to preserve the source
//START:progressivelyMigrateURLMoveAndRecurse
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
guid = [guid stringByAppendingPathExtension:modelName];
guid = [guid stringByAppendingPathExtension:storeExtension];
NSString *appSupportPath = [storePath stringByDeletingLastPathComponent];
NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager moveItemAtPath:[sourceStoreURL path] toPath:backupPath error:&error])
{
if([[self.prefs valueForKey:#"debugMode"] isEqualToString:#"Y"])
{
NSLog(#"error: %#", error);
}
//Failed to copy the file
return NO;
}
//Move the destination to the source path
if (![fileManager moveItemAtPath:storePath toPath:[sourceStoreURL path] error:&error])
{
if([[self.prefs valueForKey:#"debugMode"] isEqualToString:#"Y"])
{
NSLog(#"error: %#", error);
}
//Try to back out the source move first, no point in checking it for errors
[fileManager moveItemAtPath:backupPath toPath:[sourceStoreURL path] error:nil];
return NO;
}
//We may not be at the "current" model yet, so recurse
return [self progressivelyMigrateURL:sourceStoreURL ofType:type toModel:finalModel];
//END:progressivelyMigrateURLMoveAndRecurse
}
This is an edited version of a method I got from some Core Data book I can't remember the title of. I wish I could give credit to the author. :S
Beware, I have some code in here that you should remove in your implementation. It's mostly stuff I use to update the view on the progress of the migration.
You can use this method like so:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YongoPal.sqlite"];
// perform core data migrations if necessary
if(![self progressivelyMigrateURL:storeURL ofType:NSSQLiteStoreType toModel:self.managedObjectModel])
{
// reset the persistent store on fail
NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[NSBundle pathForResource:#"YongoPal" ofType:#"sqlite" inDirectory:documentDir] error:&error];
}
else
{
NSLog(#"migration succeeded!");
}
Remember to remove the lightweight migration option before you use this.
Apple's Core Data Model Versioning and Data Migration Programming Guide addresses what to do if "you have two versions of a model that Core Data would normally treat as equivalent that you want to be recognized as being different", which I think is what you're asking.
The answer is to set the versionHashModifier on one of your Entities or Properties in the new model.
In your example, you'd do that on the field whose meaning has changed.
First i want to apologize for starting more than one thread about this problem but i have taken step-by-step approach and steps to solve this very irritating problem. I have spend four days now to try to solve this.
The situation: I have an application where i use a pre-populated sqlite database (106k), via Core Data. I have moved the sqlite DB to the Resources folder and being able to copy it into documents. When i test i can see that the file exist, see code example and NSLog below.
I have used example code in the delegate, see below.
Everything works perfectly in the simulator but not when i test on the device.
Here is the code from the delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"FamQuiz_R0_1.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"FamQuiz_R0_1" 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];
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator_;
}
Here is the code when i read the database:
- (void)createQuestionsList: (NSMutableArray *)diffArray {
NSLog(#">>createQuestionsList<<");
NSFileManager *filemgr = [NSFileManager defaultManager];
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docsDirectory stringByAppendingPathComponent:#"FamQuiz_R0_1.sqlite"];
if ([filemgr fileExistsAtPath: filePath ] == YES)
NSLog (#"\n\n\nFile exists in createQuestionList\n\n\n");
else
NSLog (#"\n\n\nFile not foundin createQuestionList\n\n\n");
int nrOfQuestions = [[diffArray objectAtIndex:0]intValue];
int nrOfEasyPlayers = [[diffArray objectAtIndex:1]intValue];
int nrOfMediumPlayers = [[diffArray objectAtIndex:2]intValue];
int nrOfHardPlayers = [[diffArray objectAtIndex:3]intValue];
int nrOfPlayers = nrOfEasyPlayers + nrOfMediumPlayers + nrOfHardPlayers;
allHardArrayQ = [[NSMutableArray alloc]init];
allMediumArrayQ = [[NSMutableArray alloc]init];
allEasyArrayQ = [[NSMutableArray alloc]init];
allDummyQ = [[NSMutableArray alloc]init];
allJointQ = [[NSMutableArray alloc]init];
hardQ = [[NSMutableArray alloc]init];
mediumQ = [[NSMutableArray alloc]init];
easyQ = [[NSMutableArray alloc]init];
masterQuestionsArray = [[NSMutableArray alloc] initWithObjects:
[NSNumber numberWithBool:[[diffArray objectAtIndex:4]boolValue]],
[NSNumber numberWithInt:nrOfHardPlayers],
[NSNumber numberWithInt:nrOfMediumPlayers],
[NSNumber numberWithInt:nrOfEasyPlayers],
[NSNumber numberWithInt:nrOfQuestions],
nil];
NSLog(#"masterQuestionsArray %#", masterQuestionsArray);
NSError *error;
//=========PREPARE CORE DATA DB===========//
if (managedObjectContext == nil) { managedObjectContext = [(FamQuiz_R0_1AppDelegate *)
[[UIApplication sharedApplication] delegate] managedObjectContext]; }
// Define qContext
NSManagedObjectContext *qContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"questions" inManagedObjectContext:qContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [qContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
if ([[info valueForKey:#"qDiff"] intValue] == 1) {
[allEasyArrayQ addObject:[info valueForKey:#"idQ"]];
} else if ([[info valueForKey:#"qDiff"] intValue] == 2) {
[allMediumArrayQ addObject:[info valueForKey:#"idQ"]];
} else if ([[info valueForKey:#"qDiff"] intValue] == 3) {
[allHardArrayQ addObject:[info valueForKey:#"idQ"]];
}
}
NSLog(#"allEasyArrayQ %#", allEasyArrayQ);
NSLog(#"allMediumArrayQ %#", allMediumArrayQ);
NSLog(#"allHardArrayQ %#", allHardArrayQ);
Here is the output:
2011-04-25 19:35:45.008 FamQuiz_R0_1[963:307] >>createQuestionsList<<
2011-04-25 19:35:45.021 FamQuiz_R0_1[963:307]
File exists in createQuestionList
2011-04-25 19:35:45.031 FamQuiz_R0_1[963:307] masterQuestionsArray (
1,
0,
0,
1,
5
)
2011-04-25 19:35:45.238 FamQuiz_R0_1[963:307] allEasyArrayQ (
)
2011-04-25 19:35:45.246 FamQuiz_R0_1[963:307] allMediumArrayQ (
)
2011-04-25 19:35:45.254 FamQuiz_R0_1[963:307] allHardArrayQ (
)
2011-04-25 19:35:45.311 FamQuiz_R0_1[963:307] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray objectAtIndex:]: index 0 beyond bounds for empty array'
I REALLY appreciate all help i can get to solve this extremely frustrating problem :-)
UPDATE
I did put in a NSLog in the delegate to check the storeURL and got the following result when running on the external iPhone device:
storeURL: file://localhost/var/mobile/Applications/7F5FDB03-0D22-46BC-91BC-4D268EB4BBEB/Documents/FamQuiz_R0_1.sqlite
The following is when i run in the simulator, without the external iPhone device:
storeURL: file://localhost/Users/PeterK/Library/Application%20Support/iPhone%20Simulator/4.2/Applications/E27EC1A4-3A87-4A1B-97DE-D464679005BE/Documents/FamQuiz_R0_1.sqlite
The crash you are getting is not coming from the posted code. The last line in the code is:
NSLog(#"allHardArrayQ %#", allHardArrayQ);
... which prints.
Somewhere down the line from the provided code you make a call like this:
[someArray objectAtIndex:0]
... while someArray is empty and that causes the out of range error. Most likely, someArray is one of the three empty arrays just logged but not necessarily.
Your arrays are showing empty because (1) you are getting no returns from your fetch or (2) the values for qDiff and idQ aren't what or where you think they are. To confirm, add logs:
NSArray *fetchedObjects = [qContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"fetchedObjects =",fetchedObjects);
for (NSManagedObject *info in fetchedObjects) {
NSLog(#"[info valueForKey:#"qDiff"]",[info valueForKey:#"qDiff"]);
NSLog(#"[info valueForKey:#"idQ"]",[info valueForKey:#"idQ"]]);
if ([[info valueForKey:#"qDiff"] intValue] == 1) {
[allEasyArrayQ addObject:[info valueForKey:#"idQ"]];
....
... that well tell you if you are getting the data in the first place. If not then you need to look at the configuration of your Core Data stack.
More generally, I think your code demonstrates that you haven't quite conceptually grasped Core Data yet. Instead, you seem to being trying to treat Core Data as a lightweight object wrapper around SQL. This is a common misperception among those skilled at SQL but new to Core Data and it causes a lot of design issues.
Firstly, there is no reason to check for the physical presence of the store file after you have initialize the persistent store. The NSPersistentStore object in memory cannot exist without the store file so if the object is there, the file is always there.
Secondly, you are piling on a lot of additional data structures in the form of arrays. This is a common need when dealing with raw SQL but is almost always unnecessary when using Core Data. The entire point of Core Data is to manage data manually (persisting it to the disk is just an option and is not required.) All your arrays should most likely be entities in the data model and you should be using fetches and sorts to pull out specific pieces of data ordered for the immediate needs of any particular view.
It's very common to see people with a strong SQL or similar background doing way, way more work than needed to implement Core Data. It's like someone who has drive a manual transmission for year who switches to an automatic. They keep stepping on the non-existent clutch and fiddling with the gear shift.
Seems like you are not getting anything from the context, all three arrays are printed empty. And the exception seems to be thrown from out of what you ve pasted here. Frankly, there are irrelevant, redundant parts like;
//=========PREPARE CORE DATA DB===========//
if (managedObjectContext == nil) { managedObjectContext = [(FamQuiz_R0_1AppDelegate *)
...which is never used, but the main problem is i guess, you are trying to access an empty array assuming something from the context comes in there, but apparently not...
You should try it step by step and be sure that your context operations return anything...
And clean the code a bit so that you can see the problem easier, or anyone who is trying to help you...
PROBLEM SOLVED
After spending yet another evening trying to debug every line in the code, without understanding, i downloaded "iPhone Explorer" and manually deleted the two instances of the DB directly on the device and after that it finally worked.
I noticed that the DB was not copied to the documents folder, there an empty DB resided that was smaller that the populated DB.
Now both DB instances is the correct ones :-) ...meaning that copy works now.
If you want the "iPhone Explorer" you will find it here: http://www.macroplant.com/iphoneexplorer/
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.
I need to add some attributes to my core data model and I am having a terrible time getting lightweight migration to work!
I keep getting the error "Cant merge models with two different entities named blah".
Here's what I've done ...
Added this code to my app delegate.
(NSPersistentStoreCoordinator*)persistentStoreCoordinator {
//blah blah
NSDictionary* options =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
//blah blah
return _persistentStoreCoordinator;
}
Clicked on my data model, went to Design > Data Model > Add Model Version.
Made my changes to the one with the LOWEST number, basically adding a few attributes.
Deleted all the managed files produced from my previous model, sent them to trash, then created new ones from the new model.
Cleaned all targets.
Build and go.
ERROR.
Please please help. I've tried the above in numerous different ways, and loads of other stuff, each time going back to a clean copy of my project and starting again, and nothing has got me past this error.
Thanks!
Well, once again, another 6 hours of my life completely wasted because Apple are a bunch of ... well, I'll stop there.
Anyway, thanks to this lovely person: http://linkroller.com/fullpage/ad/13754/?http://iphonedevelopment.blogspot.com/2009/09/core-data-migration-problems.html I was able to solve the problem.
You follow the steps I already followed, then you need to find the following method:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return managedObjectModel;
}
and change it to:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"Foo" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}
where foo is the name of you xcdatamodeld file.
AAAAAARGH.
I had fixed the core data migration
pls follwing this steps
Go AppDelegate.m write function
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSString *databaseFilePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"sampleiOS.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath: databaseFilePath];
NSDictionary *_option = [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:_option error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return __persistentStoreCoordinator;
}
then select example.xcdatamodeld file
click the Editor menu on the top => add model version => create the new model version "example 2.xcdatamodel" then click finish button.
now show two core data model version one is source "example.xcdatamodel" another one is destination "example 2.xcdatamodel".
now add an attribute or entity in your new version datamodel "example 2.xcdatamodel". then click the group data model "example.xcdatamodeld". After that set current version to be newly created data model "example 2.xcdatamodel".
How to set current Version
select show utilities => show inspector => versioned core data model . then set current version.