views:

112

answers:

5

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.

A: 

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?

Nimrod
Thanks Nimrod. Good to know that it CAN be done, now to figure out how to do it. Searches so far haven't given any examples but I will keep looking. Whoever finds it first can post it here, or hopefully someone will give an example. By the way, new here, and I do appreciate your answer, but I'm thinking I shouldn't give a checkmark until we get the final answer - is that right??
JPK
I agree. I'd like to see a full answer too so please don't checkmark this. BTW, do note that it says you can't have cross-store relationships unfortunately.... Guess that's for referential integrity reasons.
Nimrod
Also, something I found that sort of gives an example: http://www.cimgf.com/2009/05/03/core-data-and-plug-ins/
Nimrod
Thanks for sticking with me on this Nimrod. I am still kind of new at this.. on preliminary glance at the code in that link it looked a little over my head, but I will try to take a deeper look when I have some time. I think ImHuntingWabbits is on the right track, hopefully we can figure this out...
JPK
I think setEntities forConfiguration, coupled with ImHuntingWabbits' code, is the way to go but I sure could use a full code example of this. I'm trying, but hitting my head against walls!
JPK
Oh Nimrod, I hadn't seen your comments below. I will try what you said.
JPK
A: 

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.

JPK
A: 

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.

ImHuntingWabbits
Thanks for the suggestion. I tried this and hit a wall with alloc init of the PersistentStoreCoordinator because I was using it without initWithManagedObjectModel. If I use initWithManagedObjectModel, it works, but I have to use the merged managedObjectModel. This produces a the two sqlite files, but each of them has ALL the entities in it! Without initWithManagedObjectModel, the app simply crashes with no error information. Sure I am missing something, but what?
JPK
I think the problem is the configuration:nil part. That needs to specify a list of entities assigned to that persistent store, from what I've read. "configurationNameThe name of the managed object model configuration to use. Pass nil if you do not want to specify a configuration."
Nimrod
Ok, try this. open up your model. click on an entity. click the little wrench icon in the upper right. you should see something that says "configurations". add two configurations, one for each store, and check off one of the boxes for that entity. repeat with other entities checking off the appropriate configuration. now, specify those names as strings for "configuration:" above
Nimrod
OK Nimrod. I tried it. I am just playing with so many different methods at this point, tweaking this and tweaking that. I can get it to produce two sqlite files. Each with all the entities or each with no entities. I'd post my code but I've changed it a hundred times and it's a mess. I am working in the - (NSPersistentStoreCoordinator *)persistentStoreCoordinator method and I also created a managedObjectModel for each of my datamodels (I have two, I know it can be done with one, but I don't think that is the problem). I don't know what is wrong with me - I can usually get these things!
JPK
Can't get it. Two files produced, one always has all entities, one always completely empty. Stick a fork in me. I'm done. On to the next issue.
JPK
You will have to use a merged managed object model, which actually should be fine. The key is actually creating the individual persistent stores, both using the same managed object model, and then assigning your objects to the appropriate store.
ImHuntingWabbits
ImHuntingWabbits - could you take a look at the code I posted? I think I am (thought I was) doing just that - I switched from two models to one model. I used configurations and assigned the objects to their respective stores. When there are no sqllite files, the app produces them, as expected. However, one file contains all of the entities and the other contains none. I would really appreciate it if you could tell me where I am going wrong. Have looked at this until my eyes are blurry.
JPK
A: 

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;
}
JPK
+1  A: 

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?

JPK