Что такое эффективный способ слияния двух постоянных хранилищ данных iOS Core?

В нашем разрабатываемом приложении мы используем Core Data с хранилищем sqlite для хранения наших данных. Объектная модель для нашего приложения сложна. Кроме того, общий объем данных, обслуживаемых нашим приложением, слишком велик, чтобы вписаться в комплект приложений для iOS (iPhone/iPad/iPod Touch). Из-за того, что наши пользователи, как правило, заинтересованы только в подмножестве данных, мы разделили наши данные таким образом, что приложение поставляется с подмножеством (хотя и около 100 МБ) объектов данных в приложение. Наши пользователи имеют возможность загружать дополнительные данные (размером от 5 МБ до 100 МБ) с нашего сервера после того, как они платят за дополнительное содержимое через покупки iTunes в приложении. Инкрементные файлы данных (существующие в хранилищах хранилища sqlite) используют ту же версию xcdatamodel, что и данные, поставляемые вместе с пакетом; в объектной модели нулевые изменения. Инкрементные файлы данных загружаются с нашего сервера в виде файлов с поддержкой gzipped sqlite. Мы не хотим раздувать наш пакет приложений, отправляя инкрементное содержимое с помощью приложения. Кроме того, мы не хотим полагаться на запросы через webservice (из-за сложной модели данных). Мы протестировали загрузку инкрементных данных sqlite с нашего сервера. Мы смогли добавить загруженное хранилище данных в общедоступное приложение persistentStoreCoordinator.

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

       if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:defaultStoreURL options:options error:&error])
       {            
           NSLog(@"Failed with error:  %@", [error localizedDescription]);
           abort();
       }    

       // Check for the existence of incrementalStore
       // Add incrementalStore
       if (incrementalStoreExists) {
           if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:incrementalStoreURL options:options error:&error])
           {            
               NSLog(@"Add of incrementalStore failed with error:  %@", [error localizedDescription]);
               abort();
           }    
       }
 }

Однако есть две проблемы с этим.

  • Результаты выборки данных (например, с помощью NSFetchResultController) отображаются с данные от incrementalStoreURL, добавленные к концу данных из defaultStoreURL.
  • Некоторые объекты дублируются. Существует множество объектов с данные только для чтения в нашей модели данных; они дублируются, когда мы добавляем второй persistentStore для persistentStoreCoordinator.

В идеале мы хотели бы, чтобы Core Data объединил графы объектов из двух постоянных хранилищ в один (нет общих отношений между данными из двух хранилищ во время загрузки данных). Кроме того, мы хотели бы удалить дубликаты объектов. В Интернете мы увидели пару вопросов от людей, пытающихся сделать то же самое, что и мы, - этот ответ и этот ответ. Мы читаем Блог Marcus Zarra по импорту больших наборов данных в Core Data. Однако ни одно из решений, которые мы видели, не сработало для нас. Мы не хотим вручную считывать и сохранять данные из инкрементного хранилища в хранилище по умолчанию, поскольку мы считаем, что это будет очень медленным и подверженным ошибкам на телефоне. Есть ли более эффективный способ слияния?

Мы попытались решить проблему, выполнив ручную миграцию следующим образом. Однако нам не удалось успешно добиться слияния. Мы не совсем поняли решение, предложенное в ответах 1 и 2, упомянутых выше. Блог Marcus Zarra затронул некоторые из проблем, которые мы имели в начале нашего проекта, импортируя наш большой набор данных в iOS.

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

       NSMigrationManager *migrator = [[NSMigrationManager alloc] initWithSourceModel:__managedObjectModel destinationModel:__managedObjectModel];
       if (![migrator migrateStoreFromURL:stateStoreURL
                                type:NSSQLiteStoreType 
                             options:options 
                    withMappingModel:nil
                    toDestinationURL:destinationStoreURL 
                     destinationType:NSSQLiteStoreType 
                  destinationOptions:nil 
                               error:&error])
       {
           NSLog(@"%@", [error userInfo]);
           abort();
       }
}

Кажется, что автор ответа 1 закончил чтение своих данных из инкрементного хранилища и сохранение в хранилище по умолчанию. Возможно, мы неправильно поняли решение, предложенное в обеих статьях 1 и 2. Размер наших данных может помешать нам вручную считывать и повторно вставлять наши инкрементные данные в хранилище по умолчанию. Мой вопрос: какой самый эффективный способ получить графы объектов из двух постоянных хранилищ (имеющих один и тот же объектModel), чтобы объединиться в один постоянный хранилище?

Автоматическая миграция работает очень хорошо, когда мы добавляем новые атрибуты сущностей к графам объектов или изменяем отношения. Есть ли простое решение для слияния подобных данных с одним и тем же постоянным хранилищем, которое будет достаточно устойчивым, чтобы останавливаться и возобновляться - как автоматическая миграция выполняется?

Ответ 1

После нескольких попыток я выяснил, как это сделать. Секрет состоит в том, чтобы сначала создать инкрементные данные хранилища без каких-либо данных для объектов, доступных только для чтения. Не оставляя данные только для чтения из инкрементных хранилищ, экземпляры объектов для них будут дублироваться после переноса данных и слияния. Следовательно, инкрементные хранилища должны создаваться без этих объектов только для чтения. Хранилище по умолчанию будет единственным хранилищем, в котором они есть.

Например, у меня были сущности "Страна" и "Государство" в моей модели данных. Мне нужно было иметь только один экземпляр страны и государства в моем объектном графе. Я сохранил эти объекты из инкрементных магазинов и создал их только в хранилище по умолчанию. Я использовал Fetched Properties, чтобы свободно связать мой основной граф объектов с этими объектами. Я создал хранилище по умолчанию со всеми экземплярами сущности в моей модели. Инкрементные хранилища либо не имели сущности, доступные только для чтения (например, страна и состояние в моем случае), чтобы начать или удалить их после завершения создания данных.

Следующим шагом является добавление инкрементного хранилища в его собственный persistentStoreCoordinator (не такой как координатор для хранилища по умолчанию, который мы хотим перенести во все содержимое) во время запуска приложения.

Последний шаг - вызвать метод migratePersistentStore в инкрементном хранилище, чтобы объединить свои данные в основное хранилище (то есть по умолчанию). Presto!

Следующий фрагмент кода иллюстрирует последние два этапа, упомянутые выше. Я сделал эти шаги, чтобы моя настройка объединила инкрементные данные в основное хранилище данных для работы.

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

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:defaultStoreURL options:options error:&error])
    {            
        NSLog(@"Failed with error:  %@", [error localizedDescription]);
        abort();
    }    

    // Check for the existence of incrementalStore
    // Add incrementalStore
    if (incrementalStoreExists) {

        NSPersistentStore *incrementalStore = [_incrementalPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:incrementalStoreURL options:options error:&error];
        if (!incrementalStore)
        {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }    

        if (![_incrementalPersistentStoreCoordinator migratePersistentStore:incrementalStore
            toURL:_defaultStoreURL
            options:options
            withType:NSSQLiteStoreType
            error:&error]) 
        {
            NSLog(@"%@", [error userInfo]);
            abort();

        }

        // Destroy the store and store coordinator for the incremental store
        [_incrementalPersistentStoreCoordinator removePersistentStore:incrementalStore error:&error];
        incrementalPersistentStoreCoordinator = nil;
        // Should probably delete the URL from file system as well
        //
    }
}

Ответ 2

Причина, по которой ваша миграция не работает, связана с тем, что модель управляемых объектов идентична.

Технически вы говорите о "миграции данных", а не "миграции схемы". API миграции CoreData предназначен для миграции схемы, который обрабатывает изменения в модели управляемых объектов.

Что касается передачи данных из одного магазина в другой, вы сами по себе. CoreData может помочь вам быть эффективными, используя ограничения дозирования и выборки для ваших запросов на выборку, но вам нужно реализовать логику самостоятельно.

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

Затем вы можете легко обмануть, просто запросив более крупный магазин для этих идентификаторов.

В документации для NSFetchRequest есть API для определения ваших запросов:

https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html

Ответ 3

Вам не нужна миграция - миграция предназначена для внесения изменений в NSManagedObjectModel, а не в сами данные.

Что вам действительно нужно, так это координатор постоянных хранилищ, управляющий двумя постоянными хранилищами. Это немного сложно, но не слишком сложно, правда.

Там похожий вопрос, который может объяснить вам, что вам действительно нужно делать. Можно ли использовать несколько (два) постоянных хранилища с одной объектной моделью, сохраняя при этом отношения от одного к другому?

Вот хорошая статья Маркуса Зарра

http://www.cimgf.com/2009/05/03/core-data-and-plug-ins/