合并两个iOS核心数据持久存储的有效方法是什么?

时间:2012-04-02 02:02:12

标签: objective-c core-data merge core-data-migration object-graph

在我们正在开发的应用程序中,我们使用Core Data和sqlite后备存储来存储我们的数据。我们的应用程序的对象模型很复杂。此外,我们的应用程序提供的数据总量太大,无法容纳到iOS(iPhone / iPad / iPod Touch)应用程序包中。由于我们的用户通常只对数据的一个子集感兴趣,因此我们对数据进行了分区,使得应用程序附带了一个子集(尽管大约100 MB)的数据对象。应用包。我们的用户可以选择在通过iTunes应用内购买支付额外内容后,从我们的服务器下载其他数据对象(大小约为5 MB到100 MB)。 增量数据文件(存在于sqlite后备存储中)使用与bundle附带的数据相同的xcdatamodel版本;对象模型没有任何变化。增量数据文件作为gzip 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();
           }    
       }
 }

但是,这样做有两个问题。

  1. 数据获取结果(例如,使用NSFetchResultController)显示 来自incrementalStoreURL的数据附加到了末尾 来自defaultStoreURL的数据。
  2. 某些对象是重复的。有很多实体 我们的数据模型中的只读数据;当我们添加时,这些会重复 第二个持久存储到persistentStoreCoordinator。
  3. 理想情况下,我们希望Core Data将两个持久存储中的对象图合并为一个(数据下载时两个存储中的数据之间没有共享关系)。此外,我们想删除重复的对象。 在网上搜索时,我们看到了一些人试图做同样事情的问题 - 例如this answerthis 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条和第1条所建议的解决方案。 2.我们的数据大小可能会阻止我们手动读取和重新插入我们的增量数据到默认存储中。我的问题是:从两个持久存储(具有相同的objectModel)中获取对象图的最有效方法是什么才能合并到一个persistentStore中?

    当我们向对象图添加新的实体属性或修改关系时,自动迁移非常有效。是否有一个简单的解决方案将类似的数据合并到同一个持久存储中,该存储具有足够的弹性以便停止和恢复 - 自动迁移完成后?

3 个答案:

答案 0 :(得分:6)

经过多次尝试,我已经想出了如何使这项工作。秘诀是首先创建增量存储数据,而不包含只读实体的任何数据。如果不从增量存储中删除只读数据,则在数据迁移和合并之后,这些实体的实体实例将会重复。因此,应该在没有这些只读实体的情况下创建增量存储。默认商店将是唯一拥有它们的商店。

例如,我有实体"国家"和"州"在我的数据模型中。我需要在对象图中只有一个Country和State实例。我将这些实体保留在增量存储之外,并仅在默认存储中创建它们。我使用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
        //
    }
}

答案 1 :(得分:1)

迁移不起作用的原因是托管对象模型完全相同。

从技术上讲,你所说的是“数据迁移”而不是“模式迁移”。 CoreData的迁移API专为模式迁移而设计,即处理对托管对象模型的更改。

就将数据从一个商店转移到另一个商店而言,您可以自己独立。 CoreData可以通过对获取请求使用批处理和获取限制来帮助您提高效率,但您需要自己实现逻辑。

听起来你有两个持久性商店,一个大商店和一个小商店。加载小的并进行分析,发现在较大的商店中需要查询的主键集或唯一标识符是最有效的。

然后,您只需查询较大的商店以获取这些标识符即可轻松删除。

NSFetchRequest的文档具有用于确定查询范围的API:

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

答案 2 :(得分:1)

您不需要任何迁移 - 迁移旨在带来NSManagedObjectModel的更改,而不是数据本身。

你真正需要的是一个管理两个持久商店的Pesristent商店协调员。这有点棘手,但并不太难,真的。

有一个类似的问题,可以解释你,你真正需要做的事情。 Can multiple (two) persistent stores be used with one object model, while maintaining relations from one to the other?

这是Marcus Zarra的一个很好的arcticle

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