为什么这段代码引起“CoreData:error:(19)PRIMARY KEY必须是唯一的”错误?

此代码引发“CoreData:错误:(19)PRIMARY KEY必须是唯一的”错误。 Day实体只有一个是NSDatewhen属性和一个叫做tasks的多对多关系。 为什么这个错误? 如果具有特定date的某天已经存储,则取回,否则我将其插入。 所以,对于每一天的对象,应该有一个不同的属性。 我不确定这是不是主键。 如何解决这个问题? 先谢谢你。

  NSMutableSet *occurrences = nil; occurrences = ... NSMutableOrderedSet *newSet = [NSMutableOrderedSet orderedSetWithCapacity:[occurrences count]]; for(NSDate *current in occurrences) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // try to find a corresponding Day entity whose when attribute is equal to the current occurrence // if none is available, create it Day * day = [[self getDayForDate:current inManagedObjectContext:moc] retain]; if(!day){ day = (Day *) [NSEntityDescription insertNewObjectForEntityForName:@"Day" inManagedObjectContext:moc]; } day.when = current; [day addTasksObject:aTask]; [newSet addObject:day]; [moc insertObject:day]; [moc processPendingChanges]; [day release]; [pool release]; } - (Day *)getDayForDate:(NSDate *)aDate inManagedObjectContext:(NSManagedObjectContext *)moc { NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Day" inManagedObjectContext:moc]; [request setEntity:entity]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(when == %@)", aDate]; [request setPredicate:predicate]; NSError *error = nil; NSArray *array = [moc executeFetchRequest:request error:&error]; [request release]; Day *theDay = nil; if(array && [array count] == 1){ theDay = [array objectAtIndex:0]; } return theDay; } 

我想如果你已经拥有了它,那么你不需要插入一个新的Day (当天不是零时就是这种情况)。 特别是我指的是[moc insertObject:day]

如果使用insertNewObjectForEntityForName ,则该方法在保存moc时为您插入对象。 如果你需要修改它(你已经检索了非零日)修改它并保存。 另外,当循环结束时,我将执行processPendingChanges (仅出于性能原因)。

希望有所帮助。

我最近一直在努力CoreData: error: (19) PRIMARY KEY must be unique一个内部的iOS应用程序的CoreData: error: (19) PRIMARY KEY must be unique错误,幸好在研究和代码修改一两天后发现了解决scheme,我想分享我的在这里的发现,希望他们会帮助别人。

首先,有一点背景

我们的内部应用程序从来没有使用任何代码来更新它的CoreData存储 – 而是需要在应用程序中显示新数据时进行每次应用程序构build迭代,而CoreData SQLite后备存储文件只是用一个新版本已经使用标准的基于桌面的SQLite编辑器进行了编辑 – 即根据需要修改和更新原始SQLite文件。 尽pipe人们认识到这种方法没有考虑到CoreData的“黑盒子”特性,但是在过去的几年中,它实际上为我们提供了简单的应用程序,而且应用程序已经愉快地接受了更新后的SQLite文件新应用程序构build。

然而,最近我们对应用程序进行了一次重大的改革,从应用程序资产和数据更新的模型(通过Xcode和应用程序重build)转向通过Web服务获取新的应用程序数据的模型,而在这个新的开发工作, PRIMARY KEY must be unique问题出现了。

经过几天的努力来解决这个问题,认为新的CoreData实体创build代码(已经过彻底检查和重新检查)或者其他相关问题必然存在一些错误,我通过进一步的研究发现我能够使用Xcode为我们的应用程序启用CoreData SQLdebugging(按照这个非常有用的SOpost中的说明)。

当我仔细研究Xcode中的SQL日志时,我可以看到在每次调用CoreData向SQLite后备存储中INSERT新logging之前,该框架使用以下查询来查询Z_PRIMARYKEYSELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ? 哪里? 在相关CoreData实体的相关Z_ENT值的后面被replace(您可以通过查看Z_PRIMARYKEY表的内容来查看每个CoreData实体的Z_ENT值)。 这是我终于明白了CoreData的黑盒子里发生了什么事情! 然后,我使用Mac上的Liya应用程序仔细查看了我们的应用程序的SQLite文件,然后查看了Z_PRIMARYKEY表的内容,并确信Z_MAX列值全部设置为0 。 当CoreData首先为我们的应用程序生成空的SQLite后备存储文件时,它们从未被初始值更改过!

我立即意识到发生了什么问题,为什么CoreData报告了主键错误 – 事实上并没有涉及到任何更高级别的CoreData实体对象的属性碰撞,如初步怀疑,但确实是一个较低级别错误。 到目前为止,这个错误是绝对有意义的,一直存在,只是在正确的语境下才被理解。

从进一步的研究中可以看出,过去几年里,我们的内部团队已经成功地对SQLite后台存储进行了直接编辑,从各个实体表中插入,更新和删除logging,但是我们的团队从未做过任何更改到Z_PRIMARYKEY表,并且由于我们的应用程序以只读方式使用CoreData的方式,这从来都不是问题。

但是,现在我们的应用程序试图创buildCoreData条目并将其保存到SQLite后备存储中,因此,正在生成的许多INSERT查询仅仅因为CoreData无法获取正确的最大主键值而失败在任何给定的表上生成下一个顺序主键,因此INSERT查询将失败,现在可以理解的有意义的PRIMARY KEY must be unique错误!

解决错误

我意识到每个开发人员可能会以各种方式为应用程序生成默认/初始CoreData内容,但是,如果您曾经遇到CoreData中的这个特定错误,并且努力find一个明确的答案,我希望以下的想法有帮助:

  • 我们的应用程序的CoreData后备存储是通过直接编辑SQLite文件(插入,更新和删除实体表中的logging)手动更新的 – 这种方法已经工作了很长一段时间,因为我们一直在使用这种只读方式我们的应用 然而,这种方法并没有真正地认识到CoreData的抽象“黑盒子”性质,以及SQLitelogging不等同于CoreData实体,SQLite连接并不等同于CoreData关系,等等。

  • 如果您的应用程序使用任何种类的预填充CoreData存储,并且以非CoreData的方式填充SQLite支持存储库的内容,请确保在任何CoreData实体表中创build新logging,还要确保更新Z_PRIMARYKEY表中的相关logging,将Z_MAX列值设置为相关实体表中的当前最大Z_PK值。

    例如,如果你有一个名为Employee的CoreData实体,在你的CoreData SQLite持久存储支持文件中,这个表将由一个名为ZEMPLOYEE的表来表示 – 此表中还有一些隐藏的CoreData列,包括Z_PKZ_ENTZ_OPT等到表示您的实体的属性和关系的列。 这个实体表在Z_PRIMARYKEY表中Z_PRIMARYKEY有一个Z_NAME值为Employee的对应logging – 因此当你直接向ZEMPLOYEE表中添加一个新logging时 – 确保在你完成添加logging之后,通过每个实体表并将最大Z_PK值复制到Z_PRIMARYKEY表的Z_MAX列。 您input到Z_MAX的值应该是相应表中最大的Z_PK值; 不要将Z_MAX设置为等于Z_PK + 1的值,因为这不是CoreData所期待的!

您可以使用以下SQL查询任何实体表的最大Z_PK值:

 "SELECT Z_PK FROM ZEMPLOYEE ORDER BY Z_PK DESC LIMIT 1" 

这将为您提供ZEMPLOYEE表中任何logging的最大Z_PK值 – 显然,您应该将ZEMPLOYEEreplace为应用程序数据模型的相关表名。

对于我们的应用程序,我能够编写一个简单的命令行脚本,直接读取SQLite文件,遍历Z_PRIMARYKEY表的每个logging,并用正确的Z_MAX值更新它。 一旦完成,我能够使用这个更新的文件作为应用程序的持久存储备份文件。 现在,当我插入并保存新的CoreData实体时,一切都按预期工作。

现在,我们的应用程序可以通过Web服务直接请求新数据,我们将完全从手动编辑SQLite后备存储,并专门使用CoreData框架来更新数据,但对于时间或其他应用程序,可能需要这种快速简单的数据input,希望这个答案可以帮助其他开发者,并节省他花费我的时间和精力来发现这个解决scheme。