I'm using an UIManagedDocument in my project and have to change the relationship of existing objects.
All objects already exist in the database so I'm not dealing with the temporaryID/permanentID issue. I'm also almost using updateChangeCount: on my UIManagedDocument to save changes. So it shouldn't be an issue with the UIManagedDocument's usual suspects.
Model:
Company
|-- Department
|-- Employee
Operation:
Move a Department from one Company to another by calling [aDepartment setCompany:newCompany].
Situation 1 (succeeds):
Execute a fetchRequest on entity Department calling company = newCompany returns the expected result.
Situation 2 (fails):
Execute a fetchRequest on entity Employee calling department.company = newCompany returns no result.
Situation 2 only succeeds after UIManagedDocument auto-saves.
Any ideas how to solve this problem?
After dealing with this issue for quite a long time - unfortunately even Apple engineers weren't able to help - I was able to find a solution.
Calling [myManagedDocument saveToURL:fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil]; after changing the relationships solves the problem and returns the correct state immediately.
Anyway, I was filling a bug report for this to get this done via updateChangeCount:.
the same issue. I finally find a solution by saving both managedObjectContext and it's parent context.
NSError *error;
if ( ![self.fetchedResultsController.managedObjectContext save:&error] ||![self.fetchedResultsController.managedObjectContext.parentContext save:&error]) {
// .....
}
Related
I'm using .NET4.5/EF5 and have created the model from an existing database.
I'm using the following code:
Order currentOrder = new Order();
using (var db = new ILSEntities())
{
try
{
Event currentEvent = db.Events.OrderByDescending(u => u.EventID).FirstOrDefault();
currentOrder.Event = currentEvent;
db.Orders.Add(currentOrder);
db.SaveChanges();
And I'm seeing that a duplicate record is being created of the Event object I find, which is not what I wanted to happen.
I've read a lot of posts relating to similar problems, but where the context of the two participants in the foreign key relationships are different. Here, I'm saving with the same context I use to find one, and the other object is new.
I've also tried:
currentOrder.Event.EventID = currentEvent.EventID;
but that fails as well as I get an EF validation error telling me it needs values for the other members of the Event object.
I've also tried specifically setting the EntityState of the object being duplicated to Detached, Modified etc. after adding the Order object but before SaveChanges without success.
I'm sure this is a basic problem, but it's got me baffled
In my understanding, both parent and child objects have to be in the context before you assign any relationship between them to convince the entity framework that an entity exists in the database already. I guess you are trying to add new Order object to Database, to add new object you should be using AddObject method, Add() method is used to establish relation between entitties. In your code, currentOrder is not in the context. Try to hook it in the same context and then assign a relation. Your code should look like this :
Order currentOrder = new Order();
using (var db = new ILSEntities())
{
try
{
Event currentEvent = db.Events.OrderByDescending(u => u.EventID).FirstOrDefault();
db.Orders.Attach(currentOrder); //attach currentOrder to context as it was not loaded from the context
currentOrder.Events.Add(currentEvent);//establish relationship
db.ObjectStateManager.ChangeObjectState(currentOrder, EntityState.Added);
db.SaveChanges();
}
}
OK, I did in the end figure this out, and it was my fault.
The problem was that the Order object is FK'd into another table, Shipments, which is also FK'd into Events. The problem was that it was the Event reference in the Shipment object that was causing the new record. The solution was to let EF know about these relationships by adding them all within the same context.
The code assembling the object graph was spread over a number of webforms and the responses here made me take a step back and look at the whole thing critically so whilst no one of these answers is correct, I'm voting everybody who replied up
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.
I'm writing a chat app and I'm in the process of changing my db to use Core Data. I currently use sqlite directly but I want to take advantage of iCloud feature so I'm switching the engine.
My main table is called Entry with the following properties:
NSInteger type;
NSDate* timestamp;
NSString* username;
NSString* session;
NSString* body;
where 'type' can be:
1 - message
2 - file transfer (which then 'body' represents a file name in the documents folder)
3 - user joined
4 - user left
My app also supports multi-user chat (hence why the 'user joined'/'user left' types). All messages belong to the same conversation (multi-chat only), will have a valid 'session' property.
In my chat history, my problem is how to achieve the 'load more' like Apple did in the SMS app: I will query based on 'username=%# AND session IS NULL' or 'session=%#' to show that history and use a LIMIT of 50 sorted by reversed 'timestamp'.
I then want to have a button "Load more" which will load the next 50 messages - I'm not sure how to do it with Core Data.
My next question is how to show the list of conversations. Right now with raw sqlite, I perform a join on 2 queries: the first is the last message of each user and the second is the last message of each multi-user conversation. I then sort them all by date.
Since Core Data does not support joins, I'm not sure how to perform this query.
Thanks
Having an app that does exactly the same thing, here are my insights.
First of all you should consider coredata and multithreading wisely before coding. If you need help on that let me know.
The model
You are working with entities in Coredata, which can be considered like tables in sqlite, but in a more abstract way. You should review Apple's documentation for that.
We can find at least three different entities in your case : User, Conversation, and Message. (be careful with the last one, I had an issue with the entity called Message when importing the SMS Framework, you should consider prefixing the name of the entity..)
An issue with coredata is that you can not store directly arrays (may be with some unknown type) but anyway. So two solutions to store your users : either in a NSString when they will be delimited by comas and a simple regex or split will give you the number of users..
so your model could look like :
Conversation{
messages<-->>Message.conversation
lastMessage<-->Message.whateverName
//optional
users<<-->>User.conversation
}
Message{
conversation<<-->Conversation.messages
whatevername<-->Conversation.lastmessage // "whatever as it does not really matter"
}
User{
conversations<<-->>Conversation.users
}
Conversation must have an to-many relationship to Message and Message a to-one relationship to Conversation.
--EDIT
If you want to display the last message of a conversation just like the message App (or my app), you can add one relationship with message. It won't store the message twice in the database/coredata. Indeed, you create a coredata object (in this case a message) and that you add it to a conversation, what happen inside is that a conversation store the coredata ID for that object. Adding one relationship for this message (lastMessage) will only store another ID, and not another object.
--end of EDIT
Users are slightly different because they can be part of multiple conversations (because of the group conversation) that is why you need a many-to-many relation ship.
You can add as many attributes as you want, but that's the minimal requirement !
Implementation
then in your code, if you want to mimic the behavior of iMessage, here is what I did :
in the first controller, where you can see all the conversation : use a NSFetchedResultController. The query should be only about the entity Conversation.
When clicking on a row, what I did is that the new view has the conversation object and another NSFtechedResultController. I then query only the entity Message but with a predicate specifying that I only want this conversation.
If you want to check my app to see the fluidity, go to this link.
EDIT
Code snippet to find the last Message Of A Conversation
Beware: This is a temporary answer before finding a better way to do it (i.e. when using fetched properties)
NSFetchRequest * req = [[NSFetchRequest alloc] init];
[req setEntity:[NSEntityDescription entityForName:#"Message" inManagedObjectContext:context]];
[req setPredicate:[NSPredicate predicateWithFormat:#"conversation == %#", self]]; /* did that from a Conversation object.. */
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"sent_date" ascending:NO];
[req setSortDescriptors:[NSArray arrayWithObject:sort]];
[sort release];
NSError * error = nil;
NSArray * messages = [context executeFetchRequest:req error:&error];
[req release];
if ([messages count] > 0) { /* sanity check */
return [messages objectAtIndex:0];
}
return nil;
--end of EDIT
Hope this help !
Pierre
First, your mental model is all wrong. You should not think of core data as a SQL database. Yes, most of the time it uses SQL, but it is merely an implementation detail. You should think in terms of object graphs.
Next, for your "50 items" issue, look at NSFetchRequest. You can tell it where to start (fetchOffset), and how many items to fetch (fetchLimit). There are other options for you as well. If your total number of items is relatively small, you can just fetch the entire array (and only fault so many at a time - see fetchBatchSize).
For your "join" consider how objects are related to each other, not database table joins. Unfortunately, I do not understand what you are trying to achieve with that part of the question. However, you can mimic "joined" tables by using the dot notation when forming your predicate.
EDIT
When you create a conversation object, you can include a to-many relationship to something like "participants" which would be a set of all the users that participated in that conversation. The inverse would also be a to-many relationship in "user" that contained all the conversations that user participated in (I assume your database has multiple users???).
So, to get all the conversations in which a particular user participated, you could do something like fetch on "Participant" with a predicate similar to "ALL participants.username = %#"
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.
I need to edit a row of data in an Entity that has a relationship with my main Entity from my fetchedResultsController, in this case "theUser" being an instance of my User entity.
I basically need to edit one of the CannedMessage rows that already exist and save it. I can access the "Messages" fine as you see below, but am unsure once I have found the CannedMessage I want as to how I save it back into the managedObjectContext for "theUser"
Any advice?
NSArray *msgs = [theUser.Messages allObjects];
NSPredicate *activeMatch = [NSPredicate predicateWithFormat:#"defaultMessage == 1"];
NSArray *matched = [msgs filteredArrayUsingPredicate:activeMatch];
CannedMessage *msgToEdit;
for(CannedMessage *msg in matched) {
msgToEdit = msg;
}
Your trouble is that your thinking in SQL terms instead of Core Data's object oriented terms. The data you are looking for is not in an SQL row but in the attribute of a managed object. In this case (I assume) you are looking for an attribute of a CannedMessage instance.
The matched array will contain either managed objects initialized with the CannedMessage entity or an instance of a dedicated NSManagedObject subclass (if you setup one which it looks like you did.)
Lets say the attribute is named theMsg. To access the attribute in the generic managed objects:
for(CannedMessage *msg in matched) {
msgToEdit = [msg valueForKey:#"theMsg"];
}
... to access a custom class:
for(CannedMessage *msg in matched) {
msgToEdit = msg.theMsg;
}
It's really important when learning Core Data to simply forget everything you know about SQL. Nothing about SQL truly translates into Core Data. Core Data is not an object-oriented wrapped around SQL. Entities are not tables, relationships are not link tables or joins, attributes are not columns and values are not rows. Instead, Core Data creates objects just like you would if you manually wrote a custom class to model a real world object, event or condition. Core Data uses SQL almost as an after thought as one of its many persistence options.
In my experience, the more you know about SQL, the harder it is to shift gears to Core Data and other object graph APIs. You want to translate the new stuff to what you have already mastered. It is natural but resist the urge.