Core Data Programmatically Adding a to-many Relationship - objective-c

I have an entity Item, and an entity Type (that has an attribute "Name") in a to-many relationship with Item. (Ie: Item: Brown Table, related to Type with Name "Coffee Table").
I've programmatically added new Items fine, using, for example:
[newItem setValue:([nameTextField stringValue]) forKey:#"Name"];
[newItem setValue:(costNumber) forKey:#"Cost"];
[newItem setValue:(priceNumber) forKey:#"Price"];
I've been searching for hours but can't find something that works for me adding a relationship to the new item. I'm using a NSPopUpButton to choose the Type of the item, and have tried methods like selectedItem, selectedTag, and selectedCell. I'm trying to get values from my "typeArray", which is filled as follows:
NSFetchRequest *fetchRequest2 = [[NSFetchRequest alloc] init];
NSEntityDescription *entity2 = [NSEntityDescription entityForName:#"Type"
inManagedObjectContext:managedObjectContext];
[fetchRequest2 setEntity:entity2];
NSError *error = nil;
typeArray = [managedObjectContext executeFetchRequest:fetchRequest2 error:&error];
if (typeArray == nil) {
NSLog(#"ERROR");
}
[fetchRequest2 release];
I'm not sure if the following is along the right lines:
NSManagedObject *selectedType = [typeArray objectAtIndex:[typePopUpButton selectedTag]];
But then I have no option for selectedType to add something like "addObject"..
Any help appreciated, thank you.

This is what I ended up using:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Type"
inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"Name like %#", [typePopUpButton titleOfSelectedItem]];
[fetchRequest setPredicate:predicate];
NSArray *typeSet = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (typeSet == nil) {
NSLog(#"ERROR");
}
[fetchRequest release];
NSManagedObject *typeObject = [typeSet objectAtIndex:0];
[typeObject addItemsObject:newItem];
Basically, the object needs to be fetched so that a relationship can be made between the two items, and the predicate is based on the typePopUpButton's titleOfSelectedItem method.
I ensure to only select one object with the method [objectAtIndex:0].
It does bring up a warning though: NSManagedObject may not respond to 'addItemsObject'.
However this does work for me.

Related

I insert my CoreData in background Context and when I fetch, it doesn't show the data

I am trying to do my Core-data operation on background context.
I insert all my data on the background context but when I fetch on main thread, it doesnt show any data which I already inserted.
I dont know, where am I doing the mistake...:(
Any help appreciate.
This Is what I have tried for insert and the second one is for fetch the data.
- (void)insertNameAndSurnameInDataBase:(NSString *)name surname:(NSString *)surname numbers:(NSArray *)numbers labels:(NSArray *)labels {
AppDelegate *appDel=(AppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *saveObjectContext = [appDel saveManagedObjectContext];
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.parentContext = saveObjectContext;
[bgContext performBlockAndWait:^{
NSError *error;
NameAndSurname *nameAndSurname = [NSEntityDescription insertNewObjectForEntityForName:#"NameAndSurname" inManagedObjectContext:bgContext];
NSString *nSurname = [NSString stringWithFormat:#"%# %#",name,surname];
if (nSurname.length == 0) {
nameAndSurname.nameSurname = [numbers objectAtIndex:0];
} else {
nameAndSurname.nameSurname = nSurname;
}
if ([bgContext save:&error]) {
} else {
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname" inManagedObjectContext:bgContext];
// predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"nameSurname =[c] %#", nSurname];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *fetchedObjects = [bgContext executeFetchRequest:fetchRequest error:&error];
if([fetchedObjects count] > 0){
[numbers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
id obj2 = [labels objectAtIndex:idx];
obj = [obj stringByReplacingOccurrencesOfString:#" " withString:#""];
ContactNumber *phoneNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:bgContext];
phoneNumber.number = obj;
phoneNumber.label = obj2;
phoneNumber.nameAndSurname = fetchedObjects[0];
NSError *error;
if ([bgContext save:&error]) {
} else {
}
}];
}
}];
}
- (NSArray *)fetchNameAndSurname {
AppDelegate *appDel = (AppDelegate *)[UIApplication sharedApplication].delegate;
_managedObjectContext = [appDel managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:#"nameSurname" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]]];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSError *error = nil;
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
return [fetchedObjects valueForKey:#"nameSurname"];
}
The mistake you are making is to not test the many little pieces of this code which could be not working as expected. To troubleshoot something like this, you must start at the beginning and test one piece at a time. The first issue I see is confusion between managed object contexts…
It appears that the method saveManagedObjectContext is a getter for your main managed object context. In English, save is a verb, so that this method name implies that it is saving some managed object context. If I am correct, you should change the name saveManagedObjectContext to maybe mainMangaedObjectContext.
The statement _managedObjectContext = [appDel managedObjectContext] is quite nonstandard, assigning to an instance variable from what appears to be its getter. If, as usual, _managedObjectContext is the instance variable backing the property managedObjectContext, this statement has no effect.
In your first method you use [appDel saveManagedObjectContext], and in your second method you use [appDel managedObjectContext]. It looks like these should be the same context in order for your fetch to work. Are they?
UPDATE:
As you stated in your comment, that fixes the saving, but now you have a performance problem – saving to the persistent store on disk blocks the user interface, which is what your original code was trying to fix.
This is a solved problem. The solution is that your main-thread context, which interfaces the user, should be a child of your background-thread context which interfaces to the persistent store. It is explained nicely with code in this 2015 blog post by Marcus Zarra. For further reading, Chad Wilken has published a slight variation. Both are written in Objective-C for you :)

Fetch Relationship Objects

CoreData beginner
I have a simple problem with CoreData. My model has two entities, now called A and B. Entity A has a to many relationship of B entities, which has a inverse relationship to entity A.
I'm retrieving entities A with this code:
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"A"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:request error:&error];
if (error) /* ... */;
for (id item in items)
{
/* ... */
}
[request release];
[descriptor release];
Now I'd like to retrieve, inside that loop, an array of all the objects B pointed by the relationship of A. How can I achieve this? Should I create another fetch request or there is a more practical way?
I've searched StackOverflow and found similar questions, but too vague sometimes.
NSFetchRequest has an instance method on it called -setRelationshipKeyPathsForPrefetching:.
This method takes an array of key names that will be used to prefetch any objects defined in relationships with those key paths. Consider your example, updated with the new code:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
NSString *relationshipKeyPath = #"bObjects"; // Set this to the name of the relationship on "A" that points to the "B" objects;
NSArray *keyPaths = [NSArray arrayWithObject:relationshipKeyPath];
[request setRelationshipKeyPathsForPrefetching:keyPaths];
Now once you complete your fetch request, all of those relationship objects should be faulted in and ready to go.

Core Data: keypath name not found in entity

I'm crashing with this message :
'NSInvalidArgumentException', reason: 'keypath name not found in entity
Obvisouly I'm not querying my entity correctly .
//fetching Data
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Viewer" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSString *attributeName = #"dF";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name like %#",attributeName];
[fetchRequest setPredicate:predicate];
NSLog(#"predicate : %#",predicate);
NSError *error;
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
NSLog(#"items : %#",items);
[fetchRequest release];
//end of fetch
And here is my data Model:
I want to return the value of "dF", shouldn't call it like this ? :
NSString *attributeName = #"dF";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name like %#",attributeName];
If you want to get value from your dF property, you have to fetch an array of NSManagedObjects and then use [fetchedManagedObject valueForKey:#"dF"]; to get your value.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Viewer" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *items = [context executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
NSManagedObject *mo = [items objectAtIndex:0]; // assuming that array is not empty
id value = [mo valueForKey:#"dF"];
Predicates are used to get array of NSManagedObjects that satisfy your criteria. E.g. if your dF is a number, you can create predicate like "dF > 100", then your fetch request will return an array with NSManagedObjects that will have dF values that > 100. But if you want to get just values, you don't need any predicate.
I was using a NSSortDescriptor initialized with a String key:
let fetchRequest = NSFetchRequest<SomeManagedObject>(entityName: "SomeManagedObject")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
I then changed the name of the name attribute in the model. Refactored all of the property names, but didn't catch the stringly typed key: "name".
The solution for Swift is to use NSSortDescriptor(keyPath: instead, which will fail to compile if the property key path changes.
NSSortDescriptor(keyPath: \SomeManagedObject.firstName, ascending: true)

How do you create a Set of Entities which all have Relations to other Entities with Specific Attributes? In Core Data

I've got a Core Data entity called "Card" which has a relationship "info" to another entity, "CardInfo". It's a one-to-many relationship: each card can have multiple CardInfos, but each CardInfo only has one Card.
The CardInfo entity just has two strings, "cardKey" and "cardValue". The object is to allow for arbitrary input of data for cards. Say, you wanted to know what color a card was. Then you added a CardInfo to each Card that had a cardKey of "color" and a cardValue of "black" or "red".
My general question is: what's the best way to get the set of Cards where each Card has a CardInfo where the CardKey and CardValue has specific values. For example: all Cards with relationship to CardInfo cardKey='color' and cardValue='red'? Ideally, I return an NSSet of all the appropriate Card * objects.
The loop at the end is not needed. A simple KVC call will clean it up nicely.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"CardInfo" inManagedObjectContext:self.managedObjectContext]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"cardKey = %# AND cardValue = %#", thisKey, thisValue]];
NSError *error = nil;
NSArray *items = [[self managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release], fetchRequest = nil;
NSAssert1(error == nil, #"Error fetching objects: %#\n%#", [error localizedDescription], [error userInfo]);
return [items valueForKeyPath:#"#distinctUnionOfObjects.card"];
This is the answer that I came up with, but the two-part process seems inefficient to me. I figure there must be a more elegant way to do this with key-values or something
-(NSSet *)cardsWithCardKey:(NSString *)thisKey cardValue:(NSString *)thisValue {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CardInfo"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"(cardKey=%#) AND (cardValue=%#)",
thisKey,thisValue];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
NSMutableSet *cardSet = [NSMutableSet setWithCapacity:[items count]];
for (int i = 0 ; i < [items count] ; i++) {
if ([[items objectAtIndex:i] card] != nil) {
[cardSet addObject:[[items objectAtIndex:i] card]];
}
}
return [NSSet setWithSet:cardSet];
}

Removing an Object with A Certain Title from an NSTreeController

I am wondering how I could delete an object depending on it's title for the CoreData 'name' property I have.
To Add an Object I use this code:
NSManagedObjectContext *moc = [self managedObjectContext];
JGManagedObject *theParent =
[NSEntityDescription insertNewObjectForEntityForName:#"projects"
inManagedObjectContext:moc];
[theParent setValue:nil forKey:#"parent"];
// This is where you add the title from the string array
[theParent setValue:#"myTitle" forKey:#"name"];
[theParent setValue:[NSNumber numberWithInt:0] forKey:#"position"];
But I can't seem to find an equivalent function to remove An object.
I don't know if you looked in the Core Data Programming guide in the section for adding and deleting objects.
Edit
I've modified this to delete from an array of names. Again; less then 5 minutes work with the Predicate Programming Guide.
- (void)removeObjectsWithNames:(NSArray *)nameArray {
// Get the moc and prepare a fetch request for the required entity
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Create a predicate for an array of names.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name IN %#", nameArray];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
// Execute the fetch request put the results into array
NSError *error = nil;
NSArray *resultArray = [moc executeFetchRequest:request error:&error];
if (resultArray == nil)
{
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
// Enumerate through the array deleting each object.
// WARNING, this will delete everything in the array, so you may want to put more checks in before doing this.
For (JGManagedObject *objectToDelete in resultArray ) {
// Delete the object.
[moc deleteObject:objectToDelete];
}
}
Edited 10/10/2009 - To add what Joshua has tried.
for(NSString *title in oldTasks) { // 1
// Get the moc and prepare a fetch request for the required entity
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"projects" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Create a predicate for an array of names.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title IN %d", oldTasks]; // 2
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
// Execute the fetch request put the results into array
NSError *error = nil;
NSArray *resultArray = [moc executeFetchRequest:request error:&error];
if (resultArray == nil)
{
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
JGManagedObject *objectToDelete = [resultArray objectAtIndex:0];
// Delete the object.
[moc deleteObject:objectToDelete];
}
Notes
I've highlighted two lines.
You've pasted my example as a for loop rather than a function call. This is just taking the strings off one at a time and passing them into the method. In my example, I'm passing in an array of strings that you want to match.
This is where you are having your problem. If you'd bothered reading the the Predicate Programming Guide, right at the top, in the Predicates Basics section it says it expects the class that it is being used with should be KVC compliant. This is why you are getting the error about KVC compliance. You are trying to search for title IN... but title isn't a property of your model.
I think you may be confused about what a predicate does. Look at the exaple code that I wrote.
Firstly, I create a Fetch request which will select objects from the 'Projects' entity.
Secondly, I create a predicate which says for each object returned by the fetch request, get the value of the 'name' property and compare it to the values of the objects in the 'namesArray'
Thirdly, I'm creating a sort descriptor that will sort the results in ascending order based on the 'name' property.
Then, once I've set this fetch request up, I run it against the moc and it returns an array of objects that match these criteria.