Handling duplicate entries in Core Data

一曲冷凌霜 提交于 2020-12-28 07:38:42

问题


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!


回答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




回答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




回答3:


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
}



回答4:


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.




回答5:


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
}



回答6:


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.



来源:https://stackoverflow.com/questions/18262003/handling-duplicate-entries-in-core-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!