Coredata entity with exactly one row - ios

One of my coredata tables (uh, entities) should have ever only one row of data being stored. When the row doesn't exist yet it should be created and if it already exists, the same row should be overwritten (or edited) with new data.
Currently in my implementation a new row is always added to the entity (named 'TempNames'):
/* Store names data in temporary name table. */
TempNames *tempNames = [NSEntityDescription insertNewObjectForEntityForName:#"TempNames" inManagedObjectContext:context];
tempNames.namesData = tempNamesData;
Can anyone give me some hints what is needed to change it to my desired functionality? I suppose NSPredicate is required to achieve what I want?
UPDATED WORKING IMPLEMENTATION:
/* Convert names array into serializable data. */
NSData *tempNamesData = [NSKeyedArchiver archivedDataWithRootObject:names];
/* Store names data in temporary name table. */
TempNames *tempNames = nil;
NSError *error;
NSFetchRequest *req = [NSFetchRequest fetchRequestWithEntityName:#"TempNames"];
NSArray *records = [context executeFetchRequest:req error:&error];
/* Record already exists. */
if (records.count > 0)
{
tempNames = records.firstObject;
}
else
{
tempNames = [NSEntityDescription insertNewObjectForEntityForName:#"TempNames" inManagedObjectContext:context];
}
tempNames.namesData = tempNamesData;
[context save:&error];

If you insert a new entity every time, you are not doing an insert/update operation, it's always an insert operation, even if the data of that entity is the same. What you should do is first fetch if you have that data in your store and then decide if you need to insert or update it.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"TempNames"];
NSError *error;
NSArray *entities = [context executeFetchRequest:request error:&error];
if (entities.count > 0) {
// You have already inserted the entity
}
else {
// Insert your new entity
...
// Save changes to the store
NSError *error;
[context save:&error];
}

Try it like this:
TempNames *tempNames = nil;
NSFetchRequest *appRequest = [NSFetchRequest fetchRequestWithEntityName:#"TempNames"];
NSArray *allNames = [context executeFetchRequest:appRequest error:nil];
if (allNames.count > 0) {
// your record exists
tempNames = allNames.firstObject;
} else {
tempNames = [NSEntityDescription insertNewObjectForEntityForName:#"TempNames" inManagedObjectContext:context];
}
tempNames.namesData = tempNamesData;

Related

get last inserted NSManagedObject from CoreData

This how NSManagedObject is created
NSEntityDescription *entity = [NSEntityDescription entityForName:strEntityName inManagedObjectContext:managedObjContext];
NSManagedObject * managedObject = (NSManagedObject *)[[NSClassFromString(strEntityName) alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjContext];
//values are mapped into this object
Now save NSManagedObject to persistent store and fetching currently inserted object like this :
NSError *error;
BOOL isDone = [managedObjectContext save:&error];
//BOOL isDone = [managedObjectContext obtainPermanentIDsForObjects:[NSArray arrayWithObjects:tempManagedObject, nil] error:&error];
if (isDone && error == nil){
//fetch last inserted object here
//make fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:strEntityName];
//make query using fetch request in context
NSError *error;
NSArray *arrFetchRequest = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (arrFetchRequest.count>0){
//This is last inserted object
NSManagedObject *managedObject = [arrFetchRequest lastObject];
return managedObject;
}
}
I have also refered Swift - How to get last insert id in Core Data, store it in NSUserDefault and predicate. But it will not have permanent object ID in NSManagedObject as we are saving temparory ID and fetching using that and we don't have permanent ID at all.
Canyone share any other options?
While inserting NSManagedObject in context, it will have temporaryID (this means it has not been saved to database).
So need to get permanentID (this means it has been saved to database) for NSManagedObject in context, use obtainPermanentIDsForObjects and same NSManagedObject will have permanentID
if (insertManagedObject)
{
NSLog(#"Before %# : %d",insertManagedObject.objectID,insertManagedObject.objectID.isTemporaryID);
//Obtain permanentID for object
NSError *error;
BOOL hasObtainedPermanentID = [managedObjectContext obtainPermanentIDsForObjects:[NSArray arrayWithObjects:insertManagedObject, nil] error:&error]; //;
//check if its done
if (hasObtainedPermanentID && error == nil){
NSLog(#"After %# : %d",insertManagedObject.objectID,insertManagedObject.objectID.isTemporaryID);
//check context has changes and is saved in context
if ([managedObjectContext hasChanges] && [managedObjectContext save:&error])
{
return insertManagedObject;
}
}
}

How to update transformable attribute in core data

I am in a situation where i need to update transformable attribute in my entity in core data, until now i've tried every possible answer from google and stack overflow but did't achieve anything.
This is the method where i am saving object in core data, and my object which i am saving is an NSMutablDictionary type object.
-(void)didSaveToCoreData :(NSMutableDictionary *)newDict
{
#try {
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext ;
DataModelSupport *entity = [NSEntityDescription insertNewObjectForEntityForName:#"CPIEntity" inManagedObjectContext:context];
if (newDict != nil) {
[entity.fixed_Model removeAllObjects];
entity.fixed_Model = newDict;
}
NSError *error ;
[context save:&error];
if(error)
{
NSLog(#"Error in Saving Data");
}
else
{
[self didFetchFromCoreDataModel];
NSLog(#"Successfully saved");
}
}
#catch (NSException *exception) {
[self spareMeFromTheCrash:exception];
}
#finally {
}
}
in this method i am saving a dictionary object of 19 key/value, at the first time and i am fetching it correctly in didFetchFromCoreDataModel method, but when i refresh the data and get dictionary of 18 key/value i save that dictionary in core data using the same method didSaveToCoreData and fetch it in the same way from didFetchFromCoreDataModel but it still show 19 key/value
DataModelSupport is a subclass of NSManagedObject.
In DataModelSupport.h:
#property (nonatomic,weak) NSMutableDictionary *fixed_Model;
In DataModelSupport.m:
#dynamic fixed_Model;
This is it for the DataModelSupport class.
Now here in this method i am fetching the same object form core data
-(void)didFetchFromCoreDataModel
{
#try {
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext ;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CPIEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setReturnsDistinctResults:YES];
[request setReturnsObjectsAsFaults:NO];
[request setResultType:NSDictionaryResultType];
[request setEntity:entity];
NSError *error ;
NSArray *arr = [context executeFetchRequest:request error:&error];
updatedfinalArr = [arr valueForKey:#"fixed_Model"];
if(error)
{
NSLog(#"Error");
}
else
{
}
}
#catch (NSException *exception) {
[self spareMeFromTheCrash:exception];
}
#finally {
}
}
And this is how my core data looks like:-
Any help is appreciated.
EDIT
I've implemented some changes in my code now in didSaveToCoreData method i am using this line of code to fetch the Entity by name
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"CPIEntity" inManagedObjectContext:context];
by this i am not creating new entity every time i call didSaveToCoreData method.
and this is how i am saving NSMutlableDictionary object
DataModelSupport *entity = [[DataModelSupport alloc]initWithEntity:descriptor insertIntoManagedObjectContext:context];
[entity.fixed_Model removeAllObjects]
entity.fixed_Model = newDict;
but still i am not getting correct result.
now when i refresh the data and save it using the above procedure explained in EDIT section, and fetch it, i get the updated data but it increase the number of objects, like on first attempt when i fetch i got 1 object in array, and on second attempt i got 2 objects and it goes like this, so when ever new data is added its not updating it but instead it add it in the entity s fixed_Model attribute and increase the number of object.
Lastly now i am using this line of code to get the last and update object from array in didFetchFromCoreDataModel method
NSDictionary *ddd = [[arr valueForKey:#"fixed_Model"]lastObject];
updatedfinalArr = [NSMutableArray arrayWithObject:ddd];
Your save method creates a new CPIEntity object each time. So, unless you delete the old object elsewhere in your code, I suspect your fetch is returning several objects, the first of which has the dictionary with 19 key/value pairs in the fixed_Model attribute, and the second/subsequent objects contain the 18 key/value pairs.
When you save, you should try to fetch the existing object first, and if you get zero results then create a new object. Then set the fixed_Model attribute of the new/existing object to your new dictionary.
EDIT
You are still inserting a new object each time (DataModelSupport *entity = [[DataModelSupport alloc]initWithEntity:descriptor insertIntoManagedObjectContext:context];). See below for an example of "fetch or create":
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext ;
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"CPIEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *results = [context executeFetchRequest:request error:&error];
if (results == nil) {
// This implies an error has occurred.
NSLog(#"Error from Core Data: %#", error);
} else {
if (results.count == 0) {
// No objects saved, create a new one...
DataModelSupport *entity = [[DataModelSupport alloc]initWithEntity:descriptor insertIntoManagedObjectContext:context];
entity.fixed_Model = newDict;
} else {
// At least one object saved. There should be only one
// so use the first...
DataModelSupport *entity = [results firstObject];
entity.fixed_Model = newDict;
}
}
I've assumed for simplicity that newDict is not nil; amend as appropriate to handle that case.
Can you narrow down the problem?
Ie. can you compare the two Dictionaries..the original one with 19 values and the new one with 18 values?
Is there a particular entry which is not being 'removed'? That might point to a challenge with 'delete' (or the lack there of).
Alternatively, if you completely replace the content, what result do you get on fetch?

array count returned from NSFetchRequest is wrong

I'm using Core Data to cache data, here is my code to insert object:
//data array count is 15
for (NSDictionary *dictionary in dataArray)
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CacheData" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title LIKE '%#'",dictionary[#"title"]];
[request setPredicate:predicate];
NSArray *fetchArray = [context executeFetchRequest:request error:NULL];
if ([fetchArray count] == 0)
{
CacheData *cacheData = [NSEntityDescription insertNewObjectForEntityForName:#"CacheData" inManagedObjectContext:context];
[cacheData setTitle:dictionary[#"title"]];
[cacheData setLink:dictionary[#"link"]];
[cacheData setPublishDate:dictionary[#"pubDate"]];
NSError *insertError = nil;
if (![context save:&insertError])
{
return NO;
}
}
}
The count of dataArray is 15, so I should insert 15 items to Core Data.
But once I used NSFetchRequest to fetch items, the array count returned added 1, and become 16, and fetch items again, it added 1 to 17 again:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CacheData" inManagedObjectContext:[[CacheDataManagement sharedInstance] managedObjectContext]];
[request setEntity:entity];
NSArray *fetchArray = [[[CacheDataManagement sharedInstance] managedObjectContext] executeFetchRequest:request error:NULL];
for (CacheData *data in fetchArray) {
NSLog(#"fetch:%#",[data title]);
}
NSLog(#"%ld",[fetchArray count]); //fetch array count is 16
Something wrong with my code ?
Update
Changed if ([fetchArray count] != 0) { … } to if ([fetchArray count] == 0) { … }
Since the problem it's not quite clear to me (I would like to have more details), I'll try to give you some hints.
First, the save should be done outside the for loop (In addition it's not correct to do a return within it).
// for in loop here
NSError *insertError = nil;
if (![context save:&insertError]) {
NSLog("%#", insertError);
return NO; // do it if the method you are running in returns a bool
}
Second, to check for duplicated you should rely on a GUID but if don't have it the predicate should look like the following.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title == %#",dictionary[#"title"]];
Third, also the execute for executeFetchRequest:error: should be replace with countForFetchRequest:error: since you don't need to return the objects but only a count. According to Apple doc, it
Returns the number of objects a given fetch request would have
returned if it had been passed to executeFetchRequest:error:.
Finally, in the for loop you are executing a request each time. My suggestion is to move execute a request before the loop and then checking for results within it. This according to Implementing Find-or-Create Efficiently. In this case, the pattern will enforce you to have a GUID for you entity.
Obviously, these are just hints. The real way to find the problem is to debug. In addition, I will perform tests starting from a fresh environment, i.e. the app has been deleted from the simulator or device.
In your code [fetchArray count] != 0 checks the inserting data is already exists. Try changing it like
if ([fetchArray count] == 0)
{
CacheData *cacheData = [NSEntityDescription insertNewObjectForEntityForName:#"CacheData" inManagedObjectContext:context];
[cacheData setTitle:dictionary[#"title"]];
[cacheData setLink:dictionary[#"link"]];
[cacheData setPublishDate:dictionary[#"pubDate"]];
NSError *insertError = nil;
if (![context save:&insertError])
{
return NO;
}
}

Querying Core Data for Specific Attributes when Creating New Objects and returning the object if it exists, or creating a new one if it does not

I have a problem checking whether a particular attribute of an Entity exists in the Core Data Database (through predicates) before creating a new object; if the object exists, I'd rather return it than create a new object.
I have a simple App which has a table view with a plus button in the Navigation Bar; the user clicks that and is presented with a View Controller with 4 text fields. They fill in that information, press save and it gets saved to Core Data and displayed in the TableView (through the use of NSFetchedResultsControllers).
The data model is as follows:
Transaction Entity with isReceived BOOL attribute
Person Entity with name string attribute
Occasion Entity with title string attribute
Item Entity with amount string attribute
The transaction has a relationship to the Person (whoBy), Occasion (Occasion) and Item entities.
In the view controller with the save method, I have the code below which will insert new objects into the Transaction, Person, Occasion Entities, etc. Each Transaction is of course unique, but with each transaction, the user can select an existing PERSON and/or Occasion and if that person then does not exist, it will be created (likewise with the occasion).
I'm slightly confused as to the format of the code here.
EDIT: I have tried a combination of code and can just not get this working. In the code below, I'm referencing person.name in the predicate, but I also tried creation a local NSString variable to hold the self.nameTextField.text code but that did nothing. I tried creating a NSString property to reference it that way and that not work. I tried using the words MATCHES, LIKE, CONTAINS, == and every combination in-between.
- (IBAction)save:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Transaction *transaction= [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
Occasion *occasion = [NSEntityDescription insertNewObjectForEntityForName:#"Occasion" inManagedObjectContext:context];
Item *amount = [NSEntityDescription insertNewObjectForEntityForName:#"item" inManagedObjectContext:context];
NSFetchRequest *personFind = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
personFind.predicate = [NSPredicate predicateWithFormat:#"name == %#", person.name];
// I have tried every combination of the predicate like MATCHES, LIKE.
// I created a local NSString variable and an NSString property
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
personFind.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:personFind error:&error];
if (!matches || ([matches count] > 1))
{
// Handle Error
}
else if ([matches count] == 0)
{
person.name = self.nameTextField.text;
transaction.whoBy = person;
occasion.title = self.occasionTextField.text;
transaction.occasion = occasion;
}
else
{
person = [matches lastObject];
transaction.whoBy = person;
occasion.title = self.occasionTextField.text
transaction.occasion = occasion;
}
if (![context save:&error])
{
NSLog(#"Can't save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
Logically, what I want to achieve is:
When the user is adding a Transaction, check if it's for a new person or an existing one — if it's an existing one, choose it from a list of Persons (and when the user selects a person, get its NSManagedObjectID). If it's a new one, create it on the spot.
The same for the Occasion.
Set all the other fields of the Transaction object (amount, etc.).
My question is:
What predicate do I use to get this working?
When I put a break point in this method, a NEW NAME (one that doesn't exist before) correctly calls the else if ([matches count] == 0) method and if I create an entry with an existing name, it calls the
else
{
person = [matches lastObject];
transaction.whoBy = person;
occasion.title = self.occasionTextField.text
transaction.occasion = occasion;
}
Even with the this statement, it is still creating a new person object for the same name.
I will correctly implement the occasion after getting the person working, but I'm just lost on how to get this working.
Any help would be massively appreciated!
"Is this correct?":
No. You are creating a new Person and Occasion objects whether you are using an existing person/occasion or not.
First check for existence and only if the object not already exist, insert a new one.
Alternatively, if the person/occasion exist, delete the inserted object.
"How do I retrieve the managedObjectID for person/event?":
Person* person = /*Get an existing person object*/
NSManagedObjectID* personId = person.objectID /*This is the person object ID, will work for any NSManagedObject subclass*/
To find a person that start with a string str use this predicate in a fetch request:
/*UNTESTED*/
[NSPredicate predicateWithFormat:#"(name BEGINSWITH[cd] %#)", str];
Edit:
To be more precise, you practice find or create using something like this:
(this is very limited, and only good for a single object performance-wise)
(NOT TESTED)
- (NSManagedObject*) findOrCreateObjectByValue:(id)value
propertyName:(NSString*)propertyName
entityName:(NSString*)entityName
additionalInfo:(NSDictionary*)additionalInfo
context:(NSManagedObjectContext*)context
error:(NSError* __autoreleasing*)error
{
NSManagedObject* res = nil;
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:entityName];
[r setPredicate:[NSPredicate predicateWithFormat:#"%K == %#",propertyName,value]];
NSArray* matched = [context executeFetchRequest:r
error:error];
if (matched) {
if ([matched count] < 2) {
res = [matched lastObject];
if (!res) { //No existing objects found, create one
res = [NSEntityDescription insertNewObjectForEntityForName:entityName
inManagedObjectContext:context];
[res setValue:value
forKey:propertyName];
[res setValuesForKeysWithDictionary:additionalInfo];
}
} else {
if (error) {
*error = [NSError errorWithDomain:#"some_domain"
code:9999
userInfo:#{#"description" : #"duplicates found"}];
}
}
}
return res;
}
So now, your save: method should look something like:
(I assume here that the person name and occasion title are held by a UITextField on the view controller [txtPersonName and txtOccasionTitle respectively] )
- (void) save:(id)sender
{
//create a clean context so that changes could be discarded automatically on failure
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setParentContext:[self managedObjectContext]];
//A Transaction is always created in save event, so add it to the context
Transaction* transaction = [NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:context];
__block NSError* error = nil;
Person* p = (Person*)[self findOrCreateObjectByValue:self.txtPersonName.text
propertyName:#"name"
entityName:#"Person"
additionalInfo:nil
context:context
error:&error];
if (!p) {
NSLog(#"Error: %#, person name: %#",error,self.txtPersonName.text);
return;
}
Occasion* o = (Occasion*)[self findOrCreateObjectByValue:self.txtOccasionTitle.text
propertyName:#"title"
entityName:#"Occasion"
additionalInfo:nil
context:context
error:&error];
if (!o) {
NSLog(#"Error: %#, occasion title: %#",error,self.txtOccasionTitle.text);
return;
}
transaction.whoBy = p;
transaction.occasion = o;
//Not sure what you are using this property for
transaction.item = [NSEntityDescription insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:context];
NSManagedObjectContext* ctx = context;
if ([context obtainPermanentIDsForObjects:[context.insertedObjects allObjects]
error:&error])
{
//save your changes to the store
__block BOOL saveSuccess = YES;
while (ctx && saveSuccess) {
[ctx performBlockAndWait:^{
saveSuccess = [ctx save:&error];
}];
ctx = [ctx parentContext];
}
if (!saveSuccess) {
NSLog(#"Could not save transaction, error: %#",error);
}
} else {
NSLog(#"Could not obtain IDs for inserted objects, error: %#",error);
}
//Do what you have to do next
}
This is just for making things a bit clearer on what you should do to avoid duplications, and reuse existing objects.

how to edit coredata value then save context to overwrite old values?

I am trying to edit a item in one of my coredata tables/entities. I am not 100% sure how to do this but I think its along these lines.
First you create the context, then fetchrequest the entity using a predicate to select the exact item from the table/entity. then save these values into the correct var type update the values then some how overwrite the existing item with the new values.
The last part is where I am stuck, this is the code I currently have:
- (void)editSelectedInstall:(NSString *)invGuid {
NSManagedObjectContext *context = [self managedObjectContext];
if (context == nil) {
NSLog(#"Nil");
}
else {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Install" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"invGUID==%#",invGuid];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *myArray = [context executeFetchRequest:fetchRequest error:&error];
NSMutableDictionary *myDictionary = [myArray objectAtIndex:0];
NSLog(#"%#", myDictionary);
// Here I will update the values & then save the context?
// For example I think I am to update the items like this:
myDictionary.num = #"1234";
myDictionary.name = #"new name";
}
}
I think I'm almost there I just need help saving the context so that it overwrites the previous values.
Try this:
//loop through result set and update as required
for (Install *temp in myArray) {
temp.num = #"1234";
temp.name = #"New name";
}
//save
[context save:&error];

Resources