NSPredicate to fetch child objects from an entity - objective-c

I have a simple data model with two entities. A parent entity called Character and a child entity called Statiscis. A Character can have multiple Statistics and each statistic can have only one parent, so the relationship is many to one.
From the view controller that displays the details of a Character I call to a new Table VC to list all the Statistics related to this Character. On this controller I have a nice SIGABRT when I try to build the fetchedResultsController: "Unable to generate SQL for predicate (character == currentCharacter) (problem on RHS)".
When I create the Table VC I send the managedObjectContext and the character displayed on the details VC through two properties (same name) on prepareForSegue, so in the table VC self.currentCharacter hosts an instance of a Character managed object.
#pragma mark - NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Statistic"];
// Stupid predicate :(
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"character == self.currentCharacter"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"statName"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
NSError *error = nil;
// Going to crash
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Core Data error: %#, %#", error, [error localizedDescription]);
abort();
}
return _fetchedResultsController;
}
Do not know how to create the predicate, and I tried unsuccessfully several ways

Perhaps:
[NSPredicate predicateWithFormat:#"character == %#", self.currentCharacter];

You want
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"character == %#", self.currentCharacter];

Related

Core Data NSFetchRequest within specific object using NSPredicate

I have a core data entity, "Entity 1" it has a one to many relationship lets call it "entityRelationship" to another entity "Entity 2".
I'd like to be able to perform a NSFetchRequest for use with a NSFetchResultsController to return the list of "Entity 2" objects for a specific "Entity 1" object.
I have the "Entity 1" stored out as it's own variable, but i can't seem to find the correct way to set up an NSPredicate to return the objects:
Here's my code:
NSFetchedResultsController *fetchedEvents;
NSFetchRequest *fetchRequest;
NSError *error = nil;
fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Entity2"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"Entity2 IN self = %#",entity1Object]];
[fetchRequest setSortDescriptors:#[]];// no sort descriptors
fetchedEvents = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:theManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
[fetchedEvents performFetch:&error];
if (error) {
NSLog(#"Unable to perform fetch.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
return fetchedEvents;
This crashes with the following error:
** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "NSMDEvents IN self = %#"'
Am i doing something wrong? Or is this the incorrect way to go about returning entities with relationships?
Since you have entity1Object and the defined relationship, you can retrieve the Entity2 objects directly from there
NSSet *entity2Objects = [entity1Object valueForKey:#"entityRelationship"];
An extra fetch is not needed.
But if you really need the fetch define a reverse relationship and use a property with an unique value.
For example let's assume that entity1 are clubs and entity2 are their members and you want to get all members for a specific club use this predicate:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Member"];
[NSPredicate predicateWithFormat:#"club.name == %#", currentClub.name];
The literal club in the predicate is the reverse relationship object.
Or translated to your example
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Entity2"];
[NSPredicate predicateWithFormat:#"entity1.property == %#", entity1Object.property];
Trying the suggested code (thanks vadian) kept causing my app to crash with various errors regarding keys not existing etc, this turned out to be down to a relationship issue.
"Entity2" was inheriting from another entity (had its parent Entity field set in the Data Model Inspector)"Entity 0". However the relationship between "Entity1" was between itself and "Entity0" not "Entity2".
So after a rejig of the core data model "Entity2" had a relationship added (lets call it "EntityEvents") between itself and "Entity1". Now using the following code i was able to select the specific events from the current object:
NSFetchedResultsController *fetchedEvents;
NSFetchRequest *fetchRequest;
NSError *error = nil;
fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Entity2"];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"EntityEvents == %#",Entity1]];
[fetchRequest setSortDescriptors:#[]];// no sort descriptors
fetchedEvents = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:theManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
[fetchedEvents performFetch:&error];
if (error) {
NSLog(#"Unable to perform fetch.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
return fetchedEvents;

NSPredicate to filter nested CoreData Models

I'm having a CoreData models like bellow:
Country <---->>> City (One-to-many relationship)
Listed into a UITableView as section/row structure i.e.
===Egypt===
Cairo
Alexandria
Luxur
Ras AlBar
.
.
===Qatar===
Doha
.
.
.
Now I have UISearchBar that should allow user to filter the TableView based on the existence of the entered word within both Country name OR City name
So that if user inserted 'ar' the following should show up:
===Egypt===
Ras AlBar
===Qatar===
Doha
.
.
.
Can't find any optimum query in CoreData to achieve what I want.
You should be using an NSFetchedResultsController for this. You don't mention whether you are but it makes it a lot easier to display CoreData into a TableView.
Anyway, the fetch for your NSFetchedResultsController should be on the cities. Not the countries. (Again, you don't say which it is but just in case).
So the predicate you can use is...
NSPredicate *cityPredicate = [NSPredicate predicateWithFormat:#"name [cd]CONTAINS %#", text];
NSPredicate *countryPredicate = [NSPredicate predicateWithFormat:#"country.name [cd]CONTAINS %#", text];
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubPredicates:#[cityPredicate, countryPredicate]];
Then when you use the compoundPredicate for your fetch you will get what you are looking for.
How to set up your NSFetchedResultsController
In your CountryTableViewController.m file
1 - Create properties...
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
2 - Grab the Managed Object Context from your source...
self.managedObjectContext = [(AppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
3 - Add this method...
#pragma mark - fetched results controller
- (NSFetchedResultsController*)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"City"];
[request setFetchBatchSize:20];
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"country.name" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sd]];
NSFetchedResultsController *aController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"country.name" cacheName:nil];
aController.delegate = self;
self.fetchedResultsController = aController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
4 - For the method that filters the table...
- (void)filterResultsWithText:(NSString*)text
{
NSPredicate *cityPredicate = [NSPredicate predicateWithFormat:#"name [cd]CONTAINS %#", text];
NSPredicate *countryPredicate = [NSPredicate predicateWithFormat:#"country.name [cd]CONTAINS %#", text];
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubPredicates:#[cityPredicate, countryPredicate]];
[self.fetchedResultsController.fetchRequest setPredicate:compoundPredicate];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Handle error
//exit(-1);
}
[self.tableView reloadData];
}
5 - Display the data
This will now set your data up, you don't need any more arrays.
To display it you can do...
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
- (NSString*)tableView:(UITableView*)tableView titleForSection:(NSInteger*)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo title];
}
To get the country for at an index path you can do this...
Country *country = [self.fetchedResultsController objectAtIndexPath:indexPath];
This will return the country at that index path.
There is a bit to set up but once you've done it once you will see why it is so easy.

NSFetchRequest returns wrong entity type

My Core Data app has two entities: "Note" and "Marker". The Note entity has a 1-to-many relationship to markers (ie. A note contains many markers). I have a fetchedRequestController which is responsible for fetching all the "Note" entities. After creating 1 note and 1 marker (which belongs to that note) I get an error because the fetchedRequestController fetches both the Marker and Note. The Note is expected but the Marker should not be fetched.
Here is my fetched request controller
TCModel *model = [TCModel sharedModel];
NSManagedObjectContext *context = [model managedObjectContext];
NSParameterAssert(context);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Note" inManagedObjectContext:context];
NSParameterAssert(entity);
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:#"Root"];
NSParameterAssert(controller);
self.fetchedResultsController = controller;
controller.delegate = self;
NSError *error;
BOOL success = [controller performFetch:&error];
if ( success == NO )
{
NSLog(#"Failed to fetch!");
NSParameterAssert(nil);
}
The objects are created using two helper methods and then saved using a third
- (TCNote *)newNote
{
TCNote *note = [NSEntityDescription insertNewObjectForEntityForName:#"Note"
inManagedObjectContext:self.managedObjectContext];
note.creationDate = [NSDate new];
return note;
}
- (TCMarker *)newMarker
{
TCMarker *marker = [NSEntityDescription insertNewObjectForEntityForName:#"Marker"
inManagedObjectContext:self.managedObjectContext];
return marker;
}
- (void)_save
{
NSError *error;
NSLog(#"Saving");
if (![self.managedObjectContext save:&error])
{
NSLog(#"Error saving context: Error = %#", error);
}
}
Here is the console output:
2013-01-03 17:41:12.062 timecode[10269:c07] CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
2013-01-03 17:41:12.063 timecode[10269:c07] CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZNOTEDATE, t0.ZSCENEDESCRIPTION, t0.ZSCENETITLE, t0.ZTIMECODEDATE, t0.ZDATE, t0.ZTITLE, t0.ZPARENT, t0.Z1_PARENT FROM ZNOTE t0
2013-01-03 17:41:12.064 timecode[10269:c07] CoreData: annotation: sql connection fetch time: 0.0005s
2013-01-03 17:41:12.064 timecode[10269:c07] CoreData: annotation: total fetch execution time: 0.0011s for 2 rows.
You have defined Note as "parent entity" of Marker, i.e. Marker is a "child entity" of Note:
That means that every Marker object is also a Note object (same as with class and subclass). Fetching all objects of the Note entity does therefore also return the objects of the Marker entity.
The SQLite file contains in this case only one table ZNOTE, which has columns for the properties of Note and for the additional properties of Marker.
So if you don't really need Marker to be a child entity of Note, just set the "Parent Entity" to "No Parent Entity".

How to filter NSFetchedResultsController like "filteredArrayUsingPredicate"

is it possible, to filter the results of a NSFetchedResultsController without a new call to the databaselayer, like I do it with an NSArray with "filteredArrayUsingPredicate"
Thanks
You can set a predicate on the NSFetchRequest that you use to initialize your NSFetchedResultsController. For example:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = <YOUR ENTITY>
fetchRequest.predicate = [NSPredicate predicateWithFormat:<YOUR PREDICATE>];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc sectionNameKeyPath:nil cacheName:nil];
// ...
You shouldn't need to "refresh" the fetched results controller since it should update as changes are made and saved. You may need to use the boilerplate code for using and/or updating a table view with a fetched results controller.

Core Data & Generating Model Entities

Standard newbie question. I've created a data model for an iOS application. I am able to create, update and delete entities within the model from various views by using the NSEntityDescription object.
Say if I had a mutable array of objects returned from a fetch request. How can I loop through each one when I do not have a generated object definition from the entity model? By generated object definition I mean, a header and body class definition of the entity described in the data model package.
All CoreData entities derive from NSManagedObject and all the database data from those can be accessed via key value encoding. The minimum you need to know can be gained from the Model. You don't necessarily require the headers.
For example an entity PersonEntity which has a relationship to NameEntity with attribute firstname
NSArray *results = [managedObjectContext queryEntityForName:#"PersonEntity" predicateFormat:nil argumentArray:nil];
for(NSManagedObject *object in results)
{
NSString *name = [object valueForKeyPath:#"nameobject.firstname";
[self doSomething:name];
}
queryEntityForName is my own category. You might find it useful.
#implementation NSManagedObjectContext(VMQueryAdditions)
-(NSArray *)queryEntityForName:(NSString *)name predicateFormat:(NSString *)pstring argumentArray:(NSArray *)arr
{
NSEntityDescription *entity = [NSEntityDescription entityForName:name inManagedObjectContext:self];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:entity];
NSPredicate *pred;
if(pstring)
{
if(arr) pred = [NSPredicate predicateWithFormat:pstring argumentArray:arr];
else pred = [NSPredicate predicateWithFormat:pstring];
[fetch setPredicate:pred];
}
NSError *error = nil;
NSArray *results = [self executeFetchRequest:fetch error:&error];
if (error) {
NSLog(#"MOC Fetch - Unresolved error %#, %#", error, [error userInfo]);
return [NSArray array];
}
return results;
}
#end