iPhone - Core Data : forgetting object - iphone

I am inserting new entries to a core data entity using this
newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"myEntity" inManagedObjectContext:context];
after that, I populate newEntry properties but at some point, and before this newEntry is saved to the context, I may want to remove this newEntry from the "buffer" or whatever, so it will not be saved when I commit the changes.
How do I do that? I have tried to use reset and rollback but it had no effect and the object continues to be saved.
thanks

[context deleteObject:newEntry];
This will remove it from the context.
More information and sample code

Related

Why is this code raising the "CoreData: error: (19) PRIMARY KEY must be unique" error?

This code raises the "CoreData: error: (19) PRIMARY KEY must be unique" error.
The Day entity has only a when attribute which is an NSDate, and a to-many relationship called tasks. Why this error? If a Day with a specific date is already stored, I fetch it, otherwise I insert it. So, for each day object, there should be a different when attribute. I am not sure if this is the primary key though. How to solve this ? Thank you in advance.
NSMutableSet *occurrences = nil;
occurrences = ...
NSMutableOrderedSet *newSet = [NSMutableOrderedSet orderedSetWithCapacity:[occurrences count]];
for(NSDate *current in occurrences) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// try to find a corresponding Day entity whose when attribute is equal to the current occurrence
// if none is available, create it
Day * day = [[self getDayForDate:current inManagedObjectContext:moc] retain];
if(!day){
day = (Day *) [NSEntityDescription insertNewObjectForEntityForName:#"Day" inManagedObjectContext:moc];
}
day.when = current;
[day addTasksObject:aTask];
[newSet addObject:day];
[moc insertObject:day];
[moc processPendingChanges];
[day release];
[pool release];
}
- (Day *)getDayForDate:(NSDate *)aDate inManagedObjectContext:(NSManagedObjectContext *)moc
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Day" inManagedObjectContext:moc];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(when == %#)", aDate];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
[request release];
Day *theDay = nil;
if(array && [array count] == 1){
theDay = [array objectAtIndex:0];
}
return theDay;
}
I have recently been struggling with the CoreData: error: (19) PRIMARY KEY must be unique error with an in-house iOS application, and thankfully discovered the solution after a day or two of research and code modifications, and I wanted to share my findings here in the hopes that they would be of help to others.
First, a little background
Our in-house app was never originally built with any code to update its CoreData store - instead for each app build iteration when new data was required to be displayed within the app, the CoreData SQLite backing store file was simply replaced with a new version that had been edited with a standard desktop-based SQLite editor -- that is the raw SQLite file was modified and updated as needed. While it is recognized that this approach does not take account of the "black-box" nature of CoreData, it had actually served us well with our simple app for the past few years, and the app had happily accepted the updated SQLite file with each new app build.
However, recently we undertook a major overhaul of the app, moving away from a model of updating app assets and data via Xcode and app rebuilds, to a model of obtaining new app data via a web service instead, and it was during this new development work that the PRIMARY KEY must be unique issue arose.
After several days of struggling to solve the issue, thinking that there must be some fault with the new CoreData entity-creation code (which had been checked and rechecked thoroughly) or some other related issue, I found through further research that I was able to enable CoreData SQL debugging for our app using Xcode (as per the instructions in this very helpful SO post).
When I carefully studied the SQL logs in Xcode, I could see that before each call that CoreData made to INSERT a new record into the SQLite backing store, that the framework queried the Z_PRIMARYKEY table with the following query SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ? where ? is replaced behind the scenes with the relevant Z_ENT value for the relevant CoreData entity (you can see the Z_ENT value for each of your CoreData entities by reviewing the contents of the Z_PRIMARYKEY table). This is when I finally understood what was happening within CoreData's black box! I then took a closer look at our app's SQLite file using the Liya app on the Mac, and I reviewed the contents of the Z_PRIMARYKEY table, and sure enough the Z_MAX column values were all set to 0. They had never been changed from their initial values when CoreData had first generated the empty SQLite backing store file for our app!
I immediately realised what was going wrong and just why CoreData had reported the primary key error as it had -- it was not in fact related to any higher-level CoreData entity object's attributes clashing somehow as initially suspected, but was indeed a lower-level error. Only now did the error make absolute sense, it had been there all along, it had just not been understood within the correct context.
As it turned out from further research, our in-house team had been successfully making direct edits to the SQLite backing store for the past few years, inserting, updating and deleting records from the various entity tables, BUT our team had never made any changes to the Z_PRIMARYKEY table, and due to the way that our app had been utilising CoreData in a read-only fashion, this had never been an issue.
However, now that our app was trying to create and save CoreData entries back to the SQLite backing store, many of the INSERT queries which were being generated as a result simply failed because CoreData was unable to obtain the correct maximum primary key value from which to generate the next sequential primary key on any given table, and as such the INSERT query would fail with the now understandably meaningful PRIMARY KEY must be unique error!
Solving the Error
I realise that each developer may go about generating the default/initial CoreData content for their apps in various ways, however, if you have ever come across this particular error in CoreData and also struggled to find an immediately clear answer, I hope the following thoughts are of help:
Our app's CoreData backing store was manually updated via directly editing the SQLite file (inserting, updating, and deleting records from the entity tables) - this approach had worked for a long time because of the read-only way we had been consuming this data within our app. However this approach failed to truly recognize the abstract "black box" nature of CoreData and that fact that SQLite records are not equivalent to CoreData entities, and that SQLite joins are not equivalent to CoreData relationships, and so on.
If your app uses any kind of pre-populated CoreData store, and if you are populating the contents of your SQLite backing store in any way other than through CoreData -- make sure that if you do create new records in any of your CoreData entity tables, that you also ensure that you update the relevant record in the Z_PRIMARYKEY table, setting the Z_MAX column value to the current maximum Z_PK value in the relevant entity table.
For example if you have a CoreData entity called Employee, within your CoreData
SQLite persistent store backing file this will be represented by a table named ZEMPLOYEE -
this table will have some 'hidden' CoreData columns including Z_PK, Z_ENT,
Z_OPT, etc, in addition to columns that represent your entity's
attributes and relationships. This entity table will also have a corresponding record in the Z_PRIMARYKEY table with a Z_NAME value of Employee - thus when you add a new record directly to the ZEMPLOYEE table - make sure that after you are done adding records, that you go through each entity table and copy the maximum Z_PK value to the Z_MAX column of the Z_PRIMARYKEY table. The value you enter into Z_MAX should be the largest Z_PK value in the corresponding table; do not set Z_MAX to be equal to the value of Z_PK + 1, as this will not be what CoreData is expecting!
You can query for the maximum Z_PK value of any entity table with the following SQL:
"SELECT Z_PK FROM ZEMPLOYEE ORDER BY Z_PK DESC LIMIT 1"
This will give you the largest Z_PK value for any record in the ZEMPLOYEE table - obviously you should replace ZEMPLOYEE with the relevant table name for your app's data model.
For our app, I was able to write a simple command line script that reads the SQLite file directly, iterating through each record of the Z_PRIMARYKEY table and updating it with the correct Z_MAX value. Once this was done, I was able to use this updated file as the app's persistent store backing file. Now, when I insert and save new CoreData entities, everything works as expected.
Now that our app has a way to directly request new data through a web-service we will be moving away entirely from manually editing the SQLite backing store and exclusively using the CoreData frameworks to update the data, but for times, or other apps, where such quick-and-easy data entry may be needed, I hope this answer can help out other developers and save them the time and the effort that it took me to discover this solution.
I guess you don't need to insert a new Day if you already have it (this is the case when day is not nil). In particular I'm referring to [moc insertObject:day].
If you use insertNewObjectForEntityForName, that method inserts the object for you when you save the moc. If you need to modify it (you have retrieved a non-nil day) modify it and save. In additon, I will do processPendingChanges when the loop finishes (just for performance reasons).
Hope that helps.

CoreData basics – to-many relationship array data

As I am fairly new to CoreData and coming from a MySQL-DB background, the CoreData Moddeling is kind of hard to understand at some point. I am sure you can help me out with this basic question.
CoreData model-descripton:
My database-model basically consists of two entities. The first one is called "Manager", the second one is called "Zipcodes". The "Manager" has 3 attributes, which are negligible at the moment. The important thing in my opinion is here the relationship called "zipcodes". The "Zipcodes"-Entity has an attribute called zip, which is a 16 int. It has a relationship as well, called "manager".
No I'll get to the point: Each manager has multiple zicodes in which he is responsible for all sales. The problem is now that I've setup an manager entity and want to link multiple ziplcodes to him. The zipcodes per manager are seperated in one comma seperated string. (12345,56789,...)
First of all I am creating an Manager Entity.
Manager *manager = [NSEntityDescription insertNewObjectForEntityForName:#"Manager" inManagedObjectContext:self.managedObjectContext];
The next step is seperating all zicodes to an array.
Manager *manager = [NSEntityDescription insertNewObjectForEntityForName:#"Manager" inManagedObjectContext:self.managedObjectContext];
NSArray *zipcodesArray = [[dict objectForKey:#"zipcodes"] componentsSeparatedByString:#","];
for (NSString *zip in zipcodesArray) {
???
}
So now that's the point where I am stuck. As later on I have to check the zipcodes via a searchBar they should be separated in the database. Do I now have to create a managedObjectModel for each zipcode? How do I connect all of them with the "one" manager entity? I'am sure there is a way to achieve that but I don't really know how.
Hopefully my question is understandable. If there's anything you would like to know, feel free to ask.
Thank you guys!
for (NSString *zip in zipcodesArray) {
NSManagedObject* zipcode = [NSEntityDescription insertNewObjectForEntityName:#"Zipcode"
inManagedObjectContext:self.managedObjectContext];
[zipcode setValue:zip forKey:#"zip"];
[zipcode setValue:manager forKey:#"manager"];
}
By establishing the relation from the zipcode to the manager on the last line, Core Data will automatically take care of inserting the zipcode into the relation from the manager back to the zipcodes.
Create a managed object instance of Zipcode. Set that object's zip attribute to the value of the string from your zipcodesArray. When you're done, save the managed object to your data store, check for errors, and repeat until you've walked all the way through your zip code array.

Updating an Item in Core Data

I am new to Core Data. I fetch few objects into an NSMutableArray.
Now I want to update an object with objectID x.
My question is:
How do I get the objectId for an object?
and then how do I perform updates on that particular object? (eg: change 'Name' attribute and save)
Thanks
You get the ID by calling [myObject objectID]. You can update and save a new value by calling your objects accessors like you would with any other object. Then to write the changes to the persistent store you do:
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
}

iphone core data loop array and save each

I have a core data model with two tables (meal and ingredients). I am trying to save ONE meal with MANY ingredients. I have the code below which loops through an array of ingredients. I'm trying to save it, but I cannot redeclare the "entity" below. How do I do it? I've tried releasing it, but that didn't work! Thanks for any help.
for (x=0;x<ingredients;x++) {
NSEntityDescription *entity = [NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:managedObjectContext];
entity.name = #"test";
}
(this method does work saving ONE record out of the loop.. so that's not the problem)
You don't insert entities into contexts. You insert managed objects into contexts.
You should have something like:
NSManagedObject *myMO;
for (x=0;x<ingredients;x++) {
myMo = [NSEntityDescription insertNewObjectForEntityForName:#"Ingredient" inManagedObjectContext:managedObjectContext];
[myMO setValue:#"test" forKey:#"name"];
}
Of course, if have an NSManagedObject subclass you can just set the 'name' property directly.
The important thing is not confuse entities with instances of NSManagedObject or its subclasses. Entities are just descriptions of how objects relate to each other inside the object graph of the managed object context. The context uses the entity descriptions to figure out how all the actual instances fit relate to one another and how they are fetched and stored.

Adding string to a tableview iphone sdk

i created a new project with a tableview already done by default with the add button which add dates. But the project is im not familiar with the nsmanagedobject thing. I want to add specific string to this not dates. thx for help!!
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
It sounds like you need to learn about Core Data. I would also recommend Core Data: Apple's API for Persisting Data on Mac OS X by Marcus S. Zarra.
To display a string, rather than a date, you need to modify the data model. The template created a data model with a sigle Event entity that has a data attribute.
You can remove or rename this entity and modify the attributes as desired. You want to add a string attribute.
Then, you will want to specify the name of your new string attribute as the key for sorting in the fetch. You will also want to use it to set the value of the text in the table view cell's text label. Finally, you will need to modify the insert new object method to set a default value for the new attribute.
Looks like you've started a project using Core-Data. If you don't know what Core-Data is or how to use it, you can find out more here:
http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreData/CoreData.pdf