CoreData с несколькими хранилищами: проблемы конфигурации

У меня есть проект iOS с большой, предварительно загруженной базой данных и небольшой базой данных пользователей (оба хранилища CoreData SQLite). В предыдущих вопросах предлагалось использовать конфигурации для управления тем, какие объекты используются с каким хранилищем. У меня проблемы с тем, чтобы заставить это работать. Вот что я пытался...

- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) return _managedObjectModel;
    // set up the model for the preloaded data
    NSURL *itemURL = [[NSBundle mainBundle] URLForResource:@"FlagDB" withExtension:@"momd"];
    NSManagedObjectModel *itemModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:itemURL];
    // set up the model for the user data
    NSURL *userDataURL = [[NSBundle mainBundle] URLForResource:@"UserData" withExtension:@"momd"];
    NSManagedObjectModel *userDataModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:userDataURL];
    // merge the models
    _managedObjectModel = [NSManagedObjectModel modelByMergingModels:[NSArray arrayWithObjects:itemModel, userDataModel, nil]];
    // define configurations based on what was in each model
WRONG [_managedObjectModel setEntities:itemModel.entities forConfiguration:@"ItemData"];
WRONG [_managedObjectModel setEntities:userDataModel.entities forConfiguration:@"UserData"];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
    // preloaded data is inside the bundle
    NSURL *itemURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"FlagDB.sqlite"];
    // user data is in the application directory
    NSURL *userDataURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"UserData.sqlite"];

    NSManagedObjectModel *mom = self.managedObjectModel;
    NSError *error = nil;
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

    if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData"  URL:itemURL options:nil error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    ...

Это прерывается с помощью "Модель, используемая для открытия магазина, несовместима с моделью, используемой для создания магазина". Проверка хешей в модели на соответствие хешам в хранилище показывает, что они идентичны для сущностей, которые находятся в конфигурации ItemData.

Если я попытаюсь сделать легкую миграцию, вот так:

   NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

   NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
   if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData"  URL:itemURL options:options error:&error])

Сбой с "NSInvalidArgumentException", причина: "Модель не содержит конфигурацию" ItemData "." Я предполагаю, что это потому, что облегченный процесс миграции создает новую модель, и она не содержит мою конфигурацию.

Основываясь на некоторых предложениях в других потоках, я попытался выполнить облегченную миграцию без конфигурации, а затем создать новый координатор, используя конфигурацию. Этот вид работает, но он добавляет таблицы в мой предварительно загруженный файл.sqlite, соответствующий объектам пользовательских данных (которые там не принадлежат), и создает как предварительно загруженные таблицы данных, так и таблицы пользовательских данных во вновь созданном хранилище пользовательских данных., Конечным результатом является то, что выборки не выполняются, по-видимому, потому что они ищут не в том магазине.

NSDictionary *migrationOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

// make a temp persistent store coordinator to handle the migration
NSPersistentStoreCoordinator *tempPsc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
// migrate the stores
if (![tempPsc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:itemURL options:migrationOptions error:&error])
{
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}
if (![tempPsc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:userDataURL options:migrationOptions error:&error])
{
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

// make a permanent store coordinator
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

NSDictionary *readOnlyOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSReadOnlyPersistentStoreOption, nil];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"ItemData"  URL:itemURL options:readOnlyOptions error:&error])
{
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

/*if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:@"UserData" URL:userDataURL options:nil error:&error])
 {
 NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
 abort();
 }*/

А потом позже...

    OSAppDelegate *delegate = [UIApplication sharedApplication].delegate;
    NSManagedObjectContext *context = delegate.managedObjectContext;
    // sanity check
    for (NSPersistentStore *store in context.persistentStoreCoordinator.persistentStores) {
        NSLog(@"store %@ -> %@", store.configurationName, store.URL);
        NSMutableArray *entityNames = [[NSMutableArray alloc] init];
        for (NSEntityDescription *entity in [context.persistentStoreCoordinator.managedObjectModel entitiesForConfiguration:store.configurationName]) {
            [entityNames addObject:entity.name];
        }
        NSLog(@"entities: %@", entityNames);
    }

    NSFetchRequest *categoryFetchRequest = [[NSFetchRequest alloc] init];
    categoryFetchRequest.entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:context];
    categoryFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", categoryName];
    NSError *error = nil;
    Category *category = [[delegate.managedObjectContext executeFetchRequest:categoryFetchRequest error:&error] lastObject];

Это работает нормально, возвращая объект категории с соответствующим именем, пока я не раскомментирую добавление второго хранилища. Если я это сделаю, результат извлечения вернется пустым. Диагностические сообщения NSLog выводят именно то, что я ожидаю. Каждое хранилище связано с правильной конфигурацией, и каждая конфигурация имеет соответствующие объекты.

Может кто-нибудь указать мне на исходный код для настройки работы нескольких магазинов или подсказать, что я делаю неправильно? Заранее спасибо!


РЕШЕНО: Суть проблемы заключалась в двух строках, помеченных как НЕПРАВИЛЬНЫЕ в первом листинге кода. Я пытался создавать конфигурации программно, но этого недостаточно. Если после этого вы запросите у ManagedObjectModel конфигурации, вы действительно увидите конфигурации в списке, и с этими конфигурациями будут связаны правильные объекты. Тем не менее, кажется, что нужно сделать что-то еще, чтобы PersistentStoreCoordinator мог правильно их использовать. Создание конфигураций в XCode заставляет их работать.


СЛЕДУЙТЕ ЗА: Есть дополнительная загвоздка. Решение запустить отдельный этап миграции перед настройкой окончательного координатора постоянного хранилища отлично работает... в симуляторе. На реальном устройстве разрешения более строгие. Если вы попытаетесь выполнить эту миграцию, произойдет сбой, поскольку хранилище в комплекте приложений доступно только для чтения. Миграция кажется необходимой, если вы не консолидируете свои модели. Если у вас есть только одна модель, и хранилище в комплекте приложений совместимо с ней, миграция не требуется, и доступ с использованием конфигураций, определенных в XCode, работает.

Другим вариантом может быть перемещение данных в каталог "Документы" перед попыткой миграции. Я не проверял, что этот подход работает.

1 ответ

Решение

Вы пробовали, чтобы обе конфигурации были определены в одной и той же модели (то есть в том же momd)? Вы можете легко сделать это, выбрав "Редактор-> Добавить конфигурацию" во время редактирования одной из ваших моделей данных. Перетащите объекты для UserData и ItemData в соответствующую конфигурацию. Конфигурация, указанная таким образом, соответствует базовым данным. дело не в имени файла /URL. После того как вы сделали вышеупомянутое, упростите свой _managedObjectModel, приведенный выше, чтобы искать один файл /URL momd при каждом его вызове.

В качестве альтернативы, если вы решили сохранить два отдельных файла momd, убедитесь, что вы на самом деле определили свои модели в конфигурациях с именами "UserData" и "ItemData" соответственно в их файлах определения модели.

Мое первоначальное предложение - сохранить один файл модели. Если не существует причины, по которой эти конфигурации не могут находиться в одной и той же объектной модели, не имеет смысла усложнять вещи несколькими файлами. Я думаю, что было бы довольно сложно уловить Базовые Данные для выполнения того, что вы принесли, чтобы сделать выше. Попробуйте упростить часть моделирования вашего кода.

Другие вопросы по тегам