Core Data fetch unique property - ios

I have a core data object called Item, it has 2 properties identifier and type
Item
----
identifier
type (A/B)
It is possible to have 2 items with the same identifier but with different type.
I need to fetch all the items with one condition:
if two items has the same identifier, only show the A type.
In other words I want to make sure that the fetched items will have a unique identifier, and in the case of multiple items with the same identifier, priority will be given to item with type A.
I also prefer not to use NSDictionaryResultType if possible

You can fetch from Core Data with a predicate to find (or count) objects with an identifier (btw do NOT use id, it is a reserved word in Cocoa) and you can sort by another property and then you can limit your fetch result to a single item.
That will give you your priority you are looking for. However, I strongly recommend against handling the issue that way and instead writing your creation code to avoid having duplicates in the first place.
Update
What I am saying is that you can't "fetch all the items with one condition...". What you can do is fetch per item (using the identifier), limiting the fetch to a single result sorted by type which will give you your results.
If you wanted all items of type "a", that is doable.
If you wanted all items and filtered on type in a second pass in memory, you could do that
You cannot combine them.
Now, if you want to fetch per item, filtering on the type it would look like this:
NSManagedObjectContext *moc = ...;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
[fetchRequest setFetchLimit:1];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"identifier == %#", myIdentifier];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"type" ascending:YES];
NSArray *sortArray = [NSArray arrayWithObject:sort];
[fetchRequest setSortDescriptors:sortArray];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:fetchRequest error:&error];
if (!results) {
NSLog(#"Error: %#\n%#", [error localizedDescription], [error userInfo]);
abort();
}
id mySingleObject = [results lastObject];
NOTE: This code was written in the browser, there are probably errors.

Related

Core Data, how can i find and delete managed objects effectively

My app sends a get request to a server with a date (date of last update) to update its content (2 entities in core data, no relationships, all attributes are strings)... most of the time it only receives new content but sometimes it also receives the old content that needs to be updated instead of just inserted. 1 table is pretty straight forward I just get the ids (id from server not form core data) of the items that are going to be updated in an array and I make a fetch of those items and then delete them. After that I insert the updated items as they were new. This is how I delete them:
-(void)deleteOfEntity:(NSString*)entityName theItemsWithIds:(NSArray*)ids{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext: [self managedObjectContext]];
[fetchRequest setEntity:entity];
[fetchRequest setIncludesPropertyValues:NO];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"(id IN %#)", ids]];
NSError *error;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if(fetchedObjects && [fetchedObjects count]>0){
for (NSManagedObject* toDelete in fetchedObjects) {
[[self managedObjectContext] deleteObject:toDelete];
}
}
}
because the attribute name which identifies each item is the ID as usually. But the other table needs 2 attributes to identify items, like a composite key. How do I build up the array of "ids"? an array with arrays of 2 values indicating the composite key? and the predicate? I just want to know if it is possible to do this efficiently, if not I can always fetch all the items and check 1 by 1 but for that I need a for inside another for and that is to ugly. Any help is appreciated.
When you designed the database you should have created a unique key field, even if it is just a composite of the two values. That would have made this question go away.
However, to solve the problem now you need to do a fetch on one key similar to what you have above and then loop over the second key. However, you do not need to do a loop within a loop. You can use a second NSPredicate against that returned array to get the objects to modify.
Less ugly and quite efficient since you are only going to disk once and the second filter is happening in memory.
Update
#DuncanGroenwald is correct that you still must loop through every object, but there is looping and there is looping.
A developer writing a for loop and then doing a string compare inside of that for loop is significantly less efficient then letting the frameworks perform the same option. How? With a NSPredicate against the array:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"someValue IN %#", idArray];
NSArray *results = [origArray filteredArrayWithPredicate:predicate];
If you test both options, the predicate will run significantly faster.
Well what I did is to create another attribute named "identifier" which is a stringWithFormat:#"%#%#",key1,key2, it doesn't matter the extra string in coredata because it suppose to have just a few managed objects in that entity

Updating Core Data Properties WITHOUT using Table Views

I have a POS type app that uses Core Data to store daily sales transactions using table views. I am attempting to retrieve and update certain Core Date Properties, like daily sales counts, WITHOUT using table views. Table views use row at index path to point to the correct object (row). I am using the Fetched Results controller with a predicate to retrieve the fetched object (row) Question: How do I obtain the index of the fetched row so that I can retrieve and then update the correct property values? All books and examples use table views to change properties.
Entity Product
Product *product;
______________________________
[self setupFetchedResultsController]; (This returns one object)
product = [NSFetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; (objectAtIndexPath - Errors of course)
I think you shouldn't use NSFetchedResultsController in this case. If you don't want to use it in either a UITableView or a UICollectionView, you're probably better of without it. You're probably better of using a NSFetchRequest instead, it's pretty easy to set up:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Entity"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"someValue=1"];
fetchRequest.predicate = predicate;
NSError *error = nil;
NSArray *array = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
Now you have a NSArray with all the results, which you could use without having to deal with index paths.
If you're still using a NSFetchedResultController for a table (I'm not sure if you do), those rows will still be updated whenever you make a change.
Update: To update one of the objects returned by the fetch, could be done like this:
Entity *entity = [array firstObject];
[entity setSomeProperty:#"CoreDataIsAwesome"];
NSError *error = nil;
if ([self.managedObjectContext save:&error]) {
NSLog(#"Entity updated!");
} else {
NSLog(#"Something went wrong: %#", error);
}
You can use the method indexPathOfObject: on your fetched results controller to return the index path of the given object to then do your updates.

Distinct select from Core Data

I have the following code to select the names of staff from a Core Data entity called StaffRecords. I want to select the records Distinctly but this selects all the records. How do I make the result Distinct?
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"StaffRecords"];
[fetchRequest setPropertiesToFetch:#[#"StaffName"]];
[fetchRequest setReturnsDistinctResults:YES];
NSError *error = nil;
self.StaffNames = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[self.tableView reloadData];
Using setPropertiesToFetch and setReturnsDistinctResults is correct. But, you also need to set resultType to NSDictionaryResultType.
Note that at the time of writing propertiesToFetch is documented to be an array of NSPropertyDescription instances, but an array of strings of the key names does also work.

How to fetch all to many items from a specific Core Data parent entity ID

I am pretty new to this and assume I am missing something very simple, I have tried this site and anywhere I could search but found no straightforward answer.
My situation..
My model can have many Calisthenic Entities which can of course contain many sets.
I have a calisthenic detail controller which uses a fetch request to pull all the sets data and display it. With the code below my detail view works fine.
The problem is, if I create and enter another calisthenic detail view my fetch of course pulls all the Set entities from all the calisthenic entities, I of course only want to pull the sets for the calisthenic ID I am interacting with.
So how to I pull all the sets related to 1 calisthenic ID in my fetch request?
- (void)fetchSets
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Set"];
NSString *cacheName = [#"Set" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"setNumber" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:self.calisthenic.managedObjectContext sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Fetch failed: %#", error);
}
}
You can use NSPredicate to filter the fetch request based on the parent object which is the Calisthenic.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"calisthenic == %#", calisthenic]
[fetchRequest setPredicate:predicate];
The line above assumes the property of the parent is called calisthenic.
Or if you have created model files (the subclasses of NSManagedObject) for Calisthenic and Sets, you can simply take the parent calisthenic object and get the sets directly.
calisthenic.sets
in which NSSet is returned.
Take a look at the following tutorial on how to generate model files: Getting started with Core Data

Any way to optimize simple NSFetchRequest for single object?

I'm dong data processing in a child moc in a background queue. I need to query the database by ID so that I can differentiate updating-existing-object from creating-new-object. I found most of the time(the total processing time is about 2s for 50 items) is consumed by executeFetchRequest:error:. The NSPredicate is of the simplest form — only to match a single ID attribute(ID attribute is already indexed), and the NSFetchRequest should return one or none(ID is unique). Is there any way to optimize this kind of NSFetchRequest?
Here is my current code:
+ (User *)userWithID:(NSNumber *)ID inManagedObjectContext:(NSManagedObjectContext *)context {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ID == %#", ID];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:1];
NSError *error = nil;
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
abort();
}
if ([users count] == 1) {
return [users objectAtIndex:0];
} else if ([users count] > 1) {
// Sanity check.
…
} else {
return nil;
}
}
As #ChrisH pointed out in comments under the question, doing a fetch for every ID is no good. So I changed my processing flow to this:
Enumerate data the first time to extract IDs.
Do a single fetch to fetch all existing users matching IDs and put them in a dictionary keyed by ID(named as existingUsers).
Enumerate data the second time to do the real processing: in each iteration, either update one existing user found in existingUsers or create a new user, add it into existingUsers if it is new.
The code is almost doubled, but so is the performance. Really good tradeoff!
To expand on my comment to the original question, it's not efficient to repeatedly perform fetch requests with Core Data when importing data.
The simplest approach, as #an0 indicated, is to perform one fetch of all the existing objects you will be checking against, and then constructing an NSDictionary containing the objects with the attribute you will be checking as keys. So sticking with the original User and userID example:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSError *error = nil;
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
//handle appropriately
}
NSMutableDictionary *userToIdMap = [NSMutableDictionary dictionary];
for (User *user in users){
[userToIdMap setObject:user forKey:user.ID];
}
Now in your method that processes new data you can check the userToIdMap dictionary instead of making fetch requests.
A more sophisticated approach, suited to larger data sets, is outlined in the Core Data Programming Guide's Efficently Importing Data. Take a look at the section called 'Implementing Find-Or-Create Efficiently'. The approach suggested by Apple here misses out some code regarding how to walk the arrays you create, and my solution to that problem is in this SO question: Basic array comparison algorithm

Resources