Raz-X Raz-X - 6 months ago 39
iOS Question

Core Data: The model used to open the store is incompatible with the one used to create the store

My app contains 2 databases:


  • db1: A read/write database (to store all the user settings)

  • db2: A readonly database, preloaded in another project (i copied .sqlite, .xcdatamodeld and entities class in the project)



If i initialize Core Data with 2 MOC and 2 PSC (one for each database): everything works fine. But i would like to initialize only 1 MOC/PSC for the two databases. To do this, i wrote the following code:

- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *db1ModelURL = [[NSBundle mainBundle] URLForResource:@"db1" withExtension:@"momd"];
NSManagedObjectModel *db1Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db1ModelURL];
NSURL *db2ModelURL = [[NSBundle mainBundle] URLForResource:@"db2" withExtension:@"momd"];
NSManagedObjectModel *db2Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db2ModelURL];
NSAssert(db1 != nil, @"Error initializing Managed Object Model");
NSAssert(db2 != nil, @"Error initializing Managed Object Model");

_managedObjectModel=[NSManagedObjectModel modelByMergingModels:[NSArray db1Mom,db2Mom, nil]];

return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}

NSURL * db1URL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"db1.sqlite"];

NSURL *db2URL = [[NSBundle mainBundle] URLForResource:@"db2" withExtension:@"sqlite"];

NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

_persistentStoreCoordinator = [[self managedObjectContext] persistentStoreCoordinator];
NSMutableDictionary * db2Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
@YES,NSReadOnlyPersistentStoreOption,
nil];
NSPersistentStore *store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"DB2" URL:db2URL options:db2Options error:&error];
NSAssert(store != nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);

NSMutableDictionary * db1Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
@YES,NSMigratePersistentStoresAutomaticallyOption,
@YES,NSInferMappingModelAutomaticallyOption,
nil];
store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:@"DB1" URL:db1URL options:db1Options error:&error];
NSAssert(store != nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);

return _persistentStoreCoordinator;
}


And when i launch the app, i get the following error on the DB2 database:

The model used to open the store is incompatible with the one used to create the store


I think the problem comes with the call to modelByMergingModels, the resulting model contains the db2Model, but Core Data doesn't recognize it as the base model for this database...

Suggestions?

Answer

I found a solution reading this article. Even if i don't understand why it works...

- (void)initializeCoreData{
// Initialize models
NSURL *db2ModelURL = [[NSBundle mainBundle] URLForResource:@"villes" withExtension:@"momd"];
NSManagedObjectModel *db2Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db2ModelURL];
NSAssert(db2Mom != nil, @"Error initializing Managed Object Model");

NSURL *db1ModelURL = [[NSBundle mainBundle] URLForResource:@"MMAMeteoPro" withExtension:@"momd"];
NSManagedObjectModel *db1Mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:db1ModelURL];
NSAssert(db1Mom != nil, @"Error initializing Managed Object Model");


NSManagedObjectModel * fullModel=[NSManagedObjectModel modelByMergingModels:[NSArray arrayWithObjects:db1Mom,db2Mom, nil]];

// Initialize context
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:fullModel];
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator:psc];
[self setManagedObjectContext:moc];

// Initialize stores
NSURL *db2StoreURL = [[NSBundle mainBundle] URLForResource:@"db2" withExtension:@"sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *db1StoreURL = [documentsURL URLByAppendingPathComponent:@"db1.sqlite"];

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
    NSError *error = nil;
    NSPersistentStoreCoordinator *psc = [[self managedObjectContext] persistentStoreCoordinator];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                             nil];
    NSMutableDictionary * db2Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
                                   @{@"journal_mode":@"DELETE"},NSSQLitePragmasOption,
                                   @YES, NSReadOnlyPersistentStoreOption,
                                   nil];
    NSMutableDictionary * db1Options=[NSMutableDictionary dictionaryWithObjectsAndKeys:
                                         @{@"journal_mode":@"DELETE"},NSSQLitePragmasOption,
                                         nil];
    NSPersistentStore * tempStore = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:db2StoreURL options:options error:&error];
    NSAssert(error == nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
    [psc removePersistentStore:tempStore error:&error];
    NSAssert(error == nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
    tempStore=[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"DB2Conf" URL:villeStoreURL options:db2Options error:&error];
    NSAssert(error == nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
    tempStore=[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"DB1Conf" URL:meteoStoreURL options:db1Options error:&error];
    NSAssert(error == nil, @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
});

}

We have to:

  1. Add the ReadOnly persistent store first, with the lightweight migration options on, and no ReadOnly option nor configuration
  2. Remove this persistent store (???)
  3. Add it again, this time without the lightweight migration options, but with the ReadOnly and the good configuration.
  4. Add the Read/Write Persistent Store

If someone can explain me why this configuration works... Cause i really don't get the point here.