objective c - set relationship (core data) - ios

I'm very new to core data and the project that I'm currently working on involves using core data for local data storage.
Below is the structure of my entities.
My app functions as follows. When the user first launches the app, there are presented with a login screen. New users can register. When a user tries to register, they have to enter a unique key that has already been provided to them, prior to the registration. This unique key is validated in the Facility table. If the key exists, in the db, then I will enter the user data into the database and will create a relationship between the Assessor and the Facilitythat they just entered the code for.
My question is, how would I create a relationship between a new Assessor and an existing Facility?
I'm thinking, I should fetch the required Facility object from core data and then use [assessorsetValue:facilityObjectforKey:#"facility"] to set the object.
Can anyone help me understand how to fetch the facilityObject from the Facility table in core data before assigning it to the Assessor?
Thanks in advance.
EDIT:
For example:
This is how my Facility table will look like.
code | name
H6DJ | Computing
So if the user (John Doe) enters H6DJ as the registration code, I check the Facility table to see if it exists. so in this case, I would like to create a relationship between the new user (John Doe) and the Computing facility.

The Core Data Programming Guide provides lots of sample code.
Your idea about setting the facility property of the new assessor entity is correct - you just need to fetch the facility first - presumably you would need to do this anyway to validate the facility code that was entered.
NSManagedObject *newAssessor = [NSEntityDescription
insertNewObjectForEntityForName:#"Assessor"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"Facility"
inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"code == %#", targetCode];
[request setPredicate:predicate];
NSError *error;
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
if (array != nil) {
NSUInteger count = array.count;
if (count == 1) {
NSManagedObject *facility=array[0];
if (facility["assessor"]==nil) {
newAssessor["facility"]=facility;
// Set up rest of assessor attributes before saving
} else {
// Error - facility already assigned to assessor ?
}
} else if (count == 0) {
// Code not found/valid - do something
} else {
// More than 1 matching facility - data error
}
}
else {
// Deal with error.
}

Related

Filter CoreData (Parent entity and child entity)

Two days I have been trying to filter the CoreData and I'm still stuck.
This is my model:
Users < --- >> Sessions
I managed to create the relationship between a new session and a specific user. I also managed to get the list of all the users to display them in a tableview by this code :
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *usersDescription= [NSEntityDescription entityForName:#"Users" inManagedObjectContext:_managedObjectContext];
[request setEntity:usersDescription];
NSError *error = nil;
NSMutableArray *mutableFetchresults = [[_managedObjectContext executeFetchRequest:request error:&error]mutableCopy];
if(mutableFetchresults == nil) {
//handle error
}
usernameDataMutableArray = [[NSMutableArray alloc]init];
for (int i = 0; i< [mutableFetchresults count]; i++)
{ Users* users = (Users *) [mutableFetchresults objectAtIndex:i];
[usernameDataMutableArray insertObject: [users username] atIndex:i];
}
Now when I touch a cell called "Username1", I display a new tableView. I would like display in this tableView all the Username1 sessions.
So my question is: how can I filter all my sessions to retrieve these belong to Username1?
There is no filter required.
When you push the new view controller you hand off the instance of Users that was selected. Then in the new view controller you ask the instance of Users for the Sessions instances that are associated with it.
NSSet *sessionsSet = [myUser valueForKey:#"sessions"];
You can also use a property if you have subclasses set up.
This is the point of Core Data. It is an object graph and relationships are properties on the object instance. Just call the appropriate method.
I would strongly suggest reading a book on Core Data as it will clear up a lot of the issues you are having with these fundamental concepts.
Additional Comments
Entities should not be named in the plural. Your entities should be called User and Session instead of their plural form. The name of the relationship that points to a to-many should be plural and the name that points to the to-one should be singular. This helps greatly with code clarity and maintainability. Plural tends to indicate a collection of objects instead of a single object like you have now.

How to get object reference in Core Data?

There is an entities where I am filling data from JSON, Say it is Photographer and Photo. Both have some data which I have filled using loop and managed ObjectContex ..
Like this,
NSMutableArray *ArrPhotographer= [[self.M3Arr objectForKey:#"LifeMag"]objectForKey:#"Photographer"];
for (int i = 0; i< ArrPhotographer.count; i++) {
Photographer * photographerObj = [NSEntityDescription insertNewObjectForEntityForName:#"PhotographerData"
inManagedObjectContext:[self managedObjectContext]];
NSMutableDictionary *tpDict = [cleanerListArr objectAtIndex:i];
photographerObj.cleanerName = [tpDict objectForKey:#"photographerName"];
}
Now I have done this for both Photographer and Photo Entity and as per this picture my Magazine entity is having data which are already existed in this table . And as shown I have made to one relation to both Photo and Photographer from Magazine .
Now Question is ,
If Photographer Name is already existed in the table , How can I connect it with Magazine Entity . I need the managed object Reference of that particular place .
(For Example , There are three photographer , Ron , Harry and Sunny now For Photo Cover1 I want name of Ron . then I need the Object Reference of Ron when I pre Populate it).
How to get this object Reference?
// **************** EDIT
I am getting the object is present ...but No how to fetch object and 2) how to give it to x and y stated above ?
I am using this code to saving the data in Magazine
Magazine *magObj = [NSEntityDescription insertNewObjectForEntityForName:#"Magazine"
inManagedObjectContext:[self managedObjectContext]];
magObj .issueID=[NSNumber numberWithInt:1];
magObj .photo= x;
magObj .photographer = y;
#### Edit 2
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Cleaner"];
[request setPredicate:[NSPredicate predicateWithFormat:#"cleanerName = %#", #"Robin"]];
[request setFetchLimit:1];
NSUInteger count = [[self managedObjectContext] countForFetchRequest:request error:nil];
if (count == NSNotFound){
NSLog(#"ERROR FOund");
}
// some error occurred
else if (count == 0){
NSLog(#"no matching object");
}
// no matching object
else{
NSLog(#"Found Match");
}
You need to use an NSFetchRequest to search the context for the appropriate object to connect to. The fetch request specifies the entity type to search for and you need to add an NSPredicate to filter the results to only the particular name that you're interested in.
Note that you could run a single fetch request with a list of names so that you only hit the data store for information once and then use the returned list during your object creation / connection loop.
If you're loading all of your data in one go, then you can create a dictionary which contains the managed object instances such that you can link to them without fetching.

Any way to optimize simple NSFetchRequest for single object?

I'm dong data processing in a child moc in a background queue. I need to query the database by ID so that I can differentiate updating-existing-object from creating-new-object. I found most of the time(the total processing time is about 2s for 50 items) is consumed by executeFetchRequest:error:. The NSPredicate is of the simplest form — only to match a single ID attribute(ID attribute is already indexed), and the NSFetchRequest should return one or none(ID is unique). Is there any way to optimize this kind of NSFetchRequest?
Here is my current code:
+ (User *)userWithID:(NSNumber *)ID inManagedObjectContext:(NSManagedObjectContext *)context {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ID == %#", ID];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:1];
NSError *error = nil;
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
abort();
}
if ([users count] == 1) {
return [users objectAtIndex:0];
} else if ([users count] > 1) {
// Sanity check.
…
} else {
return nil;
}
}
As #ChrisH pointed out in comments under the question, doing a fetch for every ID is no good. So I changed my processing flow to this:
Enumerate data the first time to extract IDs.
Do a single fetch to fetch all existing users matching IDs and put them in a dictionary keyed by ID(named as existingUsers).
Enumerate data the second time to do the real processing: in each iteration, either update one existing user found in existingUsers or create a new user, add it into existingUsers if it is new.
The code is almost doubled, but so is the performance. Really good tradeoff!
To expand on my comment to the original question, it's not efficient to repeatedly perform fetch requests with Core Data when importing data.
The simplest approach, as #an0 indicated, is to perform one fetch of all the existing objects you will be checking against, and then constructing an NSDictionary containing the objects with the attribute you will be checking as keys. So sticking with the original User and userID example:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"User"];
NSError *error = nil;
NSArray *users = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
//handle appropriately
}
NSMutableDictionary *userToIdMap = [NSMutableDictionary dictionary];
for (User *user in users){
[userToIdMap setObject:user forKey:user.ID];
}
Now in your method that processes new data you can check the userToIdMap dictionary instead of making fetch requests.
A more sophisticated approach, suited to larger data sets, is outlined in the Core Data Programming Guide's Efficently Importing Data. Take a look at the section called 'Implementing Find-Or-Create Efficiently'. The approach suggested by Apple here misses out some code regarding how to walk the arrays you create, and my solution to that problem is in this SO question: Basic array comparison algorithm

Core Data: import a tree structure with find or insert / duplicate entries

I have a list of Places from a rails app that I'm trying to import in an iOS5 app. Each Place has a parent which is a Place itself.
I'm trying to import that JSON data with Core Data using a dictionary
- (void)initWithDictionary:(NSDictionary *)dictionary {
self.placeId = [dictionary valueForKey:#"id"];
id parent = [dictionary objectForKey:#"parent"];
if (parent && parent != [NSNull null]) {
NSDictionary *parentDictionary = parent;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"placeId = %#", [parentDictionary objectForKey:#"id"]];
NSArray *matching = fetchedWithPredicate(#"Place", self.managedObjectContext, predicate, nil);
if ([matching count] > 0) {
self.parent = [matching objectAtIndex:0];
} else {
self.parent = [NSEntityDescription insertNewObjectForEntityForName:#"Place" inManagedObjectContext:self.managedObjectContext];
[self.parent initWithDictionary:parentDictionary];
}
}
}
fetchedWithPredicate is a method defined as such
NSArray* fetchedWithPredicate(NSString *entityName, NSManagedObjectContext *context, NSPredicate *predicate, NSError **error) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesPendingChanges:YES];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setPredicate:predicate];
NSArray *result = [context executeFetchRequest:request error:error];
return result;
}
I also have a validation method in Place.m to make sure I don't create to place with the same placeId (placeId is the id on the server side).
- (BOOL)validatePlaceId:(id *)value error:(NSError **)error {
if (*value == nil)
return YES;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"placeId = %# AND (NOT self IN %#)", *value, [NSArray arrayWithObject:self]];
NSArray *matching = fetchedWithPredicate(#"Place", self.managedObjectContext, predicate, error);
if ([matching count] > 0) {
return NO;
}
else {
return YES;
}
}
To import the data, I fetch all places from the server, returned in JSON format.
Each Place has its own information, plus a child node with informations about the parent, which means that each parent of multiple children will appear multiple times. It looks like
{ "id": 73,
"name": "Some place",
"parent": { "id": 2,
"name": "Parent's name"}
}
I thought the above code which does kind of a "find or create", with a fetch including unsaved changes, would be alright.
But it still attempt to create multiple entries for some places (and fails to since there's a validation in place). Looking deeper, it indeed insert different core data objects for the same placeId (different pointers), but I don't know why.
Thanks
It sounds like you already have a unique index on id (which is good obviously). I think it is that you are not saving the newly inserted creations to core data prior to expecting it to be returned via fetch. The simple (if perhaps not too performant depending on having lots of rows) would be to add a saveContext call right after each object is inserted/inited.
Another way would be to do it in two passes, first entirely in memory where you create a separate dictionary where the key is the id, and the object is the value. That way you'd be able to ensure each id was only in there once. After they're all in that dictionary, you can then easily (or easier, perhaps) add them all to Core Data.
So, after a bit more investigation, it's due to the fact that I was sorting my data by name...
So if a place A had 5 children, and 3 of them had a name that was before A's name, the code would:
create those 3 children with a parent that don't have any parent itself (because my json doesn't return infos about the parent's parent)
create A
create the 2 other children with A as parent (probably because of the way it's sorted, but that doesn't change the conclusion), so a parent that does have a parent
Now we have 2 objects A, one with a parent, and one without a parent, which Core Data consider has 2 objects.
The easy way out: my tree is a nested set, so I just have to sort places by the left value, and this way I'll always create parents before children.
The "sort by name" wasn't part of my description, so I'll leave scc's answer as accepted :)

iOS Core Data unique object fetch

I'm fairly new to iOS/Cocoa and I have a very fundamental question about core data. I was looking over the internet to find a appropriate answer/solution but I wasn't able to.
So the question is how do you handle uniqueness in core data? I know that core data is not a database, its something like an object graph. Lets assume we have an entity called 'User' with the attributes 'id' and 'name' and a few relations to the other entities. Now we want to update the name of a specific user (e.g. a web service gave us the id and the new name).
This was the way I have done that before:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"id == %#", anId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *user = [results lastObject];
But than I've heard this is bad practice. Is it because fetch requests with predicates are very slow? I can't imagine that this is such a big deal. As far as I know there is no other way to get a unique object rather than go over each object and checking for equality..
Would it be more efficient to fetch all objects of the entity, put them in an array and looping through it manually (instead of the fetch request)?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
User *resultUser;
for(User *user in results){
if([user.id isEqual:anId]) resultUser = user;
}
Please help me finding the right path and thanks.
If you have an index for the property that you're fetching (there's a checkbox in the model editor), it's definitely a lot more efficient to use an equality predicate for fetching. Actually, it's never more efficient to fetch all objects and iterate over them in memory, but an index makes the difference more significant.
You're not fetching a unique object but rather objects containing a notionally unique atrribute value.
All managed objects are unique instances but nothing in Core Data enforces that the attribute values are unique. You could in principle have an arbitrary number of unique managed objects all which had identical attribute values. Only relationships enforce a unique position in the object-graph.
There's no particular reason not to fetch a particular object that contains a particular value if that is what your app requires.
I think what you've read is warnings against trying to cram SQL-like key values into entities and then to try and link managed objects together with those keys using predicates, for example doing something like:
EntityOne{
sqlKey:string
}
EntityTwo{
sqlKey:string
}
… and then trying to relate objects of the two entities with predicates.
(anEntityOneObject.)sqlKey == anEntityTwoObject.sqlKey
… instead of just setting a relationship in the data model.
EntityOne{
two<-->EntityTwo.entityOne
}
EntityTwo{
one<-->EntityOne.two
}
… and just finding the related objects with AnEntityOneObj.two.

Resources