I am unable to fetch the results of a 1-1 relationship Entity's attribute?
BACKGROUND INFO
I have three UITableViewControllers, The grandfather TVC has 10 cells, Day 1, Day 2..Day 10. The father TVC has 3 cells, Workout 1, Workout 2, Workout 3. And in the child TVC I set the attributes of each workout and its "workoutscore". So each day Cell has 3 workoutcells after segue.
Workout1, Workout2, Workout3 each have a workoutscore attribute. I would like to display the workout score on the workout cells.
So I have a Day Entity with a one-one relationship with Workout1 Entity with Workout2 Entity and Workout 3 Entity.
Day <---> Workout1 Day<----->Workout2 Day<---->Workout3
After back navigation from the child to parent I correctly pass the workoutscore and save the context in the parent. The labels on the WorkOut cells correctly display the workoutscore.
Of course when I navigate back to the grandfather TVC and back to parent the parent TVC deallocs and I would like to fetch the previous scores based on the Day of the Grandfather TVC.
SAVING APPEARS IN VIEWDIDAPPEAR (This is correct)
In the parent TVC (Note: I am only showing the code for one workout as it is the same for the other 2)
AppDelegate *ad = [[UIApplication sharedApplication] delegate];
Day *myDay = [NSEntityDescription insertNewObjectForEntityForName:#"Day"
inManagedObjectContext:ad.managedObjectContext];
Workout1 *myWorkout1 = [NSEntityDescription insertNewObjectForEntityForName:#"Workout1"
inManagedObjectContext:ad.managedObjectContext];
NSNumber *theNumber = myWorkout1.workoutscore; //get old score
theNumber = [self updateNumber:theNumber forIndex: 11];
myWorkout1.workoutscore = theNumber; //update for new score
[myDay setWorkout1:myWorkout1]; //set Workout Entity to day Entity
NSError *savingError = nil;
myDay.dayname = [NSString stringWithString:self.dayname];
[ad.managedObjectContext save:&savingError];
}
FETCHING APPEARS IN VIEWDIDLOAD (This is incorrect, something is not working here)
In the parent TVC (Note: I am only showing the code for one workout as it is the same for the other 2)
AppDelegate *ad = [[UIApplication sharedApplication] delegate];
Day *day = [NSEntityDescription insertNewObjectForEntityForName:#"Day"
inManagedObjectContext:ad.managedObjectContext];
Workout1 *myWorkout1 = [NSEntityDescription insertNewObjectForEntityForName:#"Workout1"
inManagedObjectContext:ad.managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Workout1" inManagedObjectContext:ad.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObjects:#"Day",nil]];
[fetchRequest setIncludesSubentities:YES];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"day.daynumber contains %#", self.dayLabel.text]; //self.dayLabel.text is Day# in string form.
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [ad.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Nothing was fetched");
}
NSLog(#"number of fetched objects is %lu", (unsigned long)[fetchedObjects count]);
myWorkout1 = [fetchedObjects lastObject]; //most recent update of score
if ([fetchedObjects count] > 0) {
stringWS1 = [[NSMutableString alloc] init];
stringWS1 = [NSMutableString stringWithFormat:#"%#",myWorkout1.workoutscore];
self.workoutScoreLabel1.text = [NSMutableString stringWithString:stringWS1]; //Put score on label
Before the If statement of [fetchedObjects count] > 0 all I get is (null) on the labels. Now i get nothing because the array == 0. yet I save correctly?.
You have so many issues here its really hard to point in the right direction.
CoreData does not have a built-in method for "create-or-update". when you call insertNewObjectForEntityForName:inManagedObjectContext: you are introducing a new object to the context. this mean that in your current state you are inserting an empty Day and WorkoutX in every viewDidLoad call of the "parent TVC". this also happens in your viewDidAppear, but there you set values for your inserted objects.
Your fetch request could be much simpler if you'd send the Day.objectID to the "parent TVC", your predicate will be: [NSPredicate predicateWithFormat:#"day = %#",dayObjectID]
Why are you splitting the Workout entity to 3 separate entities? You could have a single Workout entity and 3 to-one relationships in the Day entity (or a to-many relationship if you like to have a variable number of workouts).
Keep references to what you are updating (day, workout) and update directly these objects (as long as you work only on the main thread there is no need to get them by their ID)
read the CoreData documentation HERE, specifically about creating, deleting and fetching managed objects.
look at some examples (like a blank CoreData project in Xcode), and see how they work with objects.
Related
I have made one sample demo on core data relationships.I have one Table "User" which is connected another table "Account" In form of "One to Many" relation.
Code
-(IBAction)savePersonData:(id)sender
{
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
[newDevice setValue:self.personName.text forKey:#"name"];
[newDevice setValue:self.personAddress.text forKey:#"address"];
[newDevice setValue:self.personMobileNo.text forKey:#"mobile_no"];
NSManagedObject *newAccount = [NSEntityDescription insertNewObjectForEntityForName:#"Account" inManagedObjectContext:context];
[newAccount setValue:self.accountNo.text forKey:#"acc_no"];
[newAccount setValue:self.accountType.text forKey:#"acc_type"];
[newAccount setValue:self.balance.text forKey:#"balance"];
NSLog(#"Saved Successfully");
[self dismissViewControllerAnimated:YES completion:nil];
}
Image is
My Question is
I have find so many time but could not find proper answer.So I post this question second time.
My question is I have insert Manually Three person With their Account's Details.
Now ,I Want A balance which I have entered for specific person when I enter Mobile number.
Ex
1)Enter 1st Mobile Num. Should be display first Person's Balance.
2)Enter 2nd Mobile Num. Should be display second Person's Balance.
1)Enter 3rd Mobile Num. Should be display third Person's Balance.
Balance Check Code
-(IBAction)checkBalance:(id)sender
{ NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"mobile_no = %#",self.textField.text];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *result = [appDelegate.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if(!([result count] == 0))
{
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *newFetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Account"];
NSMutableArray *temp = [[managedObjectContext executeFetchRequest:newFetchRequest error:nil] mutableCopy];
for (NSManagedObject *object in temp)
{
NSString *intValue = [object valueForKey:#"balance"];
NSString *alertString = [NSString stringWithFormat:#"%#",intValue];
[self displayAlertView:#"Available Balance" withMessage:alertString];
}
}
else
{
[self displayAlertView:#"Error" withMessage:#"Please Enter Valid Mobile Number That you have entered in Bank"];
}
}
Images
Output
I want Balance With Specific Person Which I enter Person Mobile No on TextField.
Sorry Guys,I have asked second time ,But Could no able to solve this core data relationships.
Thank you.
First...
Fix the naming of your relationships. As others have pointed out in comments on your other question, you have named them back to front: in the Person entity, the to-many relationship to Account should be named "accounts" not "person". Likewise in the Account entity, the to-one relationship to Person should be named "person" not "accounts".
Next...
Although you have defined the "accounts" relationship as to-many, the savePersonData code in this question creates only one Account and one Person - but does not then set the relationship between them. (You can see this in your Output: each Account has nil for its "accounts" relationship).
The code in your previous question did set the relationship (by adding the newAccount to the relationship on the newPerson object). In your code above you could use (after fixing the relationship names):
NSMutableSet *setContainer = [newDevice mutableSetValueForKey:#"accounts"];
[setContainer addObject:newAccount];
but with one-many relationships it is easier to set the inverse relationship:
[newAccount setValue:newDevice forKey:#"person"];
Next...
Your checkBalance method correctly fetches any Person objects whose "mobile_no" attribute matches. But your subsequent code then fetches ALL Account objects - even if you had correctly set the relationship.
If you want only those Account objects that are related to a given Person, that is precisely what the "accounts" relationship represents. So you could just use that relationship to access the related Account objects:
if(!([result count] == 0)) {
NSManagedObject *requiredPerson = (NSManagedObject *)result[0];
NSSet *temp = [requiredPerson valueForKey:#"accounts"];
for (NSManagedObject *object in temp) {
NSString *intValue = [object valueForKey:#"balance"];
NSString *alertString = [NSString stringWithFormat:#"%#",intValue];
[self displayAlertView:#"Available Balance" withMessage:alertString];
}
}
Alternatively, fetch the relevant Account objects directly (without fetching the Person object first) by specifying a predicate that traverses the relationship:
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *newFetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Account"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person.mobile_no = %#",self.textField.text];
[newFetchRequest setPredicate:predicate];
NSMutableArray *temp = [[managedObjectContext executeFetchRequest:newFetchRequest error:nil] mutableCopy];
for (NSManagedObject *object in temp)
{
NSString *intValue = [object valueForKey:#"balance"];
NSString *alertString = [NSString stringWithFormat:#"%#",intValue];
[self displayAlertView:#"Available Balance" withMessage:alertString];
}
This is the Core Data model. Image
DiaCD <--->> HoraCD <<---> ActividadCD
In the entity "Activity" is a category called attribute to filter the activities. My question is: How could I make a query to give me back the days with activities where the category is "X"?
Try one:
NSEntityDescription *entityDescriptionDia = [NSEntityDescription entityForName:#"DiaCD" inManagedObjectContext:managedObjectContext];
NSFetchRequest *requestDia = [[NSFetchRequest alloc] init];
NSPredicate *predicateDia = [NSPredicate predicateWithFormat:#"ANY relDiaHora.relHoraActividad.categoria == %#", categoria];
[requestDia setEntity:entityDescriptionDia];
[requestDia setPredicate:predicateDia];
NSError *errorDia;
NSArray *arrayDia = [managedObjectContext executeFetchRequest:requestDia error:&errorDia];
if ([arrayDia count] > 0) {
for (DiaCD *dia in arrayDia) {
NSSet *setHora = dia.relDiaHora;
HoraCD *horaQuery = [setHora anyObject];
ActividadCD *actividadQuery = horaQuery.relHoraActividad;
NSLog(#"Act --> %# y la categoria --> %# y la categoria --> %#", actividadQuery.titulo, actividadQuery.categoria, categoria);
}
}
If I do this query does not return good data that does not respect the category, I'm guessing, do not know why :S.
Try 2:
NSPredicate *predicateDia = [NSPredicate predicateWithFormat:#"relDiaHora.relHoraActividad.categoria == %#", categoria];
If I do the same query but only removing the "ANY" fails. Error: "reason: 'to-many key not allowed here'"
Your predicate
[NSPredicate predicateWithFormat:#"ANY relDiaHora.relHoraActividad.categoria == %#", categoria];
returns all days that have any hour with the given activity category. Your problem seems
to be that
NSSet *setHora = dia.relDiaHora;
returns all hours of the fetched day, not only the hours with the given activity category.
Therefore
HoraCD *horaQuery = [setHora anyObject];
is any hour of the fetched day, and need not have the given activity.
If you need the days and matching hours, you should execute a fetch request on the hours
instead:
NSEntityDescription *entityDescriptionHour = [NSEntityDescription entityForName:#"HourCD" inManagedObjectContext:managedObjectContext];
NSFetchRequest *requestHour = [[NSFetchRequest alloc] init];
NSPredicate *predicateHour = [NSPredicate predicateWithFormat:#"relHoraActividad.categoria == %#", categoria];
[requestHour setEntity:entityDescriptionHour];
[requestHour setPredicate:predicateHour];
NSError *errorHour;
NSArray *arrayHours = [managedObjectContext executeFetchRequest:requestHour error:&errorHour];
if ([arrayHours count] > 0) {
for (HourCD *hour in arrayHours) {
DiaCD *dia = hour.relHoraDia;
ActividadCD *actividad = hour.relHoraActividad;
// ...
}
}
OK, the first bit was wrong. However, still follow this note about naming.
A QUICK NOTE
You should not be calling your attributes "relItem1Item2". This is something that comes from relational databases. CoreData is not a relational database.
You should name them descriptively as to what they point to.
i.e.
relHoraActividad should be called actividad. As it is pointing to the actividad entity and it is a "to-one" relationship.
Also...
relActividadHora should be called horaCDs. It is pointing to the horaCD entity and it is a "to-many" relationship.
I think your setup is absurd. Why have complicated relationships to days and hours when you can simply use NSDate attributes?
From your image I see that you store other things in these entities that do not seem to have anything to do with hours or days.
To filter by day or time, you typically use a pattern like this
[NSPredicate predicateWithFormat:#"time > %# && time < %#", min, max];
I'm just getting started with Core Data and am not sure how this works. I basically have a Person entity and an alarm entity. Each person can have many alarms. What I want is to go to a detailViewController of the person object and see their alarms. Because NSSet isn't sorted, I have a method to return the alarms sorted like so:
- (NSArray *)sortedTimes {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Alarm" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSSortDescriptor *timeDescriptor = [[NSSortDescriptor alloc] initWithKey:#"time" ascending:YES selector:#selector(compare:)];
[request setSortDescriptors:#[timeDescriptor]];
NSError *error = nil;
NSArray *objects = [self.managedObjectContext executeFetchRequest:request error:&error];
// Can I do this???
//self.person.alarms = [NSSet setWithArray:objects];
// for (NSManagedObject *obj in objects) {
// NSDate *date = [obj valueForKey:#"time"];
// NSLog(#"date: %#", [date description]);
// }
return objects;
}
What I'm wondering is, in the line self.person.alarms = [NSSet setWithArray:objects]; is that ok? I guess I'm not sure as to what actually is happening. My executeFetchRequest returns an array of the objects I want. Can I just go ahead and assign it to the person entity's alarm property? I wasn't sure if there was a relationship from Person->Alarm that I should not be mucking with, or if something like this is perfectly legal. Thanks!
First of all, your fetch request returns all alarms, not only the alarms of self.person. You have to add an predicate to the fetch request:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"person = %#", self.person];
[request setPredicate:predicate];
(assuming that person is the inverse relationship from the Alarm entity to the Person entity). But you don't really need a fetch request to get the sorted alarms of a person. A more direct way is
NSArray *objects = [[self.person.alarms allObjects]
sortedArrayUsingDescriptors:#[timeDescriptor]];
Now to your question: The statement
self.person.alarms = [NSSet setWithArray:objects];
just re-assigns the same set of alarms to the person. This effectively does not change anything, because it is the same set. In particular, it does not guarantee that self.person.alarms will now be sorted by time.
Remark: It you want to display a table view with the alarms of a person, you can also use a NSFetchedResultsController (FRC) as table view data source. The advantage of using a FRC is that the table view is automatically updated if objects are inserted, removed or updated.
Have a look at the NSFetchedResultsController and NSFetchedResultsControllerDelegate documentation which contains all the required code templates.
my program has a sqlite database with two related tables. One called "Rank" and other one called "Requirement"
I want to fetch all rows from the "Requirement" table that has a relationship with the specific row in a "Rank" table. Following is my code, it grabs the whole table, but I get the specified rows only according to the above mentioned rule.
-(NSArray *) getAllRequirementsForTheRank:(Rank *) rank
{
NSError *error;
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init]autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Requirement" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSPredicate *searchType = [NSPredicate predicateWithFormat:#"Rank = %#", rank];
[fetchRequest setPredicate:searchType];
NSArray *scoutRequirementArray = [self.context executeFetchRequest:fetchRequest error:&error];
for (Requirement *r in scoutRequirementArray)
{
NSLog(#"Requirementttt : %# :", r.requirementName);
}
return scoutRequirementArray;
}
If you have the relationship modelled in core data, just get the linked objects from the relationship property. You don't need another fetch request. rank.requirements will give you an NSSet of everything you need. (I'm assuming names for your object and properties here).
I am developing an application where i used core data framework for the purpose of maintaining a database. My entity contains three attributes called: name, start time and end time of a list of applications. I am getting the correct values for name and start time attribute.
Now my problem is my end time attribute should contain the value of the next entries start time value. If anybody having any idea about this please let me know.
Thanks
You can leave the endTime attribute blank until you create the next entity. In the +Create category on the entity, get the last/first object (assuming you are using ordered entities) and update the endTime with the same value used for the new startTime.
If your objects are not ordered it could be a bit tricky since all the entities are in a set. But if ordered, you are good since NSOrderedSet responds to lastObject (and firstObject).
Enjoy,
Damien
EDIT: Here is an example factory method that either 1) returns the existing stock entity for a stock symbol or 2) creates a new entity for that symbol. Pretty easily modified to get entities and select the first/last depending on your sort order. Again see the Core Data classes from Prof. Hegarty.
+ (Stock *)stockForSymbol:(NSString *)symbol inManagedObjectContext:(NSManagedObjectContext *)context {
Stock *stock = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Stock"];
request.predicate = [NSPredicate predicateWithFormat:#"symbol = %#",symbol];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"symbol" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || [matches count] > 1) {
// handle error
} else if ([matches count] == 0) {
stock = [NSEntityDescription insertNewObjectForEntityForName:#"Stock" inManagedObjectContext:context];
stock.symbol = symbol;
stock.strategyPosition = [NSNumber numberWithInt:StrategyPositionFlat];
stock.userPosition = stock.strategyPosition;
stock.userOwns = [NSNumber numberWithBool:NO];
} else {
stock = [matches lastObject];
}
return stock;
}