在创建新对象时查询特定属性的核心数据并返回对象(如果存在)或创建新对象(如果不存在)

时间:2013-11-16 15:53:10

标签: ios core-data entity nspredicate fetch

我在创建新对象之前检查核心数据库中是否存在实体的特定属性(通过谓词)时遇到问题;如果对象存在,我宁愿返回它而不是创建一个新对象。

我有一个简单的应用程序,它在导航栏中有一个带加号按钮的表格视图;用户单击该按钮并显示带有4个文本字段的View Controller。他们填写该信息,按保存并将其保存到Core Data并显示在TableView中(通过使用NSFetchedResultsControllers)。

数据模型如下:

具有isReceived BOOL属性的交易实体 具有名称字符串属性的Person实体 具有标题字符串属性的Occasion Entity 具有金额字符串属性的项目实体

事务与Person(whoBy),Occasion(Occasion)和Item实体有关系。

在使用save方法的视图控制器中,我有下面的代码,它会将新对象插入到Transaction,Person,Occasion Entities等中。每个Transaction当然是唯一的,但是对于每个事务,用户可以选择一个现有的PERSON和/或场合,如果那个人不存在,它将被创建(同样有机会)。

我对这里的代码格式感到有些困惑。

编辑:我尝试了一些代码组合,但却无法实现这一点。在下面的代码中,我在谓词中引用了person.name,但我也尝试创建一个本地NSString变量来保存self.nameTextField.text代码但是什么也没做。我尝试创建一个NSString属性,以这种方式引用它,这是行不通的。我尝试使用单词MATCHES, LIKE, CONTAINS, ==以及中间的每个组合。

- (IBAction)save:(id)sender
{
    NSManagedObjectContext *context = [self managedObjectContext];
    Transaction *transaction= [NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context];
    Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    Occasion *occasion = [NSEntityDescription insertNewObjectForEntityForName:@"Occasion" inManagedObjectContext:context];
    Item *amount = [NSEntityDescription insertNewObjectForEntityForName:@"item" inManagedObjectContext:context];

 NSFetchRequest *personFind = [NSFetchRequest fetchRequestWithEntityName:@"Person"];

    personFind.predicate = [NSPredicate predicateWithFormat:@"name == %@", person.name];
    // I have tried every combination of the predicate like MATCHES, LIKE. 
    // I created a local NSString variable and an NSString property
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    personFind.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];

    NSError *error = nil;
    NSArray *matches = [context executeFetchRequest:personFind error:&error];

    if (!matches || ([matches count] > 1))
    {
        // Handle Error
    }
    else if ([matches count] == 0)
    {
        person.name = self.nameTextField.text;
        transaction.whoBy = person;
        occasion.title = self.occasionTextField.text;
        transaction.occasion = occasion;
    }

    else
    {
        person = [matches lastObject];
        transaction.whoBy = person; 
        occasion.title = self.occasionTextField.text
        transaction.occasion = occasion; 
    }


if (![context save:&error])
    {
        NSLog(@"Can't save! %@ %@", error, [error localizedDescription]);

    }
    [self dismissViewControllerAnimated:YES completion:nil];

}

从逻辑上讲,我想要达到的目标是:

  • 当用户添加交易时,检查它是否适用于新人或现有交易 - 如果是现有交易,请从人员列表中选择(当用户选择某个人时,获取其NSManagedObjectID)。如果是新的,请在现场创建。

  • 适用于场合。

设置Transaction对象的所有其他字段(金额等)。

我的问题是:

  • 我使用什么谓词来实现这项工作?

当我在此方法中设置断点时,新名称(之前不存在的名称)正确调用else if ([matches count] == 0)方法,如果我创建具有现有名称的条目,则调用

else
        {
            person = [matches lastObject];
            transaction.whoBy = person; 
            occasion.title = self.occasionTextField.text
            transaction.occasion = occasion; 
        }

即使使用此语句,它仍然会为同一名称创建一个新的person对象。

我会在让这个人工作之后正确地实施这个场合,但我只是迷失了如何让这个工作。

任何帮助都会受到大力赞赏!

1 个答案:

答案 0 :(得分:1)

"这是正确的吗?":
不会。无论您是否使用现有的人/场合,您都要创建新的PersonOccasion个对象。
首先检查是否存在,并且仅当对象尚不存在时,插入一个新对象 或者,如果存在人/场合,则删除插入的对象。

"如何检索人/事件的managedObjectID?":

Person* person = /*Get an existing person object*/
NSManagedObjectID* personId = person.objectID /*This is the person object ID, will work for any NSManagedObject subclass*/

要查找以字符串str开头的人,请在获取请求中使用此谓词:

/*UNTESTED*/
[NSPredicate predicateWithFormat:@"(name BEGINSWITH[cd] %@)", str];

修改

更准确地说,您可以使用以下内容练习查找或创建:
(这是非常有限的,并且仅对性能方面的单个对象有益) (未经过测试

- (NSManagedObject*) findOrCreateObjectByValue:(id)value
                                  propertyName:(NSString*)propertyName
                                    entityName:(NSString*)entityName
                                additionalInfo:(NSDictionary*)additionalInfo
                                       context:(NSManagedObjectContext*)context
                                         error:(NSError* __autoreleasing*)error
{
    NSManagedObject* res = nil;

    NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:entityName];
    [r setPredicate:[NSPredicate predicateWithFormat:@"%K == %@",propertyName,value]];

    NSArray* matched = [context executeFetchRequest:r
                                              error:error];

    if (matched) {
        if ([matched count] < 2) {
            res = [matched lastObject];
            if (!res) { //No existing objects found, create one
                res = [NSEntityDescription insertNewObjectForEntityForName:entityName
                                                    inManagedObjectContext:context];
                [res setValue:value
                       forKey:propertyName];
                [res setValuesForKeysWithDictionary:additionalInfo];
            }
        } else {
            if (error) {
                *error = [NSError errorWithDomain:@"some_domain"
                                             code:9999
                                         userInfo:@{@"description" : @"duplicates found"}];
            }
        }
    }

    return res;
}

现在,您的save:方法应该类似于:
(我在这里假设人名和场合标题由视图控制器[txtPersonNametxtOccasionTitle上的UITextField持有))

- (void) save:(id)sender
{
    //create a clean context so that changes could be discarded automatically on failure
    NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [context setParentContext:[self managedObjectContext]];

    //A Transaction is always created in save event, so add it to the context
    Transaction* transaction = [NSEntityDescription insertNewObjectForEntityForName:@"Transaction" inManagedObjectContext:context];

    __block NSError* error = nil;

    Person* p = (Person*)[self findOrCreateObjectByValue:self.txtPersonName.text
                                            propertyName:@"name"
                                              entityName:@"Person"
                                          additionalInfo:nil
                                                 context:context
                                                   error:&error];
    if (!p) {
        NSLog(@"Error: %@, person name: %@",error,self.txtPersonName.text);
        return;
    }

    Occasion* o = (Occasion*)[self findOrCreateObjectByValue:self.txtOccasionTitle.text
                                                propertyName:@"title"
                                                  entityName:@"Occasion"
                                              additionalInfo:nil
                                                     context:context
                                                       error:&error];
    if (!o) {
        NSLog(@"Error: %@, occasion title: %@",error,self.txtOccasionTitle.text);
        return;
    }

    transaction.whoBy = p;
    transaction.occasion = o;
    //Not sure what you are using this property for
    transaction.item = [NSEntityDescription insertNewObjectForEntityForName:@"Item"
                                                     inManagedObjectContext:context];

    NSManagedObjectContext* ctx = context;
    if ([context obtainPermanentIDsForObjects:[context.insertedObjects allObjects]
                                        error:&error])
    {
        //save your changes to the store
        __block BOOL saveSuccess = YES;
        while (ctx && saveSuccess) {
            [ctx performBlockAndWait:^{
                saveSuccess = [ctx save:&error];
            }];
            ctx = [ctx parentContext];
        }

        if (!saveSuccess) {
            NSLog(@"Could not save transaction, error: %@",error);
        }
    } else {
        NSLog(@"Could not obtain IDs for inserted objects, error: %@",error);
    }

    //Do what you have to do next
}

这只是为了让你更清楚你应该做些什么来避免重复,并重用现有的对象。