Handling duplicate entries in Core Data

Handling duplicate entries in Core Data

I have an app that allows users to save favorites. I am using Core Data to store the favorites as managed objects. I have written some code to prevent the possibility of storing duplicates, but am wondering if there is a better way to do so. Each favorite object has an ID field that is unique. In the following code I am simply looping through and checking the ID field, and if the value already exists, setting a flag value to true, and breaking out of the loop. 
-(BOOL)addFavorite{
    BOOL entityExists = NO;
    if(context){
        // does this favorite already exist?
        NSArray *allFaves = [AppDataAccess getAllFavorites];
        for(Favorite *f in allFaves){
            if([f.stationIdentifier isEqualToString:stID]){
                entityExists = YES;
                break;
            }
        }
        if(!entityExists){
            NSError *err = nil;
            Favorite *fave = [Favorite insertInManagedObjectContext:context];
            fave.stationRealName = riverGauge.name;
            fave.stationIdentifier = stID;
            fave.stationState = @"WV";
            if(![context save:&err]){
                NSLog(@"ERROR: Could not save context--%@", err);
            }
            return YES;            
        }
    return NO;
}

I was wondering if Core Data has the ability to check to see if an object being added is a duplicate. Is there a predicate that can handle checking for duplicates? Thanks!

Solutions/Answers:

Answer 1:

CoreData does no uniquing by itself. It has no notion of two entries being identical.

To enable such a behavior you have to implement it yourself by doing a ‘search before insert’ aka a ‘fetch before create’.

NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"Favorite"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"stationIdentifier == %@", stID];
[fetch setPredicate:predicate];
YourObject *obj = [ctx executeRequest:fetch];

if(!obj) {
    //not there so create it and save
    obj = [ctx insertNewManagedObjectForEntity:@"Favorite"]; //typed inline, dont know actual method
    obj.stationIdentifier = stID;
    [ctx save];
}

//use obj... e.g.
NSLog(@"%@", obj.stationIdentifier);

Remember this assumes single-threaded access

Answer 2:

Just an update since iOS 9.0 you can do it easily with “unique constraints” in the model. But pay attention if your store already contains duplicate core data will fail any auto migration when the app shipped.

See here for example – core data unique constraints

Answer 3:

I was wondering if Core Data has the ability to check to see if an object being added is a duplicate.

No, Core Data doesn’t care about that.

Is there a predicate that can handle checking for duplicates?

Since your objects have unique IDs that you control, do a fetch for an existing favorite with that ID. Something like

NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"Favorite"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"stationIdentifier == %@", stID];
[fetch setPredicate:predicate];

If you get any results, you know that a favorite with that ID already exists. And, you have a reference to that favorite in case you want to change it.

Your current approach is fine and probably faster if there are only a few favorites. Doing a fetch will scale better to lots of favorites.

Answer 4:

Swift 3:

func isExist(id: Int) -> Bool {
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: myEntityName)
    fetchRequest.predicate = NSPredicate(format: "id = %d", argumentArray: id)

    let res = try! theContext.fetch(fetchRequest)
    return res.count > 0 ? true : false
}

Answer 5:

If you’re dealing with multiple records, iterating over a count fetch or retrieving actual objects is VERY costly on the CPU. In my case, I did one fetch for all matching records, but asked for a dictionary of just the string of the UUID back. Saves a lot of CPU overhead.

For example, I have a uUID property on every record in core data. I have a corresponding UUID listed as @”UUID” in CloudKit.

  //1. Create a request for the entity type, returning an array of dictionaries  
      NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"someEntityName"];
      [request setResultType:NSDictionaryResultType];
      [request setReturnsDistinctResults:YES];
      [request setPropertiesToFetch: @[@"uUID"]];

  //2. Create an array of UUID strings of the downloaded objects
      NSMutableArray *UUIDstrings = [NSMutableArray new];
      for (CKRecord *record in ckRecords) {
        [UUIDstrings addObject:record[@"UUID"]];
      }

   //3. Create a predicate to find any Core Data objects with the same UUID
      [request setPredicate:[NSPredicate predicateWithFormat:@"uUID in %@", UUIDstrings]];

   //4. If there are results from the fetch, do a log and you'll see it's a dictionary. 
      NSArray *deck = [self.MOC executeFetchRequest:request error:nil];

      NSLog(@"Logging the result of index 0. Should be a dictionary %@", deck.count > 0 ? [deck objectAtIndex:0] : @"No results");

   //5. Then either do an embedded fast enumeration (for xx in xx){for xx in xx} to find a match like         

           if ([(NSString *)record[@"UUID"] isEqualToString:[dict valueForKey:@"uUID"]]) 
          {do something}

   //...Or 6. Use a more linear approach with NSSet

    //Harvest the core data strings
      NSMutableArray *coreDataStrings = [NSMutableArray new];
        for (NSDictionary *dict in deck) {
        [coreDataStrings addObject:[dict objectForKey:@"uUID"]];
      }

   //Create a set of your downloaded objects
      NSSet *arraySet = [NSSet setWithArray:ckRecords];

   //Then use a predicate search - a NOT version of above
     NSArray *final = [[arraySet filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"NOT(UUID in %@)", coreDataStrings]]allObjects];

The console log of the dictionary will look something like this. Just the smallest amount of info required to match:

dictionary {
   uUID = "AFACB8CE-B29E-4A03-9284-4BD5F5464";
}

More here at the developer site on finding unique values.

Answer 6:

Swift 4 Curled from @Vahid answer

     func isEntityAttributeExist(id: Int, entityName: String) -> Bool {
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        let managedContext = appDelegate.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
        fetchRequest.predicate = NSPredicate(format: "id == %@", id)

        let res = try! managedContext.fetch(fetchRequest)
        return res.count > 0 ? true : false
      }

Answer 7:

http://dorianroy.com/blog/2015/09/how-to-implement-unique-constraints-in-core-data-with-ios-9/

Check out the link. Its available from ios9. You can set keys in the constraints which will stop duplicates. The blog goes into greater detail.

Hope it helps others 🙂

References

Loading...