10

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

{ 
       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(); 
           }    
       } 
} 

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

  1. выборки данных результатов (например, с NSFetchResultController) показывают, с данными из incrementalStoreURL, приложенном к концу данных из defaultStoreURL.
  2. Некоторые объекты дублируются. В нашей модели данных имеется много объектов с данными только для чтения; они дублируются, когда мы добавляем второй постоянный сервер в постоянныйStoreCoordinator .

В идеале мы хотели бы, чтобы Core Data объединил графические объекты из двух постоянных хранилищ в один (нет общих отношений между данными из двух хранилищ во время загрузки данных). Кроме того, мы хотели бы удалить дубликаты объектов. Поиск в Интернете, мы видели пару вопросов от людей, пытающихся сделать то же самое, что мы делаем - например, this answer и this answer. Мы читали Marcus Zarra's blog on importing large data sets in 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), чтобы объединиться в один постоянный хранилище?

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

+0

Где Маркус Зарра, когда он мне нужен? Я сделал некоторый прогресс, используя метод [NSPersistentStore migratePersistentStore: toURL: options: withType: error]. Мне просто нужно еще немного очистить код, чтобы добраться туда, где я должен быть. – Sunny

+0

Я борюсь с тем же. Можете ли вы опубликовать то, что вы придумали? Я потерялся. – damon

+0

Сделано! Дайте мне знать, как это получается для вас. – Sunny

ответ

6

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

Например, у меня были сущности «Страна» и «Состояние» в моей модели данных. Мне нужно было иметь только один экземпляр страны и государства в моем объектном графе. Я сохранил эти объекты из инкрементных магазинов и создал их только в хранилище по умолчанию. Я использовал 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 
     // 
    } 
} 
+0

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

1

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

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

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

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

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

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

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

+0

Спасибо, что ответили на мой вопрос. Технически, основная миграция данных, как представляется, делает больше, чем миграция схемы. Мой вопрос пытается выяснить, где находится линия, и как я могу воспользоваться тем, что уже есть, чтобы выполнить эту работу. Я хочу избежать грубой силы - так как это, вероятно, приведет к сложному поддержанию кода, поскольку Apple вводит все больше и больше функций. Я сделал некоторый прогресс, используя метод [NSPersistentStore migratePersistentStore ::::]. Я почти там. Я желаю, чтобы кто-то с опытом делал то, что я пытаюсь сделать, мог бы посоветовать мне. – Sunny

1

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

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

Существует аналогичный вопрос, который может объяснить вам, что вам действительно нужно делать. Can multiple (two) persistent stores be used with one object model, while maintaining relations from one to the other?

Вот хороший arcticle Маркус Zarra

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

+0

Привет @ Никита, спасибо за ответ на мой вопрос. Я считаю, что migrationManager выполняет больше, чем перенос схемы. Следовательно, ваши данные будут перенесены при добавлении атрибута в вашу схему и включении автоматической миграции. Что касается вашего комментария об использовании одного persistentStoreCoordinator, это то, что я делаю. См. Фрагмент кода, начинающийся с "if (incrementalStoreExists) {." Предоставленные вами ссылки не касались моей проблемы. Я уже использую несколько постоянных хранилищ и использую один координатор для управления ими. – Sunny