Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
369 views
in Technique[技术] by (71.8m points)

ios - Querying Core Data for Specific Attributes when Creating New Objects and returning the object if it exists, or creating a new one if it does not

I have a problem checking whether a particular attribute of an Entity exists in the Core Data Database (through predicates) before creating a new object; if the object exists, I'd rather return it than create a new object.

I have a simple App which has a table view with a plus button in the Navigation Bar; the user clicks that and is presented with a View Controller with 4 text fields. They fill in that information, press save and it gets saved to Core Data and displayed in the TableView (through the use of NSFetchedResultsControllers).

The data model is as follows:

Transaction Entity with isReceived BOOL attribute Person Entity with name string attribute Occasion Entity with title string attribute Item Entity with amount string attribute

The transaction has a relationship to the Person (whoBy), Occasion (Occasion) and Item entities.

In the view controller with the save method, I have the code below which will insert new objects into the Transaction, Person, Occasion Entities, etc. Each Transaction is of course unique, but with each transaction, the user can select an existing PERSON and/or Occasion and if that person then does not exist, it will be created (likewise with the occasion).

I'm slightly confused as to the format of the code here.

EDIT: I have tried a combination of code and can just not get this working. In the code below, I'm referencing person.name in the predicate, but I also tried creation a local NSString variable to hold the self.nameTextField.text code but that did nothing. I tried creating a NSString property to reference it that way and that not work. I tried using the words MATCHES, LIKE, CONTAINS, == and every combination in-between.

- (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];

}

Logically, what I want to achieve is:

  • When the user is adding a Transaction, check if it's for a new person or an existing one — if it's an existing one, choose it from a list of Persons (and when the user selects a person, get its NSManagedObjectID). If it's a new one, create it on the spot.

  • The same for the Occasion.

Set all the other fields of the Transaction object (amount, etc.).

My question is:

  • What predicate do I use to get this working?

When I put a break point in this method, a NEW NAME (one that doesn't exist before) correctly calls the else if ([matches count] == 0) method and if I create an entry with an existing name, it calls the

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

Even with the this statement, it is still creating a new person object for the same name.

I will correctly implement the occasion after getting the person working, but I'm just lost on how to get this working.

Any help would be massively appreciated!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

"Is this correct?":
No. You are creating a new Person and Occasion objects whether you are using an existing person/occasion or not.
First check for existence and only if the object not already exist, insert a new one.
Alternatively, if the person/occasion exist, delete the inserted object.

"How do I retrieve the managedObjectID for person/event?":

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

To find a person that start with a string str use this predicate in a fetch request:

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

Edit:

To be more precise, you practice find or create using something like this:
(this is very limited, and only good for a single object performance-wise) (NOT TESTED)

- (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;
}

So now, your save: method should look something like:
(I assume here that the person name and occasion title are held by a UITextField on the view controller [txtPersonName and txtOccasionTitle respectively] )

- (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
}

This is just for making things a bit clearer on what you should do to avoid duplications, and reuse existing objects.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...