处理Core Data中的重复条目

时间:2021-08-02 20:24:31

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.

我有一个应用程序,允许用户保存收藏夹。我使用Core Data将收藏夹存储为托管对象。我已经编写了一些代码来防止存储重复项的可能性,但我想知道是否有更好的方法可以这样做。每个喜欢的对象都有一个唯一的ID字段。在下面的代码中,我只是循环并检查ID字段,如果该值已经存在,则将标志值设置为true,然后断开循环。

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

我想知道Core Data是否能够检查添加的对象是否重复。是否有可以处理重复检查的谓词?谢谢!

6 个解决方案

#1


16  

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

CoreData本身并不是唯一的。它没有两个条目相同的概念。

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


4  

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

我想知道Core Data是否能够检查添加的对象是否重复。

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

由于您的对象具有您控制的唯一ID,因此请使用该ID对现有收藏夹进行提取。就像是

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.

如果您得到任何结果,您就知道已经存在具有该ID的收藏夹。并且,如果您想要更改它,您可以参考该最爱。

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.

您目前的方法很好,如果只有少数收藏,可能会更快。进行抓取会更好地扩展到很多收藏夹。

#3


3  

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.

只需从iOS 9.0开始更新,您就可以通过模型中的“唯一约束”轻松完成。但请注意,如果您的商店已包含重复的核心数据,则在应用程序发布时,任何自动迁移都将失败

See here for example - core data unique constraints

请参阅此处 - 核心数据唯一约束

#4


2  

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
}

#5


1  

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.

如果您正在处理多个记录,则迭代计数获取或检索实际对象在CPU上非常昂贵。在我的例子中,我对所有匹配的记录进行了一次提取,但是要求提供一个只返回UUID字符串的字典。节省了大量的CPU开销。

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

例如,我在核心数据的每条记录上都有一个uUID属性。我在CloudKit中将相应的UUID列为@“UUID”。

  //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.

更多信息,请访问开发者网站,查找唯一值。

#6


0  

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.

看看链接。它可从ios9获得。您可以在约束中设置键以停止重复。博客更详细。

Hope it helps others :)

希望它能帮助别人:)

#1


16  

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

CoreData本身并不是唯一的。它没有两个条目相同的概念。

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


4  

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

我想知道Core Data是否能够检查添加的对象是否重复。

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

由于您的对象具有您控制的唯一ID,因此请使用该ID对现有收藏夹进行提取。就像是

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.

如果您得到任何结果,您就知道已经存在具有该ID的收藏夹。并且,如果您想要更改它,您可以参考该最爱。

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.

您目前的方法很好,如果只有少数收藏,可能会更快。进行抓取会更好地扩展到很多收藏夹。

#3


3  

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.

只需从iOS 9.0开始更新,您就可以通过模型中的“唯一约束”轻松完成。但请注意,如果您的商店已包含重复的核心数据,则在应用程序发布时,任何自动迁移都将失败

See here for example - core data unique constraints

请参阅此处 - 核心数据唯一约束

#4


2  

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
}

#5


1  

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.

如果您正在处理多个记录,则迭代计数获取或检索实际对象在CPU上非常昂贵。在我的例子中,我对所有匹配的记录进行了一次提取,但是要求提供一个只返回UUID字符串的字典。节省了大量的CPU开销。

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

例如,我在核心数据的每条记录上都有一个uUID属性。我在CloudKit中将相应的UUID列为@“UUID”。

  //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.

更多信息,请访问开发者网站,查找唯一值。

#6


0  

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.

看看链接。它可从ios9获得。您可以在约束中设置键以停止重复。博客更详细。

Hope it helps others :)

希望它能帮助别人:)