I have an issue with Core Data. I have an entity called "Group", and other entity called "Contact". These entities have a relationship "many to many" between them, because a group could have many contacts (members of the group), and a contact could have many groups (be a member of many groups).
So, this is the relationship:
Group <<----->> Contact
What I need is insert contacts (new or existing) as members of existing groups.
What is my issue? Well...I am able to insert them if I create a new contact, but if the contact already exists, Core Data does not overwrite and save it.
Here is my code:
- (void)saveMemberInGroup:(Message *)message
{
GroupInfo *groupInfo = message.group;
UserInfo *member = message.user;
AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = [app managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:CONTACT_ENTITY
inManagedObjectContext:context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"ident == %#", [member ident]]];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
//Here I check if the contact exists.
Contact *contact;
if ([fetchedObjects count] == 0) {
contact = [NSEntityDescription insertNewObjectForEntityForName:CONTACT_ENTITY
inManagedObjectContext:context];
[contact setIdent:[NSString stringWithFormat:#"%#", [member ident]]];
} else {
contact = [fetchedObjects objectAtIndex:0];
}
//Now I make a new request for the group.
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription entityForName:GROUP_ENTITY
inManagedObjectContext:context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"ident == %#", [groupInfo ident]]];
[fetchRequest setEntity:entity];
fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
Group *group = [fetchedObjects objectAtIndex:0];
//Now, I overwrite the group and the contact.
[[group members]addObject:contact];
[[contact memberGroups]addObject:group];
[context save:&error];
}
Here is my Group entity:
#interface Group : NSManagedObject
#property (nonatomic, retain) NSString *ident;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSMutableSet *members;
#end
And my Contact entity:
#interface Contact : NSManagedObject
#property (nonatomic, retain) NSString *ident;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSMutableSet *memberGroups;
#end
NOTE:
This is really weird...when I check if I have updated the objects successfully, everything seems to be ok, both groups and contacts have their NSSet updated rightly, but when I restart the app (I mean, finalize and start it again, or press "play" in XCode), Core Data only has saved the relationship with the new contacts created!!.
Example:
Create Contact1 and save in Group1.
Create Contact2 and save in Group1.
Existing Contact1 and save in Group2.
Create Contact3 and save in Group2.
If I check Core Data when my app is running, I will have in Group1 (Contact1, Contact2), and in Group2 (Contact1, Contact3).
Then I restart the app and check again Core Data, and now I have in Group1 (Contact1, Contact2), and in Group2 (Contact3)!! Core Data has lost the relationship between Contact1 and Group2, and this happens every time I have a relationship between a group and an existing contact.
What I am doing wrong??
Thanks a lot!.
You should create the class files for your entities with "Editor -> Create NSManagedObject
Subclasses ..." from the Xcode menu. A to-many relationship is represented by an NSSet,
not a NSMutableSet, so you cannot modify it directly by adding an object.
Once you have done that, you can add an additional object to group.members with
[group addMembersObject:contact];
or alternatively
[contact addMemberGroupsObject:group];
Core Data updates inverse relationships automatically, so it does not matter
which one you choose.
Related
I need to create a predicate to get object from core data,
My entity "Form" looks like that:
#interface EEForm (CoreDataProperties)
#property (nullable, nonatomic, retain) NSDate *date;
#property (nullable, nonatomic, retain) NSString *descriptor;
#property (nullable, nonatomic, retain) NSString *location;
#property (nullable, nonatomic, retain) NSMutableSet<Participants *> *participants;
entity "Participants" look like that:
#interface Participants (CoreDataProperties)
#property (nonatomic, assign) NSInteger indexRow;
#property (nullable, nonatomic, retain) NSString *participant;
#property (nullable, nonatomic, retain) EEForm *form;
I want to get objects on the base of participants field, that contains all objects from a given array (this array consist of string objects and it changes dependently of what the user selects).
Fetch request is performed by FetchResultController. I set the predicate in initialisation of it.
I did it in that way, but it includes all objects that contains at least one object from the given array.
- (NSFetchedResultsController *)fetchResultController {
if(_fetchResultController != nil) {
return _fetchResultController;
}
NSPredicate *lPredicate = [NSPredicate predicateWithFormat:#"ANY participants.participant IN %#", _selectedForPredicatel];
NSFetchRequest *lRequest = [[NSFetchRequest alloc]init];
[lRequest setPredicate:lPredicate];
NSEntityDescription *lEntity = [NSEntityDescription entityForName:#"Form" inManagedObjectContext:self.managedObjectContext];
[lRequest setEntity:lEntity];
[lRequest setFetchBatchSize:10];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"date" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[lRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *fetchResultController = [[NSFetchedResultsController alloc]initWithFetchRequest:lRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
fetchResultController.delegate = self;
self.fetchResultController = fetchResultController;
NSError *error = nil;
if(![[self fetchResultController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchResultController;
}
Could you please advice how to set predicate properly to filter objects that contain all elements from the given array.
EDIT:
array that is used for predicate looks like that:
_selectedForPredicatel = [[NSMutableArray alloc]initWithObjects:#"Child",#"Adult", #"Group of people", #"Family", #"Teenager", nil];
every time user selects parameters by which filter 'Form' objects, this array is updating, so I need fetch request to be done according to it. So if 'Form' has included participants: #"Child",#"Adult", but in an array _selectedForPredicatel there are objects: #"Adult", #"Group of people", in this case this 'Form' shouldn't be fetched. Should be fetched only that 'Form'that includes both elements of _selectedForPredicatel array.
Thanks a lot for any advice.
You want to include the forms if, for each of the user's selections, there is at least one Participant that has a participant attribute that matches.
If the participant names were unique (at least for each Form), you could count the matches and compare to the count of the user's selections. But if not, you need to create a predicate test each of the user's selections separately, and then combine the results into one big predicate:
NSPredicate *template = [NSPredicate predicateWithFormat:#"(SUBQUERY(participants, $p, $p.participant == $SELECTED).#count > 0)"];
NSMutableArray *predicateArray = [NSMutableArray array];
for (NSString *testString in _selectedForPredicatel) {
NSDictionary *subsVars = #{#"SELECTED" : testString};
NSPredicate *testPredicate = [template predicateWithSubstitutionVariables:subsVars];
[predicateArray addObject:testPredicate];
}
NSPredicate *finalPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicateArray];
NSLog(#"finalPredicate is %#", finalPredicate);
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"ListEntity"];
fetch.predicate = finalPredicate;
Here template is a dummy predicate using $SELECTED as a placeholder for the user's selection. The for loop uses this to create a predicate for each of the user's selections and adds it to a mutable array. The final predicate is built by compounding the array of predicates using AND clauses.
It's pretty messy (the final predicate is bad enough, the underlying SQL is horrible) so performance might be dismal.
Instead of ANY you can use ALL :
NSPredicate *lPredicate = [NSPredicate predicateWithFormat:#"ALL participants.participant IN %#", _sortByParticipants];
This will check if all objects are in your collection.
I'm new to Core Data. I have two entities: USER and IOU.
// User.h
#property (nullable, nonatomic, retain) NSString *name;
#property (nullable, nonatomic, retain) NSSet<NSManagedObject *> *ious;
// Iou.h
#property (nullable, nonatomic, retain) NSDecimalNumber *amount;
#property (nullable, nonatomic, retain) NSDate *date;
#property (nullable, nonatomic, retain) NSString *iouDescription;
#property (nullable, nonatomic, retain) User *user;
In a view controller, I use a predicate to fetch the ious belonging to a user and then I use that to fetch the user for its name. I then display that user's name along with her list of ious.
// PersonViewController.m
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Iou"];
// Predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", #"user.name", self.userName];
[fetchRequest setPredicate:predicate];
// Execute fetch request
NSError *error = nil;
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:&error];
Iou *iou = self.fetchedResultsController.fetchedObjects.lastObject;
self.user = iou.user;
Next, my customers can tap on an IOU and edit both the IOU and the user.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Iou *iou = [self.fetchedResultsController objectAtIndexPath:indexPath];
personEditViewController.user = self.user;
personEditViewController.iou = iou;
personEditViewController.managedContext = self.managedContext;
[[self navigationController] pushViewController:personEditViewController animated:YES];
}
Here is how I am editing both the USER entity and the IOU entity.
// PersonEditViewController.m
self.user.name = self.nameField.text;
// Description
self.iou.iouDescription = self.descriptionField.text;
// Date
UIDatePicker *datePicker = (UIDatePicker *)self.dateField.inputView;
self.iou.date = datePicker.date;
// Save
NSError *error;
[self.iou.managedObjectContext save:&error];
I use a NSFetchedResultsController to work with a UITableView. If I ONLY change IOU attributes, I get the right behavior. I get NSFetchedResultsChangeMove; however, if I also update the USER, I get NSFetchedResultsChangeDelete. If I restart, the data looks fine so it's only happening in the view.
I searched through some SO questions but I still don't get it. What's a simple pattern to use so it will always work every time.
I considered not using NSFetchedResultsController and relying on good old NSArray with a UITableView. In that case, every time I CRUD a record, it will refetch the records from DB and I'll use the fetchedResultsController.fetchedObjects.
If you amend the name property of the User object, it will no longer match the predicate - so the FRC will trigger the delete to remove the corresponding row in the table view. (Note that you might expect all the other rows to be deleted, since they also no longer satisfy the predicate, but the FRC only re-evaluates those IOUs which have been updated).
If you want to avoid this, then amend your fetch results controller code to first fetch the User object(s) that match the predicate, then amend the FRC predicate to test whether the IOU belongs to any of those users:
// PersonViewController.m
NSError *error = nil;
NSFetchRequest *userFetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"User"];
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:#"%K == %#", #"name", self.userName];
userFetchRequest.predicate = userPredicate;
NSArray *matchingUsers = [self.managedContext executeFetchRequest:userFetchRequest error:&error];
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Iou"];
// Predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"user", matchingUsers];
[fetchRequest setPredicate:predicate];
// Execute fetch request
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:&error];
Iou *iou = self.fetchedResultsController.fetchedObjects.lastObject;
self.user = iou.user;
Since the FRC's predicate is based on the User object itself, not any of its attributes, you can amend those attributes without causing its IOUs to fail to satisfy the FRC's predicate.
I'm new to Core Data, so go easy on me. :) I'm currently using iOS 7 in Xcode 5.
I have two Entities: Aircraft and Entry. There is a one aircraft to many entry relationship.
I'm trying to save a new Entry which has an aircraft object in it. Here is my Entry.h file (minus the #import):
// Entry.h
#class Aircraft;
#interface Entry : NSManagedObject
#property (nonatomic, retain) NSNumber *duration;
#property (nonatomic, retain) NSDate *flightDate;
#property (nonatomic, retain) Aircraft *aircraft;
#end
Meanwhile, over in my view controller where I save the new Entry, I have the following code. I use a dictionary to set up my data prior to creating a new record:
//Add New Entry
Aircraft *aircraft = [Aircraft new];
aircraft.aircraftRegistration = _aircraftRegistrationTextField.text;
NSDictionary *newEntry = #{
#"aircraft": aircraft, //???
#"flightDate": [formatter dateFromString:_flightDateTextField.text],
#"duration": [PPHelpers convertHHMMtoDecimal:_durationTextField.text],
};
//Save new Entry to Core Data
PPAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *item = [NSEntityDescription insertNewObjectForEntityForName:#"Entry" inManagedObjectContext:context];
[item setValuesForKeysWithDictionary:newEntry];
NSError *error;
[context save:&error];
I get a couple errors from this:
CoreData: error: Failed to call designated initializer on NSManagedObject class 'Aircraft'
and...
-[Aircraft setAircraftRegistration:]: unrecognized selector sent to instance 0x110b0b550
I suspect the issue is that I'm not adding the Aircraft to the Entry in the right way.
Any ideas what I'm doing wrong?
Aircraft *aircraft = [Aircraft new];
creates an object without calling the designated initializer. You should replace it by
Aircraft *aircraft = [NSEntityDescription insertNewObjectForEntityForName:#"Aircraft" inManagedObjectContext:context];
Note: It is possible to set Core Data attributes with setValuesForKeysWithDictionary,
but a more direct, type-safe and less error-prone way would be to use the Core Data
accessors:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry" inManagedObjectContext:context];
entry.aircraft = aircraft; // from above
entry.duration = ...;
entry.flightData = ...;
if Aircraft is an parent relation entity, you need to use instanciate it by insertNewObjectForEntityForName it's the same that Entry.
//Save new Entry to Core Data
PPAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
//Add New aircraft
Aircraft* aircraft = (Aircraft*) [NSEntityDescription insertNewObjectForEntityForName:[NSClassFromString(Aircraf) inManagedObjectContext:context];
aircraft.aircraftRegistration = _aircraftRegistrationTextField.text;
Entry* item = (Entry*) [NSEntityDescription insertNewObjectForEntityForName:NSClassFromString(Entry) inManagedObjectContext:context];
[item setAircraft:aircraft];
[item setFlightDate:[formatter dateFromString:_flightDateTextField.text]];
[item setDuration:[PPHelpers convertHHMMtoDecimal:_durationTextField.text]];
NSError *error;
[context save:&error];
I am struggling with CoreData. I'd like to fetch the following object:
#interface Email : NSManagedObject {
}
#property (nonatomic, copy) NSString *email;
#property (nonatomic, copy) NSString *contact;
#end
..and put the result inside a NSMutableArray, but the NSString contents, (not NSManagedObjects!). This is because I am using json-framework and that engine does not allow NSManagedObjects to be passed.
These lines fetch perfom the fetch from CoreData
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Emails" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"email"]];
NSError *error = nil;
NSMutableArray *fetchedObjects = [[NSMutableArray alloc] init];
fetchedObjects= [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
flight.emails=fetchedObjects;
The 'flight' object is declared as follows:
#interface Flight : NSObject {
NSMutableArray *_emails;
}
#property (nonatomic, retain) NSMutableArray *emails;
With this code I am getting CoreData faults. I tried some other implementation variants, but never actually managed to have NSString objects in flight.emails, but always NSManagedObjects. What I tried was to implement a dedicated getter function in the Email NSManagedObject that copies the fetched NSString and returns the copy.
I get the idea that this is kind of a common problem, however, my research has not led to a solution on this one here.
Thanks,
Peter
From experience, setPropertiesToFetch: only works when requesting the returned objects be dictionaries. So a couple of problems in your code:
You are asking for specific property (email), but have not set the return type to dictionary. Take a look at setResultType:.
You still need to take the result and extract the email objects from it. You cannot just assign the resulting array to your emails property.
Try this:
[fetchedRequest setResultType:NSDictionaryResultType];
NSArray* results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if(error != nil || results == nil) return;
flight.emails = [results valueForKeyPath:#"email"];
I have an entity in my core data model like this:
#interface Selection : NSManagedObject
#property (nonatomic, retain) NSString * book_id;
#property (nonatomic, retain) NSString * contenu;
#property (nonatomic, retain) NSNumber * page_id;
#property (nonatomic, retain) NSNumber * nbrOfOccurences;
#property (nonatomic, retain) NSString * next;
#property (nonatomic, retain) NSString * previous;
I have created many Selections and saved them in Core Data and now I would like to delete some selections with some criteria. For example, I would like to delete a Selection object if matches the following:
content = test
page_id = 5
book_id = 1331313
How I can do this?
What Mike Weller wrote is right. I'll expand the answer a little bit.
First you need to create a NSFetchRequest like the following:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Selection" inManagedObjectContext:context]];
Then you have to set the predicate for that request like the following:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"content == %# AND page_id == %# AND book_id == %#", contentVal, pageVal, bookVal]];
where
NSString* contentVal = #"test";
NSNumber* pageVal = [NSNumber numberWithInt:5];
NSString* bookVal = #"1331313";
I'm using %# since I'm supposing you are using objects and not scalar values.
Now you perform a fetch in the context with the previous request:
NSError* error = nil;
NSArray* results = [context executeFetchRequest:fetchRequest error:&error];
results contains all the managed objects that match that predicate.
Finally you could grab the objects and call a deletion on them.
[context deleteObject:currentObj];
Once done you need to save the context as per the documentation.
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
Hence
NSError* error = nil;
[context save:&error];
Note that save method returns a bool value. So you can use an approach like the following or display an alert to the user. Source NSManagedObjectContext save error.
NSError *error = nil;
if ([context save:&error] == NO) {
NSAssert(NO, #"Save should not fail\n%#", [error localizedDescription]);
abort();
}
You should perform a fetch request using an NSPredicate with the appropriate conditions, and then call the deleteObject: method on NSManagedObjectContext with each object in the result set.
In addition to Mike Weller and flexaddicted, after calling [context deleteObject:currentObj]; you need to save: context:
NSError *error = nil;
[context save:&error];
As from documentation:
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
That made matter in my case.