Question

PROBLEM STATEMENT

When attempting to save a record to a read/write store that is one of two SQLite stores assigned to the same PersistentStoreCoordinator, my iPhone app crashes. One obvious issue when saving a record is the PersistentStoreCoordinator does not know in which Store to save the data (only because I don't know how to make this happen).

First I will provide the big picture to make sure my approach is sound. Then I will supply the implementation details.

BACKGROUND

This is a simplified example representing the key aspects of the actual app I am working on.

Managed Object Model

Seed Data

Seed Data

User Input Scenario

User Input Scenario

CURRENT IMPLEMENTATION

Core Data Implementation

Core Data Implementation

Data Storage and Retrieval

Data Storage and Retrieval

Of course, there should be no evidence to the user, when looking at pick lists to make a choice for an attribute, that the choices come from two different Stores.

Persistent Store Coordinator Setup

- (NSPersistentStoreCoordinator*)persistentStoreCoordinator {
   if (_persistentStoreCoordinator == nil) {
       NSArray *bundles = @[[NSBundle bundleForClass:[self class]]];
       _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:bundles]];

       NSError *error;
       //--------------------------------------------------
       // Set options for the USER DATA Persistent Store.
       NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
                                       NSInferMappingModelAutomaticallyOption : @YES};
       //--------------------------------------------------
       // Add the USER DATA Store to the Persistent Store Coordinator.
       NSPersistentStore *persistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                                                   configuration:nil
                                                                                             URL:self.persistentStorePathForUserData
                                                                                         options:options
                                                                                           error:&error];
       //--------------------------------------------------
       // Set options for the SEED DATA Persistent Store.
       options = @{NSMigratePersistentStoresAutomaticallyOption : @YES,
                         NSInferMappingModelAutomaticallyOption : @YES,
                                NSReadOnlyPersistentStoreOption : @YES};
       //--------------------------------------------------
       // Add the SEED DATA Store to the Persistent Store Coordinator.
       persistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                                configuration:nil
                                                                          URL:self.persistentStorePathForSeedData
                                                                      options:options
                                                                        error:&error];
   }
   return _persistentStoreCoordinator;
}

IMPORTANT GOALS

Keep in mind the following:

  1. I would prefer, if at all possible, to do seed data updates without having to manage data versions behind the scenes (i.e. supplying with app updates only Seed Data records that are new or changed and somehow handling deletions) or implementing version-checking functionality in the code to handle a situation in which a user is upgrading from version n to n+5.
  2. User Data and Seed Data should not contain any duplicate records between the two and use the same ManagedObjectModel. So from data and model perspectives, there should be no need to merge the two stores or migrate one store to another.

RESEARCH

In this case, Save objects from multiple stores to single persistent store, the two Stores were merged followed by fetching all the records, culling out the duplicates, and saving the context. I'm hoping not to have to merge and then check thousands of records for duplicates. (See Important Goal #2 above.)

In this case, What is an efficient way to Merge two iOS Core Data Persistent Stores?, certain entities are read-only and others are read/write. In my app, all entities in the Seed Data Store are read-only and the same entities in the User Data Store are read/write. So I don't think migration is applicable. (See Important Goal #2 above.)

In Apple's, Core Data Programming Guide under "Persistent Store Coordinator", Figure 4 "Advanced persistence stack" shows a Core Data implementation using two Stores, but again, each Store is configured with separate and distinct objects. In my app, each object appears in each Store.

The solution proposed here, Combining Two SQLite Stores Into One, relative to having two stores without relationships between the objects in the different stores seems relevant, but no details are provided to compare to what I have implemented.

I have gone through the first three chapters of, Core Data (2nd Edition), by Marcus Zarra, but he doesn't get into using two stores that don't require migration. Chapter 3, however, does provide a very clear example of versioning. (It's complexity lead me to Important Goal #1 above.)

This solution, Which persistent store is used by default in core data in iPhone, suggests using multiple configurations of the ManagedObjectModel, but each entity is assigned to one and only one configuration. I'm not sure how, or even if, this solution can be extrapolated to my situation.

Maybe the solution proposed here, NSPersistentStoreCoordinator with two types of persistent stores?, is close to what I need. Unfortunately, only requests are addressed. I don't see a method analogous to NSFetchRequest method, setAffectedStores, in Class NSManagedObjectContext for saves.

Was it helpful?

Solution

Thanks to Ray Wenderlich's referral to Mic Pringle, Mic proposed a Managed Object Model architecture with which I was able to solve the issue while adhering to my goals. The key to the solution is utilizing an abstract entity as a parent entity to user and seed entities.

Managed Object Model

With this architecture, it is possible to create two Configurations that are assigned to separate stores: 1) UserData - r/w store located in the User's Documents directory.

UserData Configuration

2) SeedData - r only store located in the App Bundle.

SeedData Configuration

The downside is that record ID's must be maintained for the seed data entities (since relationships are not allowed between Configurations), but the huge upside is that changes or additions can be made to the seed data without affecting the user's entries AND without having to adopt any of the cumbersome solutions discussed in the RESEARCH section of the original post of this question.

OTHER TIPS

For the core question of saving data with one context/coordinator and two stores, the approach you want is:

  1. When adding the user-editable store via addPersistentStore, save a reference to the NSPersistentStore object that is returned.
  2. When creating a new object to save in the user-editable store, do something like this:

    NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:@"Vehicle" inManagedObjectContext:self.managedObjectContext];
    [self.managedObjectContext assignObject:newObject toPersistentStore:userEditableStore];
    

    The key here is to explicitly call assignObject:toPersistentStore: before saving changes.

For related questions:

I would prefer, if at all possible, to do seed data updates without having to manage data versions behind the scenes...

If you leave the non-editable store in the app bundle (i.e. you don't copy the file to somewhere else), you can just include a new version of the data with a new version of the app. You'll always be using whatever version is in the app bundle, so you'll have the latest data.

If you do end up copying data from the seed store to the user store, make sure that each entry includes the app (or seed store) version number when it was added. That'll make it easy to avoid duplicates.

User Data and Seed Data should not contain any duplicate records between the two and use the same ManagedObjectModel. So from data and model perspectives, there should be no need to merge the two stores or migrate one store to another.

You don't need to copy data from one to the other if you don't have any relationships between objects in different stores-- because that's just not permitted in Core Data. If you need relationships (and it sounds like you do) then look into fetched properties. These look a lot like an attribute or a relationship on an entity type, but internally they look up values from the persistent store(s). With multiple persistent store files this allows something that's almost but not quite like a relationship between objects in different stores.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top