Edit Entity in Core Data - iphone

Is there a method similar to insertNewObjectForEntityName that edits the current entity being passed in managed object context? I don't want to create another duplicate entity.
In addition, I don't want users to be able to enter two entities with identical attributes (one attribute, the event title). How can I make it so an alert pops up when they try to add a new entity with an identical title attribute?

Your first question it sounds like what you want to do is get an object that is already in the context with a fetch request, change some values on the object then call the -save method on your context.
For the second part, what you would do is when the user tries to add an item, search the context for an object with the same title, if the item exists, pop up an alert.
Edit: here is some code from my app (edited a bit) in which I set up and execute a fetch request:
NSFetchRequest *categoryRequest = [[NSFetchRequest alloc] init];
[categoryRequest setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:[self managedObjectContext]]];
NSString *categoryName = #"Cooking";
NSPredicate *categoryNameMatchesPredicate = [NSPredicate predicateWithFormat:#"name MATCHES %#", categoryName];
[categoryRequest setPredicate:categoryNameMatchesPredicate];
NSError *error = nil;
NSArray *categoryArray = [[self managedObjectContext] executeFetchRequest:categoryRequest error:&error];
After this request, the array categoryArray contains all category entities with the name "Cooking". If there are no entities with the name "Cooking" the array will be empty.
It is probably faster to use -countForFetchRequest:error: and check for nonzero count before you actually execute the fetch request, but I am not sure it matters that much in a smallish iOS app.

Related

How to check if Entity in Core Data has a value id?

I would like to get the id of some data stored in Core data... This is to check if some value exist already inside the database.. If it exist, it shows something if not else.
The id always changes because I get it in the last view from the push. So if the ID exist in the database, I want to detect it.
I think you asking about querying in Core Data. Here is a basic example:
NSString *testEntityId = #"555";
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
fetch.entity = [NSEntityDescription entityForName:#"YourEntity" inManagedObjectContext:moc];
fetch.predicate = [NSPredicate predicateWithFormat:#"entityId == %#", testEntityId];
NSArray *array = [moc executeFetchRequest:fetch error:nil];
You'll get an array of items that match.
You can compare object directly with a
[Object isEqualsTo:secondObject]
Otherwise, you can create a integer inside your core data with a field name like "GUID" or other name and verify that integer doesn't exist for each adding...
Good luck ;)

Two Contexts, 1 Persistent Store: Duplicate Fetched Entries

I am attempting to create a way for users to import contacts to their phone. How it works is this:
There are two managed object contexts. The "real" context has the current data in their address book. The "other" context has incoming data from another source. Both share the same PersistentStoreCoordinator.
I match people by e-mail, so if a contact in the "real" context matches one in the "other", I don't save the other.
When I start the program, I have two entries in the "real" context that I can fetch fine.
Then, I import two other contacts an add them to the "other" context.
When I perform a fetch operation on the "other" context, I get FOUR results - two from the "real" context and two I just added to the "other" context.
However, when I merge the changes, my scheme for detecting duplicates works.
Is there something I'm missing with my understanding of Core Data? How can I make it so that my querying of the "other" context just returns the new results.
The full code is really long, but here's the important part:
AppDelegate *appDel = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// Check to see the original data
NSManagedObjectContext *realContext = [appDel managedObjectContext];
NSFetchRequest *usersFetch= [[[NSFetchRequest alloc] init] autorelease];
[usersFetch setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:realContext]];
[usersFetch setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"email" ascending:YES]]];
NSArray *users = [realContext executeFetchRequest:usersFetch error:&error];
[usersFetch release];
NSLog(#"%#",users); // Returns 2 original objects already in database
otherContext = [[NSManagedObjectContext alloc] init];
[otherContext setPersistentStoreCoordinator:[[appDel managedObjectContext] persistentStoreCoordinator]];
for (contacts in fetchedData){
User *newUser = (User*)[NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:otherContext];
newUser.email = fetchedData.email;
newUser.firstName = fetchedData.firstName;
// etc.
}
NSFetchRequest *newUsersFetch = [[[NSFetchRequest alloc] init] autorelease];
[newUsersFetch setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:otherContext]];
[newUsersFetch setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"email" ascending:YES]]];
NSLog(#"%#",[otherContext registeredObjects]); // 2 objects that were just added
NSArray *newUsers = [otherContext executeFetchRequest:newUsersFetch error:&error];
NSLog(#"%#",[otherContext registeredObjects]); // 4 objects - added AND original
NSLog(#"Count: %i",[newUsers count]); // Count: 4
I think you may be conceptually confusing the managed object contexts with the persistent store. You can have an arbitrary number of context attached to a particular store and the changes made in any single context will eventually show up in all the others.
This is especially true of fetches which go directly to the store to find objects. Your code is working as expected if you call a save on the other context. Once you save an object, it goes into the persistent store and will show up in all entity wide fetches.
You should not be creating managed objects that might have duplicates. Instead, the normal practice is to fetch on new values to see if they already exist and only create a new managed object with the value if the value does not already exist in the store. To make that fast, you can do a fetch for a specific property and see if anything is returned.

Core Data Edit Attributes

So im really new to core data, but i went through a tutorial and pretty much understand it, well at least the idea behind most of the things. But I still have 1 question that i cant find anywhere. It seems really simple but here it is. If I were to have two strings inside one entity lets say:
1.name
2.position
If the name is already entered how might i allow a user to enter text into a textField and assign it to their position at a later time? Even if there were 20 names, considering no duplicates?
I was thinking it might be something like this...But it doesnt seem to work.
UserInfo *userInfo = (UserNumber *)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:managedObjectContext];
if ([userName isEqualToString:"#James"]) {
userInfo.Position = nameField.text;
}
On the code above you are casting (UserNumber*) to an object that you are declaring as (UserInfo*)? Which is what and is there any reason why you are doing that?
If I understand your question correctly, you want to create a record with only the username pre-populated and then allow that record to be updated at a later stage.
I will assume your entity is called UserInfo and that there are 2 NSString properties created for it - userName and position. I also assume you have created the class files for UserInfo and imported the header into the relevant view controllers.
Here's how you would do it:
1) Firstly, assuming you have username typed in a UITextField *userNameField, let's create a new record.
UserInfo *userInfo = (UserInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[userInfo setValue:userNameField.text forKey:#"userName"];
This will create a new instance of UserInfo in your managed object context and set the value of userName to the value on userNameField.text
Then at a later stage a user will get to a point where they can update their records in your app (you may need to think about authentication somewhere here). You will fetch the record that matches your specified username:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *userNamePredicate = [NSPredicate predicateWithFormat:#"(userName == %#)", userNameField.text];
[fetchRequest setPredicate:userNamePredicate];
NSEntityDescription *userInfo = [NSEntityDescription entityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:userInfo];
NSError *error;
NSArray *fetchRequestArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
If the fetchRequest found match(es) to your userNameField.text paramater, they will be saved in the fetchRequestArray. There should only be a maximum of one object there if you take the necessary steps to make the userName property mandatory AND unique.
Access the object by grabbing the objectAtIndex:0 in the array and change it's position property:
UserInfo *userInfoToBeEdited = [fetchRequestArray objectAtIndex:0];
[userInfoToBeEdit setValue:positionTextField.text forKey:#"position"];
In both cases above, remember to invoke CoreData's save method when you are ready to commit your changes. Before save is invoked your changes are only kept in your managed object context which is basically a scratch pad for your persistent data.
[EDIT TO ADD SAVE METHOD]
As per your comment, I usually have the save method below in my AppDelegate (copy/paste directly from Apple template)
- (void)saveContext
{
error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
[self seriousErrorAlert];
}
}
}
And then whenever I need to save changes, from any view controller I simply grab a reference to my AppDelegate and fire it off:
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[theDelegate saveContext];

Core-Data: How to set up relationship from one object to specific one in many objects with same name?

I have a question in core data:
there are 2 Entities in the project, Books and Pages;
there are 3 Objects books user created in Entity Books;
there are several Objects pages user created in Entity Pages;
relationship inbetween is one page belongs to one book, one book has many pages.
and my question: there are 3 book with the same object name "book",and each has unique attribute .bookName : #"metal" ;#"plastic" ;#"glass". how to set page to the book with .bookName = #"glass" ?
//In BookViewController
Books *book = (Books *)[NSEntityDescription insertNewObjectForEntityForName:#"Books" inManagedObjectContext:managedObjectContext];
//textView.text is user input text
book.bookName = textView.text;
//In PageViewController
Pages *page = (Pages *)[NSEntityDescription insertNewObjectForEntityForName:#"Pages" inManagedObjectContext:managedObjectContext];
page.itsbook = WHAT?
Thank you for reading, I stuck here like: a day, really appreciate your help, love you!
If you need to find a specific book then you need to use a NSFetchRequest to ask Core data for it.
Quite some code is needed, so you probably add a convinience method to your Bookclass that looks something like this:
+(Book*)bookWithName:(NSString*)name
{
// 0. I assume you have something like this to get the context…
NSManagedObjectContext* context = [NSManagedObjectContext threadLocalContext];
// 1. Create an empty request
NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
// 2. Set the entity description for the object to fetch
NSEntityDescription* entity = [NSEntityDescription entityForName:#"Book"
inManagedObjectContext:context];
[request setEntity:entity];
// 3. Set a predicate asking for objects with a mathing bookName property
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"%K LIKE[cd] %#",
#"bookName",
name];
[request setPredicate:predicate];
// 4. Execute the request on the managed object context.
NSArray* objects = [context executeFetchRequest:request error:NULL];
// 5. Result is an array, maybe handle empty array and many objects?
return [objects lastObject];
}
I'm not sure what you mean. If you want to insert a page as a subset of the book you want this line of code:
[book addPageToBookObject:page]; // the method will be the relationship name
If you want to get the book from the page object you'll need an inverse relationship from the pages to the book.

Core Data : How to check for the presence of Many to Many relationship

I have a "Song" Entity and a "Tag" entity and they have a many to many relationship between them. A Song can have multiple Tags and a Tag can be applied to multiple Songs.
I want to check if a Song has a particular Tag associated with it. If the Song has the Tag associted with it, I want to show a checkmark in the table view.
For a similar logic, in Apple "TaggedLocations" sample code, the following check is made to check for the presence of the relationship.
if ([event.tags containsObject:tag]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This may be inefficient if there are a lot of Tags in the database as this will fetch all of them in the memory. Please correct me if I am wrong here.
Is there a more efficient way to check if the Song is associated with a particular Tag instead of checking in Song.Tags?
It's actually pretty easy to do, if completely undocumented. You want to create a fetch request with a predicate that has a set operation. If we imagine that your Tag model has a property called tagValue, the predicate you care about is "ANY tags.tagValue == 'footag'"
NSString *tagSearch = #"footag";
// However you get your NSManagedObjectContext. If you use template code, it's from
// the UIApplicationDelegate
NSManagedObjectContext *context = [delegate managedObjectContext];
// Is there no shortcut for this? Maybe not, seems to be per context...
NSEntityDescription *songEntity = [NSEntityDescription entityForName:#"Song" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:songEntity];
// The request looks for this a group with the supplied name
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.tagValue == %#", tagSearch];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
[request release];
You are correct, using that code will retrieve the entire set and the object comparison may be quite complex, depending on how many properties and relationship are part of the object's entity.
Anyway, you can not avoid a set comparison for inclusion. Probably, the best you can do is to avoid fetching all of the properties/relationships by asking Core Data to retrieve NSManagedObjectID Objects only.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Tag" inManagedObjectContext:[self managedObjectContext]]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSManagedObjectID objects are guaranteed to be unique, therefore you can safely use them to check for set inclusion. This should be much more efficient for a performance perspective.