cocoa - 核心数据迁移: exceptions after changing a relationship from one entity to its parent entity

标签 cocoa core-data core-data-migration

我有一个核心数据模型,其中包含一个 Car 实体和一个 EngineData 实体。这是一对一的关系。

在我的应用程序的新版本中,我想添加卡车。因此,我创建了核心数据模型的新版本。我现在有一个 Vehicle 实体,它是 Car 的父实体。我添加了一个新的 Truck 实体,该实体也将 Vehicle 作为其父实体。对于 EngineData,反向关系通常称为 object,因此目标实体只是从 Car 更改为 Vehicle

我并不完全确定这是否适用于轻量级迁移,但我在几周前第一次更改了它,直到现在它看起来都很好。我有代码使用 CarengineData 属性从 EngineData 获取和更新现有数据,但我没有发现任何问题。但是,我的应用程序中有一个核心数据获取每次都会导致崩溃。我所要做的就是简单地获取我的所有 EngineData 对象来导致崩溃:

do {
    let request: NSFetchRequest<EngineData> = EngineData.fetchRequest()
    let objects = try context.fetch(request)
} catch {
    NSLog("Error fetching data: \(error)")
}

context.fetch 行上,我收到一个异常:

[error] error: Background Core Data task threw an exception.  Exception = *** -[__NSArrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0 .. 10] and userInfo = (null)
CoreData: error: Background Core Data task threw an exception.  Exception = *** -[__NSArrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0 .. 10] and userInfo = (null)

如果我尝试对这些对象实际执行任何操作,我会遇到更多异常,直到应用程序崩溃:

[General] An uncaught exception was raised
[General] *** -[__NSArrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0 .. 10]

0   CoreFoundation                      0x00007fff8861937b __exceptionPreprocess + 171
1   libobjc.A.dylib                     0x00007fff9d40d48d objc_exception_throw + 48
2   CoreFoundation                      0x00007fff88532b5c -[__NSArrayM objectAtIndex:] + 204
3   CoreData                            0x00007fff881978ed -[NSSQLRow newObjectIDForToOne:] + 253
4   CoreData                            0x00007fff8819770f -[NSSQLRow _validateToOnes] + 399
5   CoreData                            0x00007fff88197571 -[NSSQLRow knownKeyValuesPointer] + 33
6   CoreData                            0x00007fff88191868 _prepareResultsFromResultSet + 4312
7   CoreData                            0x00007fff8818e47b newFetchedRowsForFetchPlan_MT + 3387
8   CoreData                            0x00007fff8835f6d7 _executeFetchRequest + 55
9   CoreData                            0x00007fff8828bb35 -[NSSQLFetchRequestContext executeRequestUsingConnection:] + 53
10  CoreData                            0x00007fff8832c9c8 __52-[NSSQLDefaultConnectionManager handleStoreRequest:]_block_invoke + 216
11  libdispatch.dylib                   0x000000010105478c _dispatch_client_callout + 8
12  libdispatch.dylib                   0x00000001010555ad _dispatch_barrier_sync_f_invoke + 307
13  CoreData                            0x00007fff8832c89d -[NSSQLDefaultConnectionManager handleStoreRequest:] + 237
14  CoreData                            0x00007fff88286c86 -[NSSQLCoreDispatchManager routeStoreRequest:] + 310
15  CoreData                            0x00007fff88261189 -[NSSQLCore dispatchRequest:withRetries:] + 233
16  CoreData                            0x00007fff8825e21d -[NSSQLCore processFetchRequest:inContext:] + 93
17  CoreData                            0x00007fff8817d218 -[NSSQLCore executeRequest:withContext:error:] + 568
18  CoreData                            0x00007fff882436de __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke + 5486
19  CoreData                            0x00007fff8823a347 -[NSPersistentStoreCoordinator _routeHeavyweightBlock:] + 407
20  CoreData                            0x00007fff8817cd9e -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 654
21  CoreData                            0x00007fff8817af51 -[NSManagedObjectContext executeFetchRequest:error:] + 593
22  libswiftCoreData.dylib              0x0000000100c91648 _TFE8CoreDataCSo22NSManagedObjectContext5fetchuRxSo20NSFetchRequestResultrfzGCSo14NSFetchRequestx_GSax_ + 56

我认为标志 -com.apple.CoreData.SQLDebug 1 可能会给我一些有用的信息,但我在这里没有看到任何非常有用的信息:

CoreData: annotation: Connecting to sqlite database file at "/Users/name/Library/Group Containers/Folder/Database.sqlite"
CoreData: sql: pragma recursive_triggers=1
CoreData: sql: pragma journal_mode=wal
CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
CoreData: sql: SELECT TBL_NAME FROM SQLITE_MASTER WHERE TBL_NAME = 'Z_MODELCACHE'
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZDATA, t0.ZOBJECT, t0.Z9_OBJECT FROM ZENGINEDATA t0 
2017-04-28 16:18:41.693548-0400 AppName[95979:11442888] [error] error: Background Core Data task threw an exception.  Exception = *** -[__NSArrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0 .. 10] and userInfo = (null)
CoreData: error: Background Core Data task threw an exception.  Exception = *** -[__NSArrayM objectAtIndex:]: index 18446744073709551615 beyond bounds [0 .. 10] and userInfo = (null)
CoreData: annotation: sql connection fetch time: 0.0065s
CoreData: annotation: fetch using NSSQLiteStatement <0x6080004805a0> on entity 'ENGINEDATA' with sql text 'SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZDATA, t0.ZOBJECT, t0.Z9_OBJECT FROM ZENGINEDATA t0 ' returned 1185 rows with values: (
    "<JUNENGINEDATA: 0x608000480eb0> (entity: ENGINEDATA; id: 0x40000b <x-coredata://10C884E2-18EF-4DA2-BC5D-4CBD0CE7D1A6/ENGINEDATA/p1> ; data: <fault>)",
    "<JUNENGINEDATA: 0x608000480f00> (entity: ENGINEDATA; id: 0x80000b <x-coredata://10C884E2-18EF-4DA2-BC5D-4CBD0CE7D1A6/ENGINEDATA/p2> ; data: <fault>)",
    "<JUNENGINEDATA: 0x608000480f50> (entity: ENGINEDATA; id: 0xc0000b <x-coredata://10C884E2-18EF-4DA2-BC5D-4CBD0CE7D1A6/ENGINEDATA/p3> ; data: <fault>)",
    ...
    "<JUNENGINEDATA: 0x600000288110> (entity: ENGINEDATA; id: 0x128c0000b <x-coredata://10C884E2-18EF-4DA2-BC5D-4CBD0CE7D1A6/ENGINEDATA/p1187> ; data: <fault>)",
    "<JUNENGINEDATA: 0x600000288160> (entity: ENGINEDATA; id: 0x12900000b <x-coredata://10C884E2-18EF-4DA2-BC5D-4CBD0CE7D1A6/ENGINEDATA/p1188> ; data: <fault>)",
    "<JUNENGINEDATA: 0x6000002881b0> (entity: ENGINEDATA; id: 0x12940000b <x-coredata://10C884E2-18EF-4DA2-BC5D-4CBD0CE7D1A6/ENGINEDATA/p1189> ; data: <fault>)"
)
CoreData: annotation: total fetch execution time: 0.1053s for 1185 rows.
2017-04-28 16:18:41.793201-0400 AppName[95979:11442649] Got results: 1185

好消息是 EngineData 中的所有内容都可以重新创建,而且我发现如果我批量删除所有 EngineData 对象,它不再崩溃(即使在创建一些新对象之后)。如果可能的话,我非常希望了解问题的原因,并找到一个不太激烈的解决方案。

以下是我发现的其他一些事情:

  • 我尝试从另一台计算机复制数据库,但无法使用该数据复制问题。这让我想到也许数据一开始就被损坏了……
  • 但是,如果我返回旧版本的应用程序及其数据,相同的提取工作正常。如果我除了升级数据模型之外什么都不做,我也会得到同样的异常。
  • 我尝试为该关系设置版本哈希修饰符,希望这可以强制 Core Data 在迁移时清理内容,但没有成功。
  • 如果我将批量大小设置为 10,则它可以在异常发生之前循环遍历 900 个结果(总共 1185 个结果)。
  • 使用该流程,我可以一次删除一个好的记录,以缩小有问题的记录范围。 (每次删除后我都会保存,并且如果我需要返回进行进一步测试,我会保留原始数据库的备份。)

最佳答案

经过大量实验,我能够缩小范围:我有一些 EngineData 对象引用了不再存在的 Car 对象。看起来由于该关系被破坏,记录无法正确迁移到新的数据模型版本。如果我在 Mac 应用程序库中打开 .sqlite 文件,我可以看到一个新的 Z9_OBJECT 字段,该字段针对所有良好记录设置为 10NULL 表示损坏的。

一旦我发现了原因,解决问题就相当容易了。使用NSBatchDeleteRequest我能够找到并删除损坏的对象:

do {
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "EngineData")
    fetchRequest.predicate = NSPredicate(format: "object.uuid == nil")
    let batchRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
    try context.execute(batchRequest)
} catch {
    NSLog("Error deleting objects: \(error)")
}

在我的谓词中,object 是父对象(以前是 Car,现在是 Vehicle),uuid code> 是该对象的非可选属性。只要属性不是可选的(这意味着它在任何未损坏的对象上都有一个值),就应该成功地仅删除已损坏的对象。

您可能期望这会导致异常,因为它仍在获取损坏的对象!幸运的是,NSBatchDeleteRequest 的工作原理是直接在存储上运行 SQL 语句,而不将任何内容加载到内存中,因此避免了该问题。

关于cocoa - 核心数据迁移: exceptions after changing a relationship from one entity to its parent entity,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43690461/

相关文章:

objective-c - 使复选框不可编辑

ios - 在代码中使用 NSManagedObject 模型

ios - 核心数据迁移 : Possible to delete old store except for a few specific objects?

iphone - CoreData 中的重复名称?

ios - "Can' t 为源存储找到模型 "occurring during iphone "自动轻量级迁移”?

ios - Core Data和Restkit简单轻量级迁移报错

xcode - 切换 NSWindows

cocoa - 如何使用 Cocoa 将图像重叠(叠加)在文件图标上?

ios - react-native 支持 NSDataDetector 吗?

ios - 根据 Swift Core Data 中的关系获取与特定实体相关的所有条目