CoreData 管理的对象上下文保存操作违反了 Z_PK 唯一约束条件。

3

我正在一个iOS应用中使用CoreData来管理"翻转卡片"应用中的单词,以帮助人们学习新语言。

我的问题是,当我设置一个新实体的数据并尝试将其保存到数据存储区时,会违反sqlite数据库上的UNIQUE CONSTRAINT要求。问题出在Z_PK列上,我理解这是iOS core data方法在初始创建数据存储区时创建的PRIMARY KEY。

以下是我尝试保存时收到的UNIQUE CONSTRAINT消息:

2015-03-14 09:25:14.427 ghoti[25856:1107373] CoreData: error: (1555) UNIQUE constraint failed: ZGHOTIENTITY.Z_PK (lldb)

Z是所有这些SQL列的前缀
GHOTIENTITY是我的数据存储区
Z_PK是主键

以下是Firefox中sqlite "editor"中数据表的快速截图: datastructure from sqlite

业务对象实体本身的定义不包括Z_PK列:

@implementation ghotiEntity

@dynamic numberCorrect;
@dynamic numberIncorrect;
@dynamic likelihoodOfPresentingWord;
@dynamic englishText;
@dynamic hebrewText;
@dynamic phoneticText;

@end

如果只是将ghotiEntity.Z_PK设置为数据存储中Z_PK的最大值加1,我会很烦恼但仍然会遵守,但这一列甚至不是实体定义的一部分。


编辑 - 一位热心用户请求提供代码,因此我在这里添加代码,而不是试图将其塞入评论中:

我正在使用mmBusinessObject集合的核心数据方法,这些方法在网络上很常见。Kevin McNeish在这里完整地解释了它。

基本结构如下:

mmBusinessObject.m和.h很好地封装了managedObject方法,使其易于阅读和处理。为了便于阅读,我在此处复制了两个元素[entity createEntities]和[entity saveEntities]的片段,以及本帖末尾的完整.m文件。(请注意,我还没有建立所有错误检查,我只是试图使基本功能正常工作)。

创建新实体:

// Creates a new entity of the default type and adds it to the managed object context
- (NSManagedObject *)createEntity
{
    return [NSEntityDescription insertNewObjectForEntityForName:self.entityClassName inManagedObjectContext:[self managedObjectContext]];
}

保存更改

- (void)saveEntities
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

所以我的操作代码(在我的ViewController.m文件中)执行以下操作:(请注意,ghotiEntity是我的特定业务对象。)
- (IBAction)saveButtonAction:(UIButton *)sender {

    ghotiEntity *newGhotiEntry = (ghotiEntity *)[ghotiData createEntity];
    newGhotiEntry.englishText=self.englishWord.text;
    newGhotiEntry.hebrewText=self.hebrewWord.text;
    newGhotiEntry.phoneticText=self.phoneticWord.text;
    newGhotiEntry.numberCorrect=0;
    newGhotiEntry.numberIncorrect=0;
    newGhotiEntry.likelihoodOfPresentingWord=0;

    [ghotiData saveEntities];

    [self enableRegularUI];
    [self pickNextCard];
}

在上面的saveEntities方法中,正是通过NSLog行获得了诊断信息,指出Z_PK列存在唯一约束性问题。


如上所承诺,这是完整的 .m 文件。我需要确保清楚地表明,此代码的功劳归于如上所述的 Kevin McNeish 或其他几位在其他地方发布了相同代码的合作者...只需在谷歌中搜索 mmBusinessObject。我认为 Kevin 是创始人,他的教程非常好。在我看到的版本中,前几行都有 Kevin 的名字!

#import "mmBusinessObject.h"

@implementation mmBusinessObject

// Initialization
- (id)init
{
    if ((self = [super init])) {
        _copyDatabaseIfNotPresent = YES;
    }
    return self;
}

// Creates a new entity of the default type and adds it to the managed object context
- (NSManagedObject *)createEntity
{
    return [NSEntityDescription insertNewObjectForEntityForName:self.entityClassName inManagedObjectContext:[self managedObjectContext]];
}

// Delete the specified entity
- (void) deleteEntity:(NSManagedObject *)entity {
    [self.managedObjectContext deleteObject:entity];
}

// Gets entities for the specified request
- (NSMutableArray *)getEntities: (NSString *)entityName sortedBy:(NSSortDescriptor *)sortDescriptor matchingPredicate:(NSPredicate *)predicate
{
    NSError *error = nil;

    // Create the request object
    NSFetchRequest *request = [[NSFetchRequest alloc] init];

    // Set the entity type to be fetched
    NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:[self managedObjectContext]];
    [request setEntity:entity];

    // Set the predicate if specified
    if (predicate) {
        [request setPredicate:predicate];
    }

    // Set the sort descriptor if specified
    if (sortDescriptor) {
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
        [request setSortDescriptors:sortDescriptors];
    }

    // Execute the fetch
    NSMutableArray *mutableFetchResults = [[_managedObjectContext executeFetchRequest:request error:&error] mutableCopy];

    if (mutableFetchResults == nil) {

        // Handle the error.
    }

    return mutableFetchResults;
}

// Gets all entities of the default type
- (NSMutableArray *)getAllEntities
{
    return [self getEntities:self.entityClassName sortedBy:nil matchingPredicate:nil];
}

// Gets entities of the default type matching the predicate
- (NSMutableArray *)getEntitiesMatchingPredicate: (NSPredicate *)predicate
{
    return [self getEntities:self.entityClassName sortedBy:nil matchingPredicate:predicate];
}

// Gets entities of the default type matching the predicate string
- (NSMutableArray *)getEntitiesMatchingPredicateString: (NSString *)predicateString, ...;
{
    va_list variadicArguments;
    va_start(variadicArguments, predicateString);
    NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString
                                                    arguments:variadicArguments];
    va_end(variadicArguments);
    return [self getEntities:self.entityClassName sortedBy:nil matchingPredicate:predicate];
}

// Get entities of the default type sorted by descriptor matching the predicate
- (NSMutableArray *)getEntitiesSortedBy: (NSSortDescriptor *) sortDescriptor
                      matchingPredicate:(NSPredicate *)predicate
{
    return [self getEntities:self.entityClassName sortedBy:sortDescriptor matchingPredicate:predicate];
}

// Gets entities of the specified type sorted by descriptor, and matching the predicate string
- (NSMutableArray *)getEntities: (NSString *)entityName
                       sortedBy:(NSSortDescriptor *)sortDescriptor
        matchingPredicateString:(NSString *)predicateString, ...;
{
    va_list variadicArguments;
    va_start(variadicArguments, predicateString);
    NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString
                                                    arguments:variadicArguments];
    va_end(variadicArguments);
    return [self getEntities:entityName sortedBy:sortDescriptor matchingPredicate:predicate];
}

- (void) registerRelatedObject:(mmBusinessObject *)controllerObject
{
    controllerObject.managedObjectContext = self.managedObjectContext;
}

// Saves all changes (insert, update, delete) of entities
- (void)saveEntities
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

#pragma mark -
#pragma mark Core Data stack

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext {

    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created from the application's model.
 */
- (NSManagedObjectModel *)managedObjectModel {

    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:self.dbName withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    // If the sqlite database doesn't already exist, create it
    // by copying the sqlite database included in this project

    if (self.copyDatabaseIfNotPresent) {

        // Get the documents directory
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                             NSUserDomainMask, YES);
        NSString *docsDir = paths[0];

        // Append the name of the database to get the full path
        NSString *dbcPath = [docsDir stringByAppendingPathComponent:
                             [self.dbName stringByAppendingString:@".sqlite"]];

        // Create database if it doesn't already exist
        NSFileManager *fileManager = [NSFileManager defaultManager];

        if (![fileManager fileExistsAtPath:dbcPath]) {
            NSString *defaultStorePath = [[NSBundle mainBundle]
                                          pathForResource:self.dbName ofType:@"sqlite"];
            if (defaultStorePath) {
                [fileManager copyItemAtPath:defaultStorePath toPath:dbcPath error:NULL];
            }
        }



    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:
                       [NSString stringWithFormat:@"%@%@", self.dbName, @".sqlite"]];

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.

         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.


         If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.

         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]

         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.

         */
        NSLog(@"%@",storeURL);
        if ([error code] == 134100) {
            [self performAutomaticLightweightMigration];
        }
        else
        {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
    return _persistentStoreCoordinator;
}

- (void)performAutomaticLightweightMigration {

    NSError *error;

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@%@", self.dbName, @".sqlite"]];

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:storeURL
                                                         options:options
                                                           error:&error]){
        // Handle the error.
    }
}


#pragma mark -
#pragma mark Application's Documents directory

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory {
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

@end

请提供一个代码,展示如何创建和保存您的ghoti对象。CoreData在内部管理私钥,如果一切正确,通常会完美地工作。 - Eugene Dudnyk
感谢您的评论。我已经编辑了原始帖子,包括更多的代码和上下文。 - plm
在实现mmBusinessObject时,如果项目中存在数据库,则会从项目中复制。您的项目中是否有预生成的.sqlite数据库? - Eugene Dudnyk
是的,存在一个现有的sqlite数据库。我知道它正在被读取,因为当我读取表格getAllEntities时,所有行都按预期传递到我的NSMutableArray中。 - plm
3个回答

2
我曾遇到一个Z_PK约束问题。起因是我将空的sqlite数据库从我的应用程序文档文件夹中复制出来,然后使用Firefox的SQLite管理器插件进行预填充。我忘记了更新Z_PrimaryKey表这一步骤...你需要更新每个表的Z_MAX值,以便Core Data在向数据库添加新记录时知道下一个要使用的Z_PK。只需将每个Z_Max值设置为每个表中记录的数量即可。保存并将其放回应用程序中,一切都会好起来。

1

浏览您的代码,查找所有创建数据库对象的地方。确保您不是从多个线程创建对象。


遇到了相同的问题,但我没有从多个线程创建对象。 - AndrewSmiley

0

我遇到了同样的问题,但是当我想要更新一个条目时出现了这个问题,所以我通过刷新对象然后像这样保存来解决它:

[dataManager.managedObjectContext refreshObject:object mergeChanges:YES];
[dataManager saveContextWithBlock:block];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接