如何高效地合并两个iOS Core Data持久化存储?

12

我们正在开发的应用程序中,使用了带有sqlite后备存储的Core Data来存储数据。我们的应用程序对象模型很复杂。此外,我们应用程序提供的数据量太大,无法适合于iOS(iPhone/iPad/iPod Touch)应用程序包。由于我们的用户通常只对数据的子集感兴趣,因此我们将我们的数据划分为这样一个方式:应用程序与子集一起发布(尽管是大约100 MB的子集)。通过iTunes应用内购买付款后,我们的用户可以选择下载其他数据对象(大小为5 MB到100 MB)。   增量数据文件(存在于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();
           }    
       }
 }

然而,这种方法有两个问题:

  1. 使用数据获取结果(例如NSFetchResultController)时,增量存储URL中的数据将附加到默认存储URL的数据末尾。
  2. 一些对象会被复制。在我们的数据模型中有许多只读数据实体;当我们将第二个persistentStore添加到persistentStoreCoordinator时,它们会被复制。

理想情况下,我们希望Core Data将来自两个persistent stores的对象图合并为一个(下载数据时两个store之间没有共享关系)。此外,我们还希望删除重复的对象。通过搜索网络,我们看到了一些人试图做与我们相同的事情的问题,例如这个答案这个答案。我们已经阅读了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所提出的解决方案。由于我们的数据量可能使我们无法手动读取和重新插入增量数据到默认存储中,我的问题是:什么是将具有相同对象模型的两个persistentStores中的对象图合并成一个persistentStore的最有效方式?

当我们向对象图添加新实体属性或修改关系时,自动迁移效果相当不错。是否有一种简单的解决方案可以将类似的数据合并到同一持久存储中,并且足够强大以停止和恢复 - 就像自动迁移一样?


当我需要Marcus Zarra时他在哪里?我已经使用了[NSPersistentStore migratePersistentStore:toURL:options:withType:error]方法取得了一些进展。我只需要一些清理代码就可以到达我想要的地方了。 - Sunny
我也在苦恼同样的问题。你能发一下你目前想到的吗?我有点迷失了。 - damon
完成了!请告诉我结果如何。 - Sunny
@Sunny,我也遇到了类似的问题,你能在这里帮帮我吗?http://stackoverflow.com/questions/33694048/coredatamigrate-data-from-bundled-db - anoop4real
@anoop4real,我回答了你的问题,请看一下。如果我误解了问题,请告诉我。 - Sunny
3个回答

8
经过多次尝试,我已经知道如何使其正常工作。秘密在于首先创建增量存储数据时不包括只读实体的任何数据。如果在增量存储中没有留下只读数据,则这些数据迁移和合并后这些实体实例将会被复制。因此,应该在不包括这些只读实体的情况下创建增量存储。默认存储将是唯一拥有它们的存储。
例如,我的数据模型中有“国家”和“州/省”实体。我需要在对象图中只有一个“国家”和“州/省”的实例。我将这些实体排除在增量存储之外,并仅在默认存储中创建它们。我使用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
        //
    }
}

你能否对这个解决方案的性能进行评论,以帮助那些正在考虑使用它而不是手动读写数据从一个存储到另一个存储的人? - Rory O'Bryan

1

你的迁移无法工作的原因是托管对象模型是相同的。

从技术上讲,你正在谈论“数据迁移”,而不是“模式迁移”。CoreData的迁移API被设计用于模式迁移,即处理托管对象模型的更改。

至于将数据从一个存储转移到另一个存储,你需要自己去实现逻辑。CoreData可以通过在获取请求上使用批处理和获取限制来帮助你提高效率,但你需要自己实现逻辑。

听起来你有两个持久存储,一个大的和一个小的。最有效的方法是加载小的那个并进行分析,发现你需要在更大的存储中查询的主键或唯一标识符集合。

然后你可以通过简单地查询较大的存储来轻松地完成去重。

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

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


感谢您回答我的问题。从技术上讲,核心数据迁移似乎比模式迁移做得更多。我的问题是要找出界限,并了解如何利用已有的内容来完成工作。我想避免采用蛮力方法——因为这可能会导致难以维护的代码,随着苹果引入越来越多的功能。我已经使用[NSPersistentStore migratePersistentStore::::]方法取得了一些进展。我快要成功了。希望有经验的人能给我建议。 - Sunny

1

您不需要进行任何迁移 - 迁移旨在对NSManagedObjectModel进行更改,而不是数据本身。

你真正需要的是一个Persistent Store Coordinator来管理两个Persistent Stores。这有点棘手,但实际上并不太难。

有一个类似的问题,可以解释你真正需要做什么。 当维护从一个到另一个的关系时,可以使用一个对象模型与多个(两个)持久性存储吗?

这里有一篇由Marcus Zarra撰写的好文章。

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


嗨@Nikita,谢谢你回答我的问题。我相信migrationManager不仅仅是迁移模式。因此,当您向模式添加属性并打开自动迁移时,您的数据会被迁移。关于您使用单个persistentStoreCoordinator的评论,这就是我正在做的。请参见代码片段,从“if(incrementalStoreExists)开始。”您提供的链接没有解决我的问题。我已经在使用多个persistent store并使用单个协调器来管理它们。 - Sunny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接