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"];
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 have a method that returns an NSMutableArray of entities from my CoreData database. The entities look like this
#property (nonatomic, retain) NSNumber * iD;
#property (nonatomic, retain) NSString * number;
#property (nonatomic, retain) ManufacturerNumber *manufacturerNumber;
I need to create a unique array of manufacturerNumber entities based of the number NSString.
this is how my method that returns the current array with duplicates looks.
- (NSMutableArray *)readNumber
{
NSManagedObjectContext *context = [self managedObjectContext];
// Test listing all FailedBankInfos from the store
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ManufacturerNumber" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *manufacturerNumbersDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (ManufacturerNumber *manufacturerNumber in fetchedObjects) {
[manufacturerNumbersDictionaryArray addObject:manufacturerNumber];
}
return manufacturerNumbersDictionaryArray;
}
This returns an NSMutableArray including duplicates, and I'd like to remove the duplicates.
Update to question
I have now decided to edit the array when I go to display the values in my UITableview, below I explain what the array contains etc.
I have a NSMutableArray that contains the coredata entities described above, I would like to know how to create a NSSet of unique values from the NSMutablerArray based from the entities cNumber attribute.
This is how I have created the NSMutableArray
tableViewMArray = [NSMutableArray arrayWithArray:[cardManufacturerNumber.cNumbers allObjects]];
As you can see cardManufacturerNumber is a coredata object with a one to many relationship with cNumbers.
cNumbers has 3 attributes
numString
numID
parentObj
I would like to know how to create a unique NSMutableArray based off cNumbers numString attribute.
The NSMutableArray should consisit of unique cNumbers coredata objects. not just numString strings as I need to know the other values.
Have you tried using NSMutableSet instead of NSMutableArray? If you properly implement - (NSUInteger)hash and - (BOOL)isEqual:(id)anObject in a way that makes logical sense on your ManufacturerNumber class this should give you the result you want (this is necessary since sets use these methods to ensure the uniqueness of each object. How you implement these depends on the internals of your ManufacturerNumber class). Or, if you care about order but want to maintain uniqueness, you could use NSMutableOrderedSet.
Unless I am missing the point, this should help for the first part of your question...
Regarding your method (NSMutableArray *)readNumber, try this instead...
- (NSSet *)readNumber
{
NSSet *setReturned = nil;
NSManagedObjectContext *context = [self managedObjectContext];
// Test listing all FailedBankInfos from the store
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ManufacturerNumber" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *manufacturerNumbersDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (ManufacturerNumber *manufacturerNumber in fetchedObjects) {
[manufacturerNumbersDictionaryArray addObject:manufacturerNumber];
}
setReturned = [NSSet setWithArray: manufacturerNumbersDictionaryArray];
return setReturned;
}
This should return a discrete set of manufacturerNumber without duplicates.
For the second and updated part of your question, I'm struggling to understand exactly what you need, so forgive me if I am off the mark.
Have you attempted an NSFetchRequest with an NSPredicate, instead of the setting the variable tableViewMArray?
For example...
NSString *key = #"cNumbers";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"cNumber"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K != %d", key, cardManufacturerNumber.cNumbers];
[fetchRequest setPredicate:predicate];
NSArray *array = [<<insert local managedObjectContext>> executeFetchRequest:fetchRequest error:nil];
NSSet *set = [NSSet setWithArray:array];
and then to make the array mutable...
(with thanks to this SO Q&A)
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:[set allObjects]];
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.
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.