【问题标题】:Core Data - mixing lightweight and custom migrationCore Data - 混合轻量级和自定义迁移
【发布时间】:2014-10-06 17:57:35
【问题描述】:

所以我在 App Store 中有我的 Core Data 应用程序的第 1 版,现在我开始使用第 2 版。

我对我的数据库模型进行了一些小的更改,并且在这些更改之后我需要运行一些自定义代码以完成从版本 1 到版本 2 的升级。

我可以使用 Core Data 的轻量级迁移来处理模型更改,并且可以在迁移完成后运行我的自定义代码。

问题是,我不确定将来会发生什么,什么时候我会构建版本 3、4、5...

假设是这样:
版本 1 到版本 2 - 使用轻量级迁移
版本 2 到版本 3 - 使用带有模型映射的自定义迁移
版本 3 到版本 4 - 再次使用轻量级迁移
等等……

我不确定如何构建一种机制来处理这种轻量级和自定义迁移的混合。
我在网上或 Core Data 文档中找不到任何讨论此问题的代码,我的意思是对于大多数 Core Data 应用程序来说,这是一个非常常见的问题,是否有针对此问题的最佳实践示例?

【问题讨论】:

    标签: ios objective-c core-data core-data-migration


    【解决方案1】:

    下面写了两个方法:

    1. 我个人喜欢这种方法,因为它只是 IMO 的天才,你可以从我的详细解释中看出为什么我对这种方法如此兴奋。这种方法已经写在 Marcus Zarra 的核心数据第二版书中。它是一种自动渐进式迁移过程的实现,适用于重度和轻量级使用。

    2. 在 OP 询问是否有办法避免每次迁移升级都必须使用映射模型后,我提出了一种可能实现的方法

    方法一 这就是渐进式迁移的用武之地。您为每个版本升级创建映射模型,这将在两个模型版本之间进行处理。映射模型将告诉迁移管理器如何将旧版本的数据存储映射到新版本的数据存储。所以映射模型需要sourcedestination

    然后,您将拥有涵盖所有版​​本升级增量所需的尽可能多的映射模型。因此,如果您有 4 个版本,您将拥有 3 个映射模型,这将有助于在每个版本升级之间进行转换。最后你会得到他们的名字:V1toV2MappingModelV2to3MappingModelV3toV4MappingModel


    然后,您可以在每个映射模型中切换选项,让迁移管理员知道您希望如何迁移。无论是通过自定义策略还是正常的副本迁移。因此,您说对于 v1 到 v2,您需要轻量级迁移,只需选择适当的映射模型,然后使用模型/映射编辑器在旧属性到新属性之间建立所需的连接/映射,如果需要自定义迁移,然后转到正确的映射模型,选择您希望为其自定义迁移的产品实体映射,然后在 entitymapping 检查器中,您将看到您可以应用自定义迁移策略,只需复制您的迁移名称政策说例如MigrationPolicyV2toV3,因为它是您想要自定义的特定版本。

    所以在上图中,您可以在左侧的映射模型名称中看到它适用于版本 1 到版本 2。请注意,ProductToProduct 实体映射的属性映射为空 - 在中间.这是因为如果您在entity mapping 检查器中查看图像的右侧,其中显示Custom Policy,我已经输入了我的迁移策略的名称。然后,这让迁移管理器知道(在迁移过程中)如何将属性和值从源映射到目标 - 版本 1 到版本 2 - 由于输入的迁移策略,它知道这一点。这也会导致值type 更改为自定义。让您知道当涉及到 ProductToProduct 实体映射时,它将是一个自定义迁移。

    然后,您必须在迁移策略中定义,这将确定您希望如何复制这些值。这是您进行自定义操作的地方。

    从上图中可以看出,它是我为ProductToProduct 实体映射设置的自定义迁移策略。你会注意到我实际上并没有做任何自定义,所有这一切都可以在没有迁移策略的情况下完成,并且可以通过调整entityMapping inspector 中的一些值来简单地进行复制迁移(轻量级迁移)和调整之前图像中的Attribute mapping 值。我只是将所有这些迁移政策自定义内容作为练习,以便我可以学习并为未来做好准备,以防万一我需要进行大量迁移。最好现在学习它,而不是以后嘿;)

    这就是做定制的东西。我建议您阅读以下apple developer reference on NSEntityMigrationPolicy 以及执行更多自定义操作所需的方法,以便您知道在特定版本升级需要部分或完全自定义迁移时如何在整个迁移过程中进行完全控制。


    对于任何自定义/重量级迁移 - 在我的情况下,我使用 migration policy 以便我可以在我的数据存储中从 V2 迁移到 V3 期间执行一些自定义代码的东西 - 然后你创建一个称为“迁移”的东西策略”,以便该映射模型将遵守您指定的自定义迁移规则。

    您只需为每个映射模型应用所有适当的转换/映射,以便迁移管理器知道如何从一个存储升级到下一个存储。

    您只需要一些递归代码,它会查看现有商店的元数据以确定它是否与最新版本兼容,如果不兼容,它将自动为您执行递归迁移,遵循每个映射模型的规则,因为它通过版本升级,直到商店与当前版本保持同步。

    因此,您可以通过这种方式为所有用户提供任何版本,并让他们快速了解当前版本的商店。因此,如果用户处于版本 1,它将递归地从 V1 转到 V2,然后一直迁移到 v3 直到您当前的版本。如果用户有任何其他版本,这同样适用。

    这确实意味着迁移过程需要更长的时间,但这是迁移所有用户的好方法,无论他们拥有哪个版本。

    要找到这个渐进式迁移代码,你需要阅读一本名为Core Data 2nd Edition - Data storage and management for iOS, OS X, and iCloud 的书,第 3 章Versioning and Migration 子章3.6 Progressive Data Migration (An Academic Exercise) 来自页面54 to 59

    他通过代码与您交谈,并逐步告诉您如何编写progressivelyMigrateURL:ofType:toModel:error:方法。与他一起编写方法后,他会告诉您如何在应用程序启动时调用此方法,以便您的用户也可以逐步自动迁移他们的商店。

    所以你应该先写方法,然后按照我上面的步骤,或者你可以阅读之前的子章节中的迁移策略。

    在过去的 48 小时内,我几乎学会了所有这些,现在已经全部启动并运行。能够为某些版本进行轻量级迁移,然后为我的其他版本自动完成自定义迁移。

    方法 2 - 另一种解决方案 尽管 IMO 更麻烦:您始终可以使用轻量级迁移设置,但显然您可以通过轻量级迁移实现甚至复杂的情况。但是在特定版本升级需要大量迁移的情况下,您始终可以执行 if 语句来检查当前存储,并且当且仅当持久存储的当前版本与需要大量迁移的升级匹配时,然后,您告诉迁移管理器执行大量迁移并遵循仅针对该实例的迁移策略的映射模型,然后如果要进行更多版本升级以使持久存储到最新的模型版本,则恢复轻量级迁移.

    【讨论】:

    • +1 非常感谢您的详细回答。几个问题: 1.你写的V2toV4MappingModel,应该是V2toV3MappingModel吧? 2.在第三段你写的我应该在映射模型中设置轻量级迁移。你是什​​么意思?据我所知,轻量级迁移是通过在持久存储选项中设置两个标志来完成的,不是吗?那么如何使用映射模型进行轻量级迁移呢? 3. 如果你能分享你的运行示例,或者一些专门关于混合轻量级迁移和自定义迁移的代码,我将非常感激。谢谢
    • Question 1. you wrote V2toV4MappingModel, it should be V2toV3MappingModel right? 答案:正确。在我刚刚花了最后 58 小时解决核心数据问题之后,我打字速度太快了,无法尝试帮助你,那时我学到了我在这个答案中与你分享的所有这些信息。我想尽快把它发给你,以免你头疼;)哈哈
    • 我认为我们在说“轻量级迁移”时都有不同的含义。如果我错了,请纠正我,但您认为这只是映射模型迁移的一个简单案例,我正在谈论轻量级和使用映射模型之间的技术差异。如核心数据文档中所述的轻量级(在创建 psc 时仅使用两个标志),与映射模型相比具有性能优势,例如,无需将对象带到内存并复制它们。因此,我想尽可能利用这种方法,即使在不需要时也不为每次迁移使用映射模型。
    • 您可以以低于 40.00 美元的价格购买这本书的电子版,而且不包含运费。看起来你在这里花了更多时间问这个问题而不是书的价值;-)
    • 嘿@MarcusS.Zarra,使用我在您的渐进式迁移-(学术练习)子章节中使用的方法,现在可以说对于版本 4-5只是做一个lightweight 迁移? Atm,我创建了一个名为MappingModelV4toV5 的映射模型,即使没有自定义代码。有人告诉我,我将所有内容都放入内存中,以便从 V4toV5 迁移的这一部分,而这只需要轻量级迁移。我解释说使用 Marcus 的设置,每个版本升级都需要一个映射模型`轻量级或不否则代码会中断 对吗?续。
    【解决方案2】:

    如果您可以从服务器或类似设备重新获取数据,最好的处理方法是删除旧模型并立即使用当前结构重新创建一个新模型。这嵌入在单例类中,每次必须创建新的 managedobjectcontext 时都会调用它。我的代码如下:

    - (NSManagedObjectContext *)managedObjectContext {
        if (__managedObjectContext != nil) {
            return __managedObjectContext;
        }
    
        NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
        if (coordinator != nil) {
            __managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            [__managedObjectContext setPersistentStoreCoordinator:coordinator];
        }
        return __managedObjectContext;
    }
    
    - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
        if (__persistentStoreCoordinator != nil) {
            return __persistentStoreCoordinator;
        }
    
        NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"YOURDB.sqlite"];
    
        NSError *error = nil;
        __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
        if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
            if (error.code == 134100) {
                if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
                    NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
                    if ( !existingPersistentStoreMetadata ) {
                        // Something *really* bad has happened to the persistent store
                        [NSException raise: NSInternalInconsistencyException format: @"Failed to read metadata for persistent store %@: %@", storeURL, error];
                    }
    
                    if ( ![[self managedObjectModel] isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
                        if (![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] ) {
                            NSLog(@"*** Could not delete persistent store, %@", error);
                            abort();
                        } else {
                            [__persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeURL options: nil error: &error];
                        }
                    }
                }
            } else {
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
        }
    
        return __persistentStoreCoordinator;
    }
    

    【讨论】:

    • 这是一个好主意,特别是如果您可以从服务器获取数据。但是,如果需要获取数千行数据,那么如果在 3g 连接上,用户将不得不等待几分钟让所有数据到达。我有一个正在运行的应用程序,我的客户必须等待那么长时间,这就是我合并 Core Data 的原因,这样他就不需要每次升级商店时都获取数据。我认为当没有大量数据要从服务器获取并且你没有保存任何用户数据(除非它存储在服务器 2 上)时,你的方法会非常有效
    【解决方案3】:

    最简单的方法是将旧属性保留在新版本中,不再使用它们。如果您需要将它们映射到新属性,只需在轻量级数据库升级后进行映射。世界将保持和平。

    好处:

    1. 无需制作映射模型。
    2. 继续使用轻量级升级。

    让我举个例子: 假设我们在 V1 中有一个名为 Student 的实体,其属性为 name,我们将其映射到 V2 中的新属性 firstName。 我们有一个方法如下,那么我们就可以在常规的轻量级CoreData升级之后调用这个方法,享受吧!

    - (void)migrateStudentRecords {
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Student"];
        NSArray *result = [defaultContext executeFetchRequest:fetchRequest error:error];
        if (result && result.count > 0) {
            [result enumerateObjectsUsingBlock:^(Student  *_Nonnull newRecord, NSUInteger idx, BOOL * _Nonnull stop) {
                newRecord.firstName = newRecord.name;
                newRecord.name = nil;
            }];
            if (defaultContext.hasChanges) {
                [defaultContext save:error];
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-22
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      • 2011-01-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多