iOS / Core Data Entity Relationships with Pre-Defined Data - ios

I have two Objective-C Core Data entities - say Person and Nationality. Person would have a To-One relationship with Nationality, whilst Nationality would have a To-Many relationship with Person. Also, Person class can have any number of objects / rows, whereas Nationality would have a pre-defined list of 200 odd instances. So Person should not be able to assign a nationality to itself other than those 200 objects.
Can someone advise please how would we code this out or in case there is a sample code available? Afraid I dont seem to be able to get a start on how to leverage setValue: forKey: here...
Much appreciated!

Let's assume that your Nationality entity has a "name" attribute that uniquely identifies that nationality. You can provide any manner of UI to get this from the user. It can be typing in a string or fetching all nationalities and putting them in a table or some kind of picker.
If you want all nationalities, that's easy.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Nationality"];
NSError *error;
NSArray *nationalities = [moc executeFetchRequest:fetchRequest error:&error];
if (nationalities == nil) {
// handle error
} else {
// You now have an array of all Nationality entities that can be used
// in some UI element to allow a specific one to be picked
}
If you want to look it up based on a string for the name...
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Nationality"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"name = %#", nationalityName];
fetchRequest.fetchLimit = 1;
NSArray *nationalities = [moc executeFetchRequest:fetchRequest error:&error];
if (nationalities == nil) {
// handle error
} else {
NSManagedObject *nationality = [nationalities firstObject];
// If non-nil, it will be the nationality object you desire
}
Creating the person and assigning its nationality is also straight forward...
if (nationality) {
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:moc];
// Set any attributes of the Person entity
[person setValue:#"Fred Flintstone" forKey:#"name"];
// Assign its nationality, and as long as the relationship is setup with
// inverse relationships, the inverse will be automatically assigned
[person setValue:nationality forKey:#"nationality"];
}

Related

How to fetch Specific Data in One to Many Core Data Relationships?

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];
}

iOS: core data update object with one- with to-many relationship

In my app I have this code to update a DB:
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest=[NSFetchRequest fetchRequestWithEntityName:#"Struct"];
NSError *error = nil;
for (id element in array){
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"id==%#",[element objectForKey:#"id"]]];
Struct *struct = [[context executeFetchRequest:fetchRequest error:&error] lastObject];
if (struct != nil){
//updating value of attributes
struct.name = [element objectForKey:#"n"];
struct.val = [element objectForKey:#"val"];
struct.pos = [element objectForKey:#"pos"];
}
else{
//create a new identity
}
}
it's all ok but I have other two objects in relationship with Struct:
"Id_Loc" (one to many)
"Details" (one to one)
I don't know how to update them, I don't know how to call them from my object 'Struct'
I don't find nothing
Can you help me?
thanks
It works the same way you're using .name, .val and .pos.
The one-to-one relationship will return an object, the one-to-many relationship will return a NSSet.
Say your one-to-one relationship is from Struct to Bar and the relationship name is bar, you'll receive a Bar object by using struct.bar.
Edit:
If you want to add new objects to the one-to-many relationship you should use [struct addFooObject:]; where Foo is your objects name.

Updating a particular attribute of core data

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;
}

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 :)

Core Data, display to-many relationship distinctly along with aggregate count

I'm beginner in Core Data, so trying to wrap my mind around the following.
Let's say I have the following model:
Kid (1) -> (M) ToyName (car)
ToyAttributes (1) -> (M) key (color), value (black)
key (price), value (20)
key (store), value (toys r us)
ToyAttributes has 3 key/value pairs.
What I want to do is list the colors distinctly. So, if other toys are black also, I want to display black only once.
Another nice thing to do would be to display the aggregate count of how many black toys we have.
I currently have the following:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Kid" inManagedObjectContext:managedObjectContext_];
But unsure how to structure the proper KVC to find the proper key=color and get those values for every kid.
You need to set inverse relationship on your Entity, then if you use an NSPredicate to fetch the black one you will be able to follow the inverse relationship to retrieve the desired ToyName.
You minimally want to read those :
Fetching Managed Objects
Predicate Format String Syntax
This is some old code I've got, not perfect and not exactly what you are looking for, but still an example of the use of a NSPredicate.
This is a simple NSFetchRequest that will return all kListe entity ordered in some way.
- (NSFetchRequest *)requestPourListe{
NSFetchRequest *resultat = nil;
NSEntityDescription *description4Liste =
[NSEntityDescription entityForName:kListe
inManagedObjectContext:self.managedObjectContext];
resultat = [[NSFetchRequest alloc] init];
[resultat setEntity:description4Liste];
[resultat setSortDescriptors:self.sortDescriptorsPourNom];
// Si on veut faire un Fetch plus petit en mémoire (Partial Fault)
//[resultat setPropertiesToFetch:[NSArray arrayWithObject:kNom]];
return [resultat autorelease];
}
// Now time to use that request get the request
NSFetchRequest *requet4Liste = FRUtile.requestPourListe;
NSError *monErreur = nil;
// And Use it agains a NSManagedObjecContext (moc)
NSArray *lesListes = [moc executeFetchRequest:requet4Liste
error:&monErreur];
In the folloing self.list is one of the NSManagedObject that was fetch with the preceding request.
// those NSSet are set that were return from a simple relationship query
// so those set contain NSManagedObject
NSSet *desListeItems = self.liste.listeItems;
// HERE I'm fetching the relationship kRelationPrixElement on all NSManagedObject
// present in the set desListeItems (one to one relationShip)
NSSet *desPrixElements = [desListeItems valueForKey:kRelationPrixElement];
// generate the path, traverse the relationship call kRelationCommerce and
// reach it's attribute name kNom
NSString *predicateKey = [NSString stringWithFormat:#"%#.%#", kRelationCommerce, kNom];
// get an array out of one set probably not necessary
NSArray *desPrixElementsAR = [desPrixElements allObjects];
NSMutableArray *lesPrixAAfficher = [[NSMutableArray alloc] initWithCapacity:0];
for (NSString *value in self.commercesNoms) {
// in a predicate with format %K MUST be the key or keyPath and
// %# MUST be the value you are searching for
// the predicate here is done agains a many-to-one relation
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(%K MATCHES %#)", predicateKey, value];
// Now I'm applying the predicate to the array
[lesPrixAAfficher addObjectsFromArray:[desPrixElementsAR filteredArrayUsingPredicate:predicate]];
}
There is a thing that you must take into account while designing your model and your fetch request, you can follow relationship in a keyPath throughout many Entities AS LONG AS those relationship are to one relationship.
You can't use a keyPath to traverse a to-many relation, because how could Core Data know which of the entity it should follow on.
And finally when you got an NSManagedObject you can use dot notation on it to retreive it's properties and relationships.

Resources