Why does this code raise the value "CoreData: error: (19) PRIMARY KEY should be a unique" error "?

This code causes the error "CoreData: error: (19) PRIMARY KEY must be unique." The Day object has only the when attribute, which is an NSDate and a to-many relation called tasks . Why is this a mistake? If a Day with a certain date is already saved, I retrieve it, otherwise I insert it. Thus, for each object of the day there should be a different when attribute. I am not sure if this is the primary key. How to solve this? Thanks 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; } 
+6
source share
2 answers

I think you do not need to insert a new Day if you already have one (this is the case when the day is not equal to zero). In particular, I mean [moc insertObject:day] .

If you use insertNewObjectForEntityForName , this method inserts an object for you when you save the moc. If you need to change it (you received a non-negligible day), change it and save. In addition, I will do processPendingChanges when the loop ends (for performance reasons only).

Hope this helps.

+7
source

I recently struggled with a CoreData: error: (19) PRIMARY KEY must be unique using my own iOS application and, fortunately, I found a solution in a day or two of research and code modifications, and I wanted to share my findings here in the hope that they will help others.

First a bit of background

Our own application was never originally built with any code to update our CoreData repository - instead, for each iteration of the application assembly, when new data was to be displayed in the application, the CoreData SQLite repository file was simply replaced by a new version that was edited by the standard SQLite editor on the desktop - this is a raw SQLite file, has been modified and updated as needed. Although it is recognized that this approach does not take into account the nature of the β€œblack box” of CoreData, in fact it really helped us well with our simple application over the past few years, and the application happily accepted the updated SQLite file with each new application.

Recently, however, we have carried out an overhaul of the application, abandoning the model of updating application assets and data using Xcode and application restructuring, to the model of receiving new application data through the web service instead, and this was during this new development in which PRIMARY KEY must be unique problem.

After several days of trying to solve this problem, believing that there was some kind of error in the new code to create the CoreData entity (which was carefully checked and double-checked), I found that I was able to enable CoreData SQL debugging for our application using Xcode (following the instructions in this SO post is very useful).

When I carefully examined the SQL logs in Xcode, I could see that before every call that CoreData made for INSERT new record in the SQLite backup storage, this structure requested a table Z_PRIMARYKEY with the following SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ? query SELECT Z_MAX FROM Z_PRIMARYKEY WHERE Z_ENT = ? where ? replaced behind the scenes with the corresponding Z_ENT value for the corresponding CoreData object (you can see the Z_ENT value for each of your CoreData objects by viewing the contents of the Z_PRIMARYKEY table). This is when I finally realized what was happening in the CoreData black box! Then I looked in more detail at our SQLite application file using the Liya application on a Mac, and I looked at the contents of the Z_PRIMARYKEY table, and, of course, the values ​​of the Z_MAX column were set to 0 . They never changed from their original values ​​when CoreData first generated an empty SQLite repository file for our application!

I immediately realized what was going wrong, and only why CoreData reported a primary key error, as it were - in fact, it was not associated with any attribute of a higher-level CoreData object object, somehow, as originally suspected but really a lower level error. Only now the mistake made an absolute sense, it was there all the time, it simply was not understood in the right context.

As it turned out from further research, over the past few years, our own team has successfully made direct changes to the SQLite backup store, inserting, updating, and deleting records from different entity tables, BUT our team never made any changes to the Z_PRIMARYKEY table, and Because our application used CoreData in read-only mode, this has never been a problem.

However, now that our application was trying to create and save CoreData records back to the SQLite backup storage, many of the INSERT queries that were generated as a result simply failed because CoreData was not able to get the correct maximum primary key value from which the next sequential will be generated the primary key in any given table, and as such the INSERT query will fail with the understandable PRIMARY KEY must be unique error PRIMARY KEY must be unique !

Error solution

I understand that every developer can generate standard CoreData content for their applications in different ways in different ways, however, if you have ever encountered this specific error in CoreData, and also struggled to find a clear answer right away, I hope The following tips will help:

  • Our CoreData backup storage was manually updated by editing the SQLite file directly (inserting, updating, and deleting records from entity tables) - this approach worked for a long time because of the read-only method, we consumed this data in our application. However, this approach could not truly recognize the abstract type of the black box of CoreData and the fact that SQLite records are not equivalent to CoreData objects and that SQLite connections are not equivalent to CoreData relations, etc.

  • If your application uses any type of full CoreData storage, and if you populate the contents of your SQLite backup storage in any way other than through CoreData, make sure that if you create new entries in any of your CoreData entity tables, you also guarantee that You will update the corresponding entry in the Z_PRIMARYKEY table by setting the value of the Z_MAX column to the current maximum Z_PK value in the corresponding entity table.

    For example, if you have a CoreData object called Employee , in your CoreData SQLite persistent store backing file will be represented by a table called ZEMPLOYEE - this table will contain some "hidden" CoreData columns, including Z_PK , Z_ENT , Z_OPT , etc., in addition to columns that represent attributes and relationships. This entity table will also have a corresponding entry in the Z_PRIMARYKEY table with the Z_NAME value of Employee - this way, when you add a new record directly to the ZEMPLOYEE table - make sure that after you finish adding entries, you look at each entity table and copy the maximum Z_PK value in the Z_MAX column of the Z_MAX table. The value you enter in Z_MAX should be the largest Z_PK value in the corresponding table; do not set Z_MAX to Z_PK + 1, as this will not be what CoreData expects!

You can query 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 highest Z_PK for any entry in the ZEMPLOYEE table - obviously, you should replace ZEMPLOYEE with the appropriate table name for your application data model.

For our application, I was able to write a simple command line script that directly reads the SQLite file, iterating over each record in 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 a backup application storage support file. Now when I insert and save new CoreData objects, everything works as expected.

Now that our application has the ability to directly request new data through a web service, we will completely move away from manually editing SQLite backup storage and exclusively using CoreData frameworks to update data, but temporarily or to another application where such quick and easy input data may be required, I hope this answer helps other developers and saves them from the time and effort that it took me to find this solution.

+19
source

Source: https://habr.com/ru/post/925671/


All Articles