Managing multiple NSPersistentStores with PersistentStoreCoodinator - iphone

I am trying to get NSPersistentStoreCoordinator to manage the deletion and insertion of multiple persistent stores. So far I have managed to configure the PSC with two stores and I have been able to remove either store by specifying its index.
Like this…
NSPersistentStore *store = [[self.persistentStoreCoordinator persistentStores] objectAtIndex:0];
if (![self.persistentStoreCoordinator removePersistentStore:store error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[[NSFileManager defaultManager] removeItemAtURL:store.URL error:&error];
But I'm finding that when I add the store back in to the PSC the index value is incorrect and it cannot be specified with the existing class methods. The consequence of this is that the new data is downloaded and added to the wrong store.
Does anyone have any suggestions on how to this should be done?
Background (Updated)
The reason for using two persistent stores is so that I can designate a unique store for the two xml documents that will be downloaded over the network. As both these files are relatively large I am hoping to reduce network traffic. So a check is done to see if either file has been modified. If they have then the corresponding persistent store is deleted and a new store added. It's at this point the problem starts. Adding a new store always adds it to the end of the Persistent Stores Array. This appears to create a mismatch in the stores when merging the data back with the MOC.
Code
Here's what I've tried so far which removes and adds the persistent store but the new data is added to the wrong store.
static NSString * const kLastDateStoreUpdateKey = #"eventLastStoreUpdateKey";
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSString *last_modified = [NSString stringWithFormat:#"%#",[[(NSHTTPURLResponse *)response allHeaderFields] objectForKey:#"Last-Modified"]];
NSDateFormatter *dFormatter = [[NSDateFormatter alloc] init];
[dFormatter setDateFormat:#"EEE, dd MMM yyyy HH:mm:ss zzz"];
[dFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_GB"]];
[dFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
dateModified = [dFormatter dateFromString:last_modified];
NSDate *previousDate = [[NSUserDefaults standardUserDefaults] objectForKey:kLastDateStoreUpdateKey];
if (!previousDate || [previousDate compare:dateModified] != NSOrderedSame) {
[self.managedObjectContext lock];
[self.managedObjectContext reset];
if ([[NSFileManager defaultManager] fileExistsAtPath:self.persistentStorePath]) {
NSError *error = nil;
NSArray *stores = [self.persistentStoreCoordinator persistentStores];
NSURL *storeUrls = [NSURL fileURLWithPath:persistentStorePath];
for (NSPersistentStore *store in stores){
if ([[store.URL absoluteURL] isEqual:[storeUrls absoluteURL]]) {
if (![self.persistentStoreCoordinator removePersistentStore:store error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[[NSFileManager defaultManager] removeItemAtURL:store.URL error:&error];
NSLog(#"Check store removed %#", [self.persistentStoreCoordinator persistentStores]);
}
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:persistentStorePath];
NSError *error = nil;
[self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
if (error) {
NSLog(#"event error %# %#",error, [[self.persistentStoreCoordinator persistentStores] objectAtIndex:0]);
}
NSLog(#"Check store added %#",self.persistentStoreCoordinator.persistentStores);
[self.managedObjectContext unlock];
}else {
[self cancelDownload];
NSLog(#"event cancel %# %# %#",previousDate, dateModified, [self.persistentStoreCoordinator persistentStores]);
}
}
Solved
As Hunter pointed out below my PSC Configuration was set to as nil. So data was not being added to each individual Persistent Store. When I created a Configuration in the Core Data Model and targeted the relevant Entities I set the PSC to have that configuration. It then worked as expected. See below.
NSURL *storeUrl = [NSURL fileURLWithPath:persistentStorePath];
NSError *error = nil;
[self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:#"ConfigEvent" URL:storeUrl options:nil error:&error];
if (error) {
NSLog(#"event error %# %#",error, [[self.persistentStoreCoordinator persistentStores] objectAtIndex:0]);
}
NSLog(#"Check store added %#",self.persistentStoreCoordinator.persistentStores);

If you truly require the two stores [I can't tell from your explanation if that's really the best way to do this], you might want to reference them via properties or an ivar instead of relying on the array.
Alternatively, you could iterate through the PSC's stores array and identify each one by inspecting the URL.
Update:
If you're having trouble storing specific data in different NSPersistentStores, take a look at Core Data configurations. This allows you to tell Core Data to put specific entities in specific persistent stores.
You specify configurations in your model and when you add persistent stores. More here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdMOM.html

You should be able to achieve this with one Persistent Store Coordinator and two Managed Object Contexts. You only need to go to the complexity of two Persistent Store Coordinators if you are experiencing locking which is hurting performance.
Remember: The Simplest Possible Thing (tm)

Related

Cannot fetch data from Core Data sqlite DB

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/

Core data: Why removing the sqlite file did not clear all the previous data insert immediately?

I want to reset the data store clean by removing the app's sqlite file. I wrote this function in my data helper class:
-(void) resetPersistenStore {
NSError *error = nil;
[persistentStoreCoordinator_ release];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MyApp.sqlite"];
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
if (error) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
managedObjectModel_ = nil;
}
I put this following test in UIApplication::didFinishLaunchingWithOptions
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* test */
[TestDataHelper populateTestData:[self managedObjectContext]];
[TestDataHelper populateTestData:[self managedObjectContext]];
[self resetPersistenStore];
[TestDataHelper populateTestData:[self managedObjectContext]];
[TestDataHelper testPopulateTestData:[self managedObjectContext]];
Instead of one set of data created by the function populateTestData, I can see actually three set of data (because I called the function three times)
It is clear that resetPersistenStore() works, because without it, the data will keep accumulating.
So my question is:
Why the reset does not take effect immediately?
I have set managedObjectContext to nil in the reset function, but it did not help.
Here is my populateTestData function
+(void)populateTestData:(NSManagedObjectContext*)managedObjectContext {
Stock* s1 = (Stock*)[NSEntityDescription insertNewObjectForEntityForName:#"Stock"
inManagedObjectContext:managedObjectContext];
s1.code = #"ABC2";
s1.name = #"ABC";
s1.enteredBy = #"akong";
[managedObjectContext save:&error];
if (error) {
NSLog(#"Data error %#", [error description]);
} else {
NSLog(#"Init completed");
}
}
Even though the context saved the data in populateTestData, it still has data loaded in it.
Suppose when the application starts, the file has 3 objects. Now the first populateTestData message adds an object into the context, and save it. So 4 objects in the file, 1 in the context. After the second message, 5 in the file, 2 in the context. Now the file is gone, but still there are 2 in the context. Now the final message adds another object to the context and to the file, so there are 3 objects.
You said setting managedObjectContext to nil in the reset function didn't help. I doubt if the context has been properly reset there. Try [managedObjectContext reset] instead.
I am only speculating here but are you by any chance using a fetchedResultsController to verify/display the results of these operations? If so try to set its caching to nil and see if it helps.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"xxxxxxxx" cacheName:nil];
Cheers,
Rog
It may simply be an artifact of the simulator. If you're running in the simulator, you may need to reset it. From the iOS Simulator menu, select Reset Content and Settings...

CoreData Save Permanently?

I have been working with Core Data in an iPad app and I can successfully save and fetch data inside the app. However when completely closing the application, fully, quit, take it out of multitasking, and that data disappears.
So does Core Data in anyway keep this data anywhere when the app is closed? Or do I need to look somewhere else?
EDIT: This is in the app delegate didFinishLaunchingWithOptions: [[[UIApplication sharedApplication] delegate] managedObjectContext]; and then I have this: context_ = [(prototypeAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; in the UIView subclass.
This is the NSPersistentStoreCoordinator code premade in the app delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"prototype.sqlite"];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
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.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator_;
}
So far I am using this to fetch data:
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *testEntity = [NSEntityDescription entityForName:#"DatedText" inManagedObjectContext:context_];
[fetch setEntity:testEntity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"dateSaved == %#", datePicker.date];
[fetch setPredicate:pred];
NSError *fetchError = nil;
NSArray *fetchedObjs = [context_ executeFetchRequest:fetch error:&fetchError];
if (fetchError != nil) {
NSLog(#"fetchError = %#, details = %#",fetchError,fetchError.userInfo);
}
noteTextView.text = [[fetchedObjs objectAtIndex:0] valueForKey:#"savedText"];
And this to save data:
NSManagedObject *newDatedText;
newDatedText = [NSEntityDescription insertNewObjectForEntityForName:#"DatedText" inManagedObjectContext:context_];
[newDatedText setValue:noteTextView.text forKey:#"savedText"];
[newDatedText setValue:datePicker.date forKey:#"dateSaved"];
NSError *saveError = nil;
[context_ save:&saveError];
if (saveError != nil) {
NSLog(#"[%# saveContext] Error saving context: Error = %#, details = %#",[self class], saveError,saveError.userInfo);
}
Do you save the context in the right places? It is a common mistake not to save the context when entering background application state only in willTerminate.
Save the context in the following appdelegate method:
-(void)applicationDidEnterBackground:(UIApplication *)application
You are saving your context directly after inserting the object, this should be sufficient. Check the sqlite file in simulator if it contains any data after saving.
if
noteTextView.text = [[fetchedObjs objectAtIndex:0] valueForKey:#"savedText"];
does not throw an exception, there is an object found in context. Maybe it does not contain the expected value?
Log the returned object from your fetchrequest to console to see if this might be the case
You should post the code that sets up your NSPersistentStoreCoordinator and adds your NSPersistentStore. By any chance are you using NSInMemoryStoreType as the type of your store? Because that would result in the behavior you're seeing. Alternately, you could be using a different path to the store each time, which would give you a fresh store each time. In general, your store should be in your Documents folder, and it should be given the same name on every launch. It should also use the NSSQLiteStoreType
I have discovered the problem. It turns out that due to its use of UIDatePicker, at the start of the program it set that date picker to today using:
NSDate *now = [[NSDate alloc] init];
[datePicker setDate:now];
So without using this it works perfectly. So currently I am looking for a solution to this issue, as this line seems to cause the problem.
UIDatePicker Interfering with CoreData
If you add a CoreData after create your project.There is a risk to make mistake in lazy_init your NSManagedObject.
-(NSManagedObjectContext*) managedObjectContext{
if (!_managedObjectContext) {
_managedObjectContext =[self createManageObjectContextWithName:#"name.sqlite"];
}
return _managedObjectContext;}
Here is the right way:
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;}

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.

Updates not saving to DB and properties not firing faults

Two hopefully minor questions regarding CoreData that I've been unable to find answers to:
1) I have a faulted object. Accessing an attribute as a property is not firing the fault, accessing the same property via KVC IS firing the fault. Any idea why?
i.e. object.title returns nil and object is still faulted, but [object valueForKey:#"title"] returns the title and the object is no longer a fault.
2) Updates to existing records have stopped working. Add/Delete works. Add/Update share the same code path (one is passed the existing object, the other a newly inserted object). However Update wont work. The data in the updated object is correct and set to the new values and the save succeeds with no errors, but the record in the database remains unchanged. Any idea?
NB: There is only one NSManagedObjectContext
Cheers
couldn't tell much from your description without code.
however it looks like you have updated the object in ram but the update wasn't submitted to the database layer making the physical change.
EDIT:
Yes, "Add" and "Delete" is different from "edit/update" a record.
for performance reason mapped objects are saved in memory as entities when you doing manipulation against NSManagedObjectContext you are not coding against database entirely.
check the link below:
http://cocoawithlove.com/2010/02/differences-between-core-data-and.html
normal work flow:
load appropriate rows from a database
instantiate objects from these rows
make changes to the graph objects
that are now in memory
commit the changes back to the
database
This is my core data for saving.
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Tweet *newTweet = (Tweet *)[NSEntityDescription insertNewObjectForEntityForName:#"Tweet" inManagedObjectContext:app.managedObjectContext];
newTweet.status = status;
newTweet.post_date = theDate;
newTweet.post_id = post_id;
newTweet.sent_error = sent_error;
newTweet.sent_status = sent_status;
newTweet.screen_name = [Settings getActiveScreenName];
// SAVE
NSError* error = nil;
if (![app.managedObjectContext save:&error]) {NSLog(#"did this work?? = %# with userInfo = %#", error, [error userInfo]);}
I have this in my app delegate
- (void)applicationWillTerminate:(UIApplication *)application {
// need to check if TweetViewController is activel.
// User is writing a tweet.
UIViewController * topController = [navigationController visibleViewController];
if([topController isKindOfClass:[TweetViewController class]] ){
[Settings setObject:[(TweetViewController*)topController tweetText].text forKey:#"last_tweet_text"];
}
NSLog(#"good bye");
NSError *error;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Handle error.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
}
and this as well in AppDelegate
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
// ~/Library/Application Support/iPhone Simulator/User/
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"tweetv12.sqlite"]];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
// Handle error
NSLog(#"cannot save data, change db name.");
}
return persistentStoreCoordinator;
}
I am also able to save delete and update data with this.