Can I fetch relationship's data in Core Data?
The relationship is one-many.
For example: I have 2 entities (department and employee). The department has a to-many employees relationship and employee has a to-one department relationship. I want to get employees of department entity. Can I using fetch function to obtain the data?
Thanks in advance.
You can just use the relationship property to get the set of employees for a department:
Department *theDepartment = ...; // your department
NSSet *employeesInDepartment = theDepartment.employees; // set of Employee objects
Or, if you need an array:
NSArray *employeesInDepartment = [theDepartment.employees allObjects];
Alternatively, you can use the following fetch request:
Department *theDepartment = ...; // your department
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Employee"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"department = %#", theDepartment];
[request setPredicate:predicate];
NSError *error;
NSArray *employees = [yourManagedObjectContext executeFetchRequest:request error:&error];
Related
Let's say my data model has two entities: Department and Person. Departments has a to-many relationship called 'departmentToPerson' with Person. Person has a to-one relationship called 'personToDepartment' with Department.
I want to populate an array of people belonging to a selected department. To selected the department I've created a UILabel that displays a departmentName that is selected from a popup tableview. When I run the app the log shows:
personToDepartment.departmentName == (entity:
Department; id: 0x8cc1920
;
data: {
departmentName = Marketing;
departmentToPerson = "";
I've read the purpose of faults and implemented setRelationshipKeyPathsForPrefetching, but still I get the fault. I'll put in the disclaimer that I'm a newbie and may be missing something obvious. When I delete the predicate my table view populates with all personName. Here's the code:
- (NSFetchedResultsController *)fetchedResultsController {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Person" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"personToDepartment.departmentName = %#", selectedDepartment];
NSLog(#"predicate is: %#",predicate);
[fetchRequest setPredicate:predicate];
[fetchRequest setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObjects: #"departmentToPerson", nil]];
fetchRequest.returnsObjectsAsFaults = NO;
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"personName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Again, I select a new department from the UILabel, the log displays selectedDepartment, but then states relationship fault and does not populate the table.
Thanks in advance for the help!
Updated 9JUNE
I've also found this works:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"personToDepartment = %#", selectedDepartment];
I'm also kind of newbie to CoreData, but it seems to me that predicate is wrong. According to Log record you're about to comparing personToDepartment.departmentName to a department Object. Predicate should look like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"personToDepartment.departmentName = %#", selectedDepartment.departmentName];
But there is a better way. selectedDepartment.departmentToPerson will return an NSSet with all persons belonging to this department (if relationship was set previously). But warning, if you'll try to NSLog(#"%#", selectedDepartment.departmentToPerson) probably you'll get "relationship fault", because CoreData will not do fetch until you address specific object in NSSet or enumerate through it. But for example NSLog(#"%d", selectedDepartment.departmentToPerson.count) will force CoreData to make fetch and you'll see number of persons in department.
UPDATE:
NSSet is empty probably because you are not setting relationship, when creating person's object. Inverse To-Many relationship from department to persons will be set automatically.
- (id)insertNewPerson:(NSDictionary *)personInfo inDepartment:(Department*)departmentObject
{
Person *personObject = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
personObject.name = personInfo.name;
personObject.departmentName = departmentObject.name;
...
personObject.personToDepartment = departmentObject; //this is RELATIONSHIP, if set, then you can access all persons from specific department by doing: departmentObject.departmentToPerson
}
I am actually new to iPhone programming and have successfully create an app but I can't figure out how to retrieve the values stored in NSSet. I have 2 entities in core data related to each other. Users one-to-many to Scores. Users entity has firsNname and lastName attributes and Scores entity has onTime, wasAbsent, and dateIn attributes. I fetch using predicate based on firstName and lastName and then execute the fetch. The fetch is successful and I am able to get both entities in one fetch call. However I cannot get values for the values from Scores entity. Whatever I do, it always returns NSSet object. What I want to do is to retrieve the boolean value which was stored in onTime and wasAbsent attributes and feed them to UISwitch.
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
self.managedObject = delegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Student"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"firstname == %# AND lastname == %#", #"James", #"Smith"];
NSError *error = nil;
self.scoresArray = [self.managedObject executeFetchRequest:fetchRequest error:&error];
if ([self.scoresArray count]) {
Student *student = [self.scoresArray objectAtIndex:0];
NSSet *score = student.scores;
NSArray *arr = [score allObjects];
NSLog(#"%#", [arr objectAtIndex:0]);
}
if I can directly access the Score entity instead of using NSSet, that would be ideal that way I can reference it using a dot notation.
Any help would be appreciated. Thanks
Use something like the following:
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = delegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Student"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"firstname == %# AND lastname == %#", #"James", #"Smith"];
NSError *error = nil;
NSArray *studentArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (Student *student in studentArray) {
Student *student = [self.studentArray objectAtIndex:0];
NSLog(#" student is %# %#", student.firstname, student.lastname);
for (Score *score in student.scores) {
// Do something with score
NSLog(#" score is %#", score);
}
}
Because Student has to-many relationship to Scores (and the property for it is scores), when you get the value of student.scores, it returns an unordered collection, which is NSSet in Foundation. So you just work with it like you work with NSSet and with collections in general.
I have three entities
Forms{
name:string
jobs<-->>JSAjobs.form
}
JSAjobs{
name:string
form<<-->Forms.jobs
}
Jobs{
step:string
jobs<<-->Forms.jobs
}
I am getting this error:
to-many relationship fault "jobs" for objectID 0x95afe60. . . fulfilled from database. Got 0 rows
Now I save the row for Forms entity first later on I need to fetch the last record on the Form entity create a new row on JSAjobs with details on JSAjop like next
Thanks
NSMutableArray *jobData = [[NSMutableArray alloc]initWithArray:controller.jobData];
NSManagedObjectContext *context = [self managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"JSAform" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *testForFalse = [NSPredicate predicateWithFormat:#"emailed == NO"];
[fetchRequest setPredicate:testForFalse];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
NSLog(#"Fetched Rows: %i", [fetchedObjects count]);
//NSManagedObject *existingParent= //... results of a fetch
JSAform *lastForm = [fetchedObjects objectAtIndex:0];
JSAjobs *newJobs = [NSEntityDescription insertNewObjectForEntityForName:#"JSAjobs" inManagedObjectContext:context];
// Setting new values
newJobs.jobType = [NSString stringWithFormat:#"%#", [jobData objectAtIndex:0]];
newJobs.jobName = [NSString stringWithFormat:#"%#", [[[jobData objectAtIndex:1]objectAtIndex:0] objectAtIndex:0]];
newJobs.comments = [NSString stringWithFormat:#"%#", [[[jobData objectAtIndex:1]objectAtIndex:0] objectAtIndex:1]];
newJobs.date = [NSDate date];
[newJobs setValue:lastForm forKey:#"form"];
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
//New SOP Value
JOBsop *jobSOP = [NSEntityDescription insertNewObjectForEntityForName:#"JOBsop" inManagedObjectContext:context];
for (int i = 0; i< [[jobData objectAtIndex:1]count]; i++){
NSLog(#"Value for key: %i", i);
if (i > 0){
for (int k = 0; k< [[[jobData objectAtIndex:1]objectAtIndex:i] count]; k++){
jobSOP.step = [[[jobData objectAtIndex:1]objectAtIndex:i] objectAtIndex:k];
[jobSOP setValue:newJobs forKey:#"jobs"];
// [NSNumber numberWithInt:[txtBoots.text integerValue]];
NSLog(#"Simple key: %#", [[[jobData objectAtIndex:1]objectAtIndex:i] objectAtIndex:k]);
}
}
}
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
enter code here
Your entities are very confusing because you did not pick usable entity names. You were too confused to lay out these simple relationships yourself. This results in chaotic code and does not allow you to think things through in a structured way.
Your code is completely incomprehensible. You have a data array despite a fetched results controller (presumably). The second part of your code sports a mysterious and cryptic new entity JOBsob. No way you can ask a meaningful question like this, let alone get an answer.
You have nested arrays without any type checking which is bound to break and in any way completely impossible to debug. Get rid of all of these.
Nevertheless, let's five it a try to get you started:
First, it does not make sense to use the plural for the entity name. If the entity represents a "form", it should be Form not Forms.
Maybe you want this setup:
Form <<----> Job <----->> JobDetail
One job has many forms and many job details. So the Form has a relationship job while the Job has a relationship forms. Similarly, a Jobdetail has a relationship job while Job has a relationship details.
When you have a form and create a new job you can only assign one job to it. Thus, the old job (if any) relationship would be broken.
oldForm.job = newJob;
This is a much safer way to assign relationships. Of course, you have created NSManagedObject subclasses for these entities for this purpose.
If however, the relationship between Job and Form is one-to-many in the other direction, your scheme would look like this.
Form <---->> Job <------>> JobDetail
I do not really now what a "form" would mean in this case - I will just rename it Project for clarity.
Project <---->> Job <------>> JobDetail
Now you can assign the a new job to a project and link the other relationships like this:
newJob.project = existingProject;
newJobDetail.job = newJob;
I am new to core data and am still trying to get my head around it. I have a problem that I have spent hours on trying to solve. I just can't seem to solve it and was hoping someone could help me.
I can't post images yet so here is a link to my data model:
http://yourpcsolution.net/model.png
Each relationship has an inverse. A Game can have many Periods and a Period can have many Players. A Player can be in more than one Period.
Here is my code for populating DB:
- (void) prepareDB {
Games *game = [NSEntityDescription insertNewObjectForEntityForName:#"Games" inManagedObjectContext:self.managedObjectContext];
game.date = [NSDate date];
game.name = #"Game 1";
Periods *period = [NSEntityDescription insertNewObjectForEntityForName:#"Periods" inManagedObjectContext:self.managedObjectContext];
period.date = [NSDate date];
period.name = #"1st Period";
NSMutableSet *gameSet = [[NSMutableSet alloc] initWithObjects:period, nil];
game.periods = gameSet;
Players *player = [NSEntityDescription insertNewObjectForEntityForName:#"Players" inManagedObjectContext:self.managedObjectContext];
player.name = #"Player1";
NSMutableSet *playerSet = [[NSMutableSet alloc] initWithObjects:player, nil];
period.players = playerSet;
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
I think that's the correct way to add data because when looking at the sql db the data is there. My problem is in trying to retrieve the players that belong to each period. Because Periods is a set in Games and Players is a set in Periods in core data. So I can't retrieve players by going: game.periods.players
Here is my code for retrieving a Period and in the xcode log i am getting a fault:
"Relationship 'players' fault on managed object (0x8e72bc0) <Periods: 0x8e72bc0>
(entity: Periods; id: 0x8e727c0 <x-coredata://7F63902B-FCB6-4ACA-BB40-904755D37A4A/Periods/p1>;
data: {\n date = \"2013-07-09 19:35:48 +0000\";\n
games = \"0x8e6d760 <x-coredata://7F63902B-FCB6-4ACA-BB40-904755D37A4A/Games/p1>\";\n
name = \"1st Period\";\n players = \"<relationship fault: 0x9840330 'players'>\";\n})"
My Code for retrieving a Period:
NSString *firstPeriod = #"1st Period";
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", firstPeriod];
[request setEntity:[NSEntityDescription entityForName:#"Periods" inManagedObjectContext:self.managedObjectContext]];
[request setPredicate:predicate];
NSArray *period = [self.managedObjectContext executeFetchRequest:request error:&error];
I'm not sure how to proceed from here to retrieve the players. I know how to retrieve what Periods belong to each game but I don't know how to retrieve the players that belong to a period. I have been trying for days to figure this out and it's just not working.
Is my data model correct? How should I be retrieving the data? Any help or advice is appreciated.
To fetch all players of a given period, the following should work:
NSString *firstPeriod = #"1st Period";
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Players" inManagedObjectContext:self.managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY periodsin.name == %#", firstPeriod];
[request setPredicate:predicate];
NSArray *players = [self.managedObjectContext executeFetchRequest:request error:&error];
"ANY" in the predicate is necessary because each player is connected to multiple Periods.
Remark: The data type of a to-many relationship is NSSet, not NSMutableSet.
ADDED: To fetch all periods for a given player, you can use the following fetch request:
Players *givenPlayer = ... ; // the given player
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Periods" inManagedObjectContext:self.managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY players == %#", givenPlayer];
[request setPredicate:predicate];
NSArray *periods = [self.managedObjectContext executeFetchRequest:request error:&error];
Alternatively, you can access the relationship directly:
Players *givenPlayer = ... ; // the given player
NSArray *periods = [givenPlayer.periodsin allObjects]; // "allObjects" converts NSSet to NSArray
I have the following data model in my app:
Basically I want to store country names aswell as city names. Each city belongs to one country and one country contains 0 - n cities.
Before I now add new cities to a certain country I need to know if the country already contains a city with this name.
Until now I do this:
- (BOOL)countryForName:(NSString *)countryName containsCity:(NSString *)cityName {
Countries *country = [self countryForName:countryName];
NSSet *cityNames = [country valueForKey:#"cities"];
for (Cities *city in cityNames) {
if ([city.cityName isEqualToString:cityName]) {
return YES;
}
}
return NO;
}
This obviously is very slow, what I need is an equivalent fetch with a correct predicate. I perform a search for one entity like this:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Countries" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"countryName" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
fetchRequest.sortDescriptors = sortDescriptors;
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"GetCountryList"];
aFetchedResultsController.delegate = self;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"countryName == %#", countryName];
[aFetchedResultsController.fetchRequest setPredicate:predicate];
But how do I do this if a search involves multiple entities? Or in other words: How do I add unique city names for one country only?
If you want to check that a given country object already contains a city with a name,
you can do
Countries *country = ...
NSString *cityName = ...
if ([[country valueForKeyPath:#"cities.name"] containsObject:cityName]) {
// ...
}
Or, using a fetch request:
NSString *countryName = ...
NSString *cityName = ...
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Countries"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"countryName == %# AND ANY cities.cityName == %#", countryName, cityName];
[fetchRequest setPredicate:predicate];
NSError *error;
NSUInteger count = [self.managedObjectContext countForFetchRequest:fetchRequest error:&error];
if (count == NSNotFound) {
// error
} else if (count == 0) {
// no matching country
} else {
// at least one matching country
}
Note that a NSFetchedResultsController is normally used to display the contents of a
fetch request in a table view, so it is not needed here.