此代码引发" CoreData:错误:(19)PRIMARY KEY必须是唯一的"错误。
Day
实体只有一个when
属性NSDate
,以及一个名为tasks
的多对多关系。为什么这个错误?如果已经存储了具有特定日期的Day
,我会获取它,否则我会插入它。因此,对于每天对象,应该有不同的when
属性。我不确定这是否是主键。怎么解决这个?先感谢您。
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;
}
答案 0 :(得分:19)
我最近一直在努力解决内部iOS应用程序的CoreData: error: (19) PRIMARY KEY must be unique
错误,并且幸运地在经过一两天的研究和代码修改后发现了解决方案,我想在此处分享我的发现。希望他们对别人有所帮助。
首先,一点背景
我们的内部应用程序最初从未构建过任何代码来更新其CoreData存储 - 相反,当需要在应用程序中显示新数据时,每个应用程序构建迭代,CoreData SQLite后备存储文件只是被替换为使用标准的基于桌面的SQLite编辑器编辑的新版本 - 即原始SQLite文件已根据需要进行了修改和更新。虽然人们认识到这种方法没有考虑到"黑盒子" CoreData的本质,它实际上在过去几年中使用我们的简单应用程序很好地服务了我们,应用程序很高兴地接受了每个新应用程序构建的更新的SQLite文件。
然而,最近我们对应用程序进行了重大改革,从通过Xcode和应用程序重建更新应用程序资产和数据的模型转变为通过Web服务获取新应用程序数据的模型,而且是在PRIMARY KEY must be unique
问题出现的这项新的开发工作。
经过几天努力解决问题,认为新的CoreData实体创建代码(已经过彻底检查和重新检查)或其他相关问题必定存在一些错误,我通过进一步的研究发现我能够使用Xcode为我们的应用程序启用CoreData SQL调试(根据this非常有用的SO帖子中的说明)。
当我仔细研究Xcode中的SQL日志时,我可以看到在CoreData向INSERT
每次调用之前向SQLite后备存储区发送新记录时,框架查询Z_PRIMARYKEY
表以下查询SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ?
其中?
在幕后被替换为相关CoreData实体的相关Z_ENT
值(您可以看到每个CoreData实体的Z_ENT
值通过查看Z_PRIMARYKEY
表的内容。这是我终于明白CoreData的黑匣子里发生了什么!然后,我使用Mac上的Liya应用程序仔细查看了我们应用程序的SQLite文件,并查看了Z_PRIMARYKEY
表的内容,确定Z_MAX
列值都是设为0
。当CoreData首次为我们的应用程序生成空的SQLite后备存储文件时,它们从未从初始值更改过来!
我立即意识到出现了什么问题以及为什么CoreData报告了主键错误 - 事实上它并没有与任何更高级别的CoreData实体对象的属性发生冲突,因为最初怀疑,但确实是一个较低级别的错误。只是现在这个错误是绝对有意义的,它始终存在,它只是在正确的背景下才被理解。
从进一步的研究中可以看出,我们的内部团队在过去几年中成功地直接编辑了SQLite后备存储,插入,更新和删除了各种实体表中的记录,但我们的团队从来没有对Z_PRIMARYKEY
表做了任何更改,由于我们的应用程序以只读方式使用CoreData的方式,这从来就不是问题。
但是,现在我们的应用程序正在尝试创建并将CoreData条目保存回SQLite后备存储,因此生成的许多INSERT
查询失败,因为CoreData无法获取正确的在任何给定表上生成下一个顺序主键的最大主键值,因此INSERT
查询将失败,现在可以理解为PRIMARY KEY must be unique
错误!
解决错误
我意识到每个开发人员可能会以各种方式为他们的应用程序生成默认/初始CoreData内容,但是,如果您在CoreData中遇到过这个特定错误并且还在努力寻找一个立即明确的答案,我希望以下想法是有帮助的:
我们的应用程序的CoreData后备存储是通过直接编辑SQLite文件(插入,更新和删除实体表中的记录)手动更新的 - 这种方法因为读取而工作了很长时间 - 我们在应用程序中消耗这些数据的方式。然而,这种方法未能真正认识到抽象的黑盒子#34; CoreData的本质以及SQLite记录与CoreData实体不等同的事实,并且SQLite连接不等同于CoreData关系,依此类推。
如果您的应用使用任何类型的预先填充的CoreData存储,并且您通过CoreData以外的任何方式填充SQLite后备存储的内容 - 请确保如果您确实创建了新的记录任何CoreData实体表,您还要确保更新Z_PRIMARYKEY
表中的相关记录,并将Z_MAX
列值设置为相关实体表中的当前最大Z_PK
值
例如,如果您的CoreData中有一个名为Employee
的CoreData实体
SQLite持久性存储支持文件,这将由名为ZEMPLOYEE
的表表示 -
这张桌子会隐藏一些' CoreData列包括Z_PK
,Z_ENT
,
Z_OPT
等,以及代表您实体的列
属性和关系。此实体表还将在Z_PRIMARYKEY
表中具有Z_NAME
值为Employee
的相应记录 - 因此当您将新记录直接添加到ZEMPLOYEE
表时 - make确保在完成添加记录后,您将浏览每个实体表并将最大Z_PK
值复制到Z_MAX
表的Z_PRIMARYKEY
列。您输入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"
这会为Z_PK
表中的任何记录提供最大ZEMPLOYEE
值 - 显然,您应该使用应用数据模型的相关表名替换ZEMPLOYEE
对于我们的应用程序,我能够编写一个简单的命令行脚本,直接读取SQLite文件,遍历Z_PRIMARYKEY
表的每个记录并使用正确的Z_MAX
值进行更新。完成此操作后,我可以将此更新的文件用作应用程序的持久性存储后备文件。现在,当我插入并保存新的CoreData实体时,一切都按预期工作。
既然我们的应用程序有办法直接通过Web服务请求新数据,我们将完全从手动编辑SQLite后备存储并专门使用CoreData框架更新数据,但有时或其他应用程序可能需要快速简便的数据输入,我希望这个答案可以帮助其他开发人员,并节省他们花时间和精力来发现这个解决方案。
答案 1 :(得分:7)
我想如果您已经拥有它,则不需要插入新的Day
(当天不是零时就是这种情况)。特别是我指的是[moc insertObject:day]
。
如果使用insertNewObjectForEntityForName
,则在保存moc时该方法会为您插入对象。如果你需要修改它(你已经检索了一个非零日)修改它并保存。另外,我会在循环结束时执行processPendingChanges
(仅出于性能原因)。
希望有所帮助。