【问题标题】:What is an efficient way to Merge two iOS Core Data Persistent Stores?合并两个 iOS 核心数据持久存储的有效方法是什么?
【发布时间】:2012-04-15 17:45:33
【问题描述】:

在我们正在开发的应用程序中,我们使用 Core Data 和 sqlite 后备存储来存储我们的数据。我们应用程序的对象模型很复杂。此外,我们的应用程序提供的数据总量太大,无法放入 iOS(iPhone/iPad/iPod Touch)应用程序包中。由于我们的用户通常只对数据的一个子集感兴趣,因此我们以这样一种方式对数据进行了分区,即应用程序在应用程序包。在通过 iTunes 应用内购买支付额外内容后,我们的用户可以选择从我们的服务器下载额外的数据对象(大小约为 5 MB 到 100 MB)。 增量数据文件(存在于 sqlite 后备存储中)使用与捆绑包附带的数据相同的 xcdatamodel 版本;对象模型的变化为零。增量数据文件作为 gzip 压缩的 sqlite 文件从我们的服务器下载。我们不希望通过随应用程序提供增量内容来膨胀我们的应用程序包。此外,我们不想依赖对 web 服务的查询(因为复杂的数据模型)。 我们已经测试了从我们的服务器下载增量 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. 有些对象是重复的。有很多实体 我们数据模型中的只读数据;当我们添加时,这些会重复 第二个persistentStore 到persistentStoreCoordinator。

理想情况下,我们希望 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 和 2 所建议的解决方案。数据的大小可能会阻止我们手动读取增量数据并将其重新插入到默认存储中。我的问题是:从两个持久存储(具有相同的 objectModel)中获取对象图以合并到一个持久存储中的最有效方法是什么?

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

【问题讨论】:

  • 当我需要 Marcus Zarra 时他在哪里?我使用 [NSPersistentStore migratePersistentStore:toURL:options:withType:error] 方法取得了一些进展。我只需要更多的清理代码就可以到达我需要的地方。
  • 我也在为同样的事情苦苦挣扎。你能发布你到目前为止的想法吗?我迷路了。
  • 完成!让我知道结果如何。
  • @Sunny 我也面临一些类似的问题,你能帮我看看吗stackoverflow.com/questions/33694048/…
  • @anoop4real,我发布了你的问题的答案,看看。接受答案,如果我误解了问题,请告诉我。

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


【解决方案1】:

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

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

至于将数据从一家商店传输到另一家商店,您有点靠自己。 CoreData 可以通过对获取请求使用批处理和获取限制来帮助您提高效率,但您需要自己实现逻辑。

听起来你有两个持久存储,一个大的和一个小的。加载小型存储并对其进行分析,发现您需要在较大存储中查询的一组主键或唯一标识符是最有效的。

您可以通过简单地在更大的存储中查询这些标识符来轻松地进行重复数据删除。

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

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

【讨论】:

  • 感谢您回答我的问题。从技术上讲,核心数据迁移似乎比模式迁移做得更多。我的问题是试图弄清楚线路在哪里,以及我如何利用已有的东西来完成这项工作。我想避免蛮力——因为随着 Apple 引入越来越多的功能,这可能会导致代码难以维护。我使用 [NSPersistentStore migratePersistentStore::::] 方法取得了一些进展。我快到了。我希望有经验做我想做的事情的人能给我建议。
【解决方案2】:

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

您真正需要的是管理两个 Persistent Store 的 Persistent Store Coordinator。这有点棘手,但不是太难,真的。

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

这是 Marcus Zarra 的一篇好文章

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

【讨论】:

  • 嗨@Nikita,感谢您回答我的问题。我相信migrationManager 不仅仅是迁移模式。因此,当您将属性添加到架构并打开自动迁移时,您的数据会被迁移。关于您关于使用单个 persistentStoreCoordinator 的评论,这就是我正在做的事情。请参阅从“if (incrementalStoreExists) {”开始的代码片段。您提供的链接没有解决我的问题。我已经在使用多个持久存储并使用单个协调器来管理它们。
【解决方案3】:

经过几次尝试,我已经弄清楚了如何进行这项工作。秘诀是首先为只读实体创建没有任何数据的增量存储数据。如果不将只读数据留在增量存储中,这些实体实例将在数据迁移和合并后复制。因此,应该在没有这些只读实体的情况下创建增量存储。默认商店将是唯一拥有它们的商店。

例如,我的数据模型中有实体“Country”和“State”。我只需要在我的对象图中有一个 Country 和 State 实例。我将这些实体保留在增量存储之外,并且仅在默认存储中创建它们。我使用 Fetched Properties 将我的主要对象图松散地链接到这些实体。我使用模型中的所有实体实例创建了默认存储。增量存储要么没有只读实体(在我的例子中为 Country 和 State),要么在数据创建完成后删除它们。

下一步是在应用程序启动期间将增量存储添加到它自己的 persistentStoreCoordinator(与我们要将所有内容迁移到的默认存储的协调器不同)。

最后一步是在增量存储上调用 migratePersistentStore 方法以将其数据合并到主(即默认)存储。快!

以下代码片段说明了我上面提到的最后两个步骤。我执行这些步骤是为了使我的设置能够将增量数据合并到主数据存储中。

{
    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
        //
    }
}

【讨论】:

  • 您能否评论此解决方案的性能,以帮助正在考虑此选项的任何人,而不是手动从一个商店读取数据并将数据写入另一个商店?
猜你喜欢
  • 2015-02-03
  • 2023-03-07
  • 1970-01-01
  • 2012-12-20
  • 1970-01-01
  • 1970-01-01
  • 2011-08-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多