Core Data attribute uniqueness - objective-c

Let's suppose that I download data from a remote web service. To be more precise I download a list of news from a web service, each news have a newsID that we can consider a primary key for the service so we won't find two equal newsID.
How can I can be sure that i insert only the data with a newsID that doesn't already exist?
I've draft my solution with this method:
- (BOOL)validateNewsID:(NSNumber **)newsID error:(NSError **)error
{
NSError *countError = nil;
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[self entity]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"newsID == %#", *newsID];
[fetchRequest setPredicate:predicate];
NSUInteger resultCount = [SharedManagedObjectContext countForFetchRequest:fetchRequest error:&countError];
if (countError) {
OLog(countError);
*error = countError;
return NO;
}
if (resultCount > 0) {
NSString *errorString = #"NewsID should be unique";
NSDictionary *userInfoDict = [NSDictionary dictionaryWithObject:errorString
forKey:NSLocalizedDescriptionKey];
NSError *uniquenessError = [[[NSError alloc] initWithDomain:kNewsValidationDomain
code:kNewsValidationUniquenessCode
userInfo:userInfoDict] autorelease];
*error = uniquenessError;
return NO;
}
return YES;
}
but doesn't works as expect because, I suppose, when i perform the fetch request i find my same object previously insert in the context. Am I wrong?
How can i fix it?

Seems like you would find the same object you're trying to validate. Try one of these:
Execute the fetch request and remove the current object from the result array before checking for other entries.
Adjust the predicate so it excludes the current object, if possible.
Change resultCount > 0 to resultCount > 1.

Related

Core Data update does not save and does not give any error, why?

I am using a very simple piece of code to update an NSManagedObject, but the save does not make it to the persistent store (SQLite). There are no error messages and the logs look ok so I am a little lost.
I have scaled down the code as much as possible to try and isolate the problem as shown below.
The logs tell me that the orderNumber and status are set correctly and also the debug output of the arrays are all correct, but my simple update still fails without any error at all.
+ (int) synchOrderWithStatusUpdate:(NSString *)orderNumber : (int)status {
if ([NWTillHelper isDebug] == 1) {
NSNumber *statusCheck = [NSNumber numberWithInt:status];
NSLog(#"WebServices:synchOrderWithStatusUpdate:ordernumber = %#, status = %d, status2 = %#", orderNumber, status, statusCheck);
}
synchOrderErrorCode = 0;
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = appDelegate.persistentContainer.viewContext;
// *** The query for all tables uses orderNumber as selection so we set that up first for re use
NSPredicate *predicateOrderNumber =[NSPredicate predicateWithFormat:#"orderNumber like[cd] %#", [NWTillHelper getCurrentOrderNumber]];
NSFetchRequest *fetchRequestOh = [[NSFetchRequest alloc] initWithEntityName:#"OrderHead"];
NSFetchRequest *fetchRequestOrp = [[NSFetchRequest alloc] initWithEntityName:#"OrderRow"];
NSFetchRequest *fetchRequestTender = [[NSFetchRequest alloc] initWithEntityName:#"Tender"];
fetchRequestOh.predicate = predicateOrderNumber;
fetchRequestOrp.predicate = predicateOrderNumber;
fetchRequestTender.predicate = predicateOrderNumber;
fetchRequestOh.resultType = NSDictionaryResultType;
fetchRequestOrp.resultType = NSDictionaryResultType;
fetchRequestTender.resultType = NSDictionaryResultType;
NSError *errorOh = nil;
NSMutableArray *orderHeads = [[context executeFetchRequest:fetchRequestOh error:&errorOh] mutableCopy];
NSError *errorOrp = nil;
NSArray *orderRows = [[context executeFetchRequest:fetchRequestOrp error:&errorOrp] mutableCopy];
NSError *errorTender = nil;
NSArray *tenderRows = [[context executeFetchRequest:fetchRequestTender error:&errorTender] mutableCopy];
if ([NWTillHelper isDebug] == 1) {
NSLog(#"WebServices:synchOrderWithStatusUpdate:orderHeadsArray: %#", [orderHeads objectAtIndex:0]);
NSLog(#"WebServices:synchOrderWithStatusUpdate:orderRowsArray: %#", orderRows);
NSLog(#"WebServices:synchOrderWithStatusUpdate:tenderRowsArray: %#", tenderRows);
}
// *** Set the status before upload since this dictates what will happen in backend
// *** regardless if synch is successful or not
NSManagedObject *orderHeadObject = nil;
orderHeadObject = [orderHeads objectAtIndex:0];
[orderHeadObject setValue:[NSNumber numberWithInt:status] forKey:#"status"];
// Save the objects to persistent store
NSError *error = Nil;
[context save:&error];
NSLog(#"Jongel Error = %#", error);
if(error !=nil) {
if([NWTillHelper isDebug] == 1) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
synchOrderErrorCode = 99;
}
return 10101;
The orderHeads are fetched as dictionaries. The NSManagedObjectContext will not be aware of any changes made to a dictionary. Only NSManagedObjects will be managed by the NSManagedObjectContext, hence the name ;-)
Try to fetch the orderHeadObject as an NSManagedObject as this will allow you to save the changes back into the store. If you need the dictionary for JSON serialisation: Just fetch it again as a dictionary after you have made the changes. That second fetch will be very fast since all values of the object are already cached, so CoreData won't have to reload from the database.

Core data delete single record from to many relation ship

I have created 4 entities.
User, Mobile, Email, Address.
User have to many relationship (name as numbers) with Mobile.
I have subclass for all of above. How to delete single mobile number from particular user? How to update single mobile number from particular user?,what Predicate use for that?
code is here:
NSManagedObjectContext *context = [self managedObjectContext]; NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:NSStringFromClass([User class])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY numbers.number == %#",self.textField.text];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error]; Mobile *objMobile;
User *objUser;
objUser = [array objectAtIndex:0];
for (User *obj in [objUser.numbers valueForKey:#"number"]) {
NSLog(#"%#",obj);
NSString *str1 = [NSString stringWithFormat:#"%#",obj];
NSString *str2 = [NSString stringWithFormat:#"%#",self.textDelete.text];
if ([str1 isEqualToString:str2] ) {
[objUser removeNumbersObject:objMobile]; }
}
NSLog(#"%#",[objUser.numbers valueForKey:#"number"]); self.textAddUserDetail.text = #"";
if (![context save:&error]) { }
Basically, don't search based on the user and the relationship. Instead, search directly for the number and then delete it or edit it:
NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:NSStringFromClass([Number class])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"number == %#", self.textField.text];
Then you also don't need a loop and extra consideration of what to do.
It will be useful to use "Magical Records" Library.it work with Key value coding need not to maintain Relationships.it will Automatically get the unique Key From all the Entitys

Address of entity used in fetch request does not match address of entity in relationship when it should

I have a method for destroying outdated NSManagedObjects called messages:
+ (void)syncMessagesByIDs:(NSArray *)messageIDs toGroup:(Group *)group context:(NSManagedObjectContext *)context
{
// This is getting all the Messages that still have a relationship to this Group but
// are not in our passed in set of messages and therefor should be removed
// from the Group.
NSPredicate *searchTerm = [NSPredicate predicateWithFormat:#"(%# IN %K) && NOT (%K in %#)", group, #"groups", #"id_number", messageIDs];
NSArray *results = [Message fetchManyWithPredicate:searchTerm context:context];
for (Message *message in results) {
DLog(#"message.groups %d", message.groups.count);
[message removeGroupsObject:group];
if (message.groups.count == 0) {
[context deleteObject:message];
}
}
}
+ (NSArray *)fetchManyWithPredicate:(NSPredicate *)predicate context:(NSManagedObjectContext *)context
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:context];
fetchRequest.predicate = predicate;
fetchRequest.returnsObjectsAsFaults = NO;
NSArray *results = nil;
NSError *error = nil;
results = [context executeFetchRequest:fetchRequest error:&error];
if (error) {
// TODO: Handle error.
}
[fetchRequest release];
return results;
}
Oddly, I have results returned for my fetch. This implies that there are a set of messages that have a relationship to the group which I used for the query BUT do not have an id_number in the array of messageIDs.
However, when I print message.groups from the console I get 0x1f83c740 as the address of the group that matches the group in my query, but the group in my query has an address of 0x1fe99db0, two different addresses.
Because the addresses of the group in my query and the group in the relationship are different my call to [message removeGroupsObject:group] does nothing.
Why could this be happening?
More Info:
If it helps, this call is made in a background thread with a context of NSPrivateQueueConcurrencyType.
The following code also returns a group with a different address than both of the other groups previously shown.
searchTerm = [NSPredicate predicateWithFormat:#"SELF == %#", group];
NSArray *groupResults = [RDGroup fetchManyWithPredicate:searchTerm context:context];
DLog(#"groupsResults: %#", groupResults);
I'm getting the same group with a different address every time.
Wups! I looked down the call stack and just realized my Group entity was never created in the thread of the context I'm using for the fetch request. I needed to pass in the objectID of the Group and create a new Group in the correct context.
I'm curious to why an exception wasn't thrown for accessing an entity outside of the context that owns it though.
Hope this helps anyone else in the future!

Core Data not updating, updated model

I have a very weird problem that has stumped me rather!
I have a core data entity that i have just added some new attributes to:
deleted - Boolean
deletedDate - Date
I have the following code, that upon pressing sets both those values on the core data object:
- (IBAction)deleteButtonInTable:(id)sender {
//Get the ID of the currently selected item in the table
NSInteger selected = [self.tweetTableView rowForView:sender];
//Create a predicate and fetch the objects from Core Data
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *testForTrue = [NSPredicate predicateWithFormat:#"approved == NO"];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"postDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[request setPredicate:testForTrue];
[request setSortDescriptors:sortDescriptors];
[request setEntity:[NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:_managedObjectContext]];
//Setup the Request
[request setEntity:[NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:_managedObjectContext]];
//Assign the predicate to the fetch request
NSError *error = nil;
//Create an array from the returned objects
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:request error:&error];
Tweet *selectedTweet = [fetchedObjects objectAtIndex:selected];
if (selectedTweet) {
selectedTweet.deleted = [NSNumber numberWithBool:TRUE];
selectedTweet.deletedDate = [NSDate date];
NSLog(#"%#",selectedTweet);
[self refreshTableView];
if (! self.tweetTableView){
NSLog(#"Tableview doesn't exist!!)");
}
[[self tweetTableView] reloadData];
[[self managedObjectContext] commitEditing];
[self saveAction:nil];
}
if ([self.autoWriteTweets isEqualToString:#"YES"]){
[self writeTweetsToXML];
[self saveAction:nil];
}
}
Now, if i watch the object in xcode with some breaks, i can see the attribute change on the object as i pass through the function, but i have an Table displaying a datasource, which is filtered to only show objects that have the deleted bool set to true, and nothing ever shows up there.
Now, to make things even more confusing i have a function that exports an array of the objects:
-(void)writeTweetsToXML{
//Create new fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//Set new predicate to only fetch tweets that have been favourited
NSPredicate *filterFavourite = [NSPredicate predicateWithFormat:#"approved == YES"];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:self.exportSort ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[request setSortDescriptors:sortDescriptors];
//Setup the Request
[request setEntity:[NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:_managedObjectContext]];
[request setResultType:NSDictionaryResultType];
//Assign the predicate to the fetch request
[request setPredicate:filterFavourite];
NSError *error = nil;
//Create an array from the returned objects
NSArray *tweetsToExport = [_managedObjectContext executeFetchRequest:request error:&error];
NSAssert2(tweetsToExport != nil && error == nil, #"Error fetching events: %#\n%#", [error localizedDescription], [error userInfo]);
//NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//NSString *path = [NSString stringWithFormat:#"%#/tweets.xml", documents];
NSString *writeerror;
if(tweetsToExport) {
NSString * exportLocationFull = [[NSString alloc]initWithFormat:#"%#/tweets.xml",self.exportLocation];
BOOL success = [tweetsToExport writeToFile:exportLocationFull atomically:YES];
NSLog(#"Write Status = %d to %#", success, exportLocationFull);
}
else {
NSLog(#"%#",writeerror);
}
}
Now, when i look at the exported file, two things happen which are odd!
Firstly, an object that i have seen have it's deleted value set to true, exports with the value as 0.
Secondly, the deletedDate attribute does not export at all, every, despite it being in the core data model. I can't see any way this can happen as i am doing no specific filtering on the export.
It's like a getter/setter somewhere is broken, but i have checked the class files and everything is as it should be and set to #dynamic.
Any help would be greatly appreciated as i'm a bit lost as to what the hell is going on.
People had warned me about core data's quirks, but this is just plain odd!
Cheers
Gareth
Note 1
As an aside, i am using the exact same code from the first section to set other attributes on objects that are filtered and that seems to work fine!
You should not name an Core Data attribute "deleted", that conflicts with the
isDeleted method of NSManagedObject.
Compare https://stackoverflow.com/a/16003894/1187415 for a short analysis of that problem.
There are other attribute names that cause conflicts, e.g. "updated" (compare Cannot use a predicate that compares dates in Magical Record). Unfortunately, there are no warnings at compile time or runtime,
and the documentation on what acceptable attribute names are is also quite vague.
Things to check:
Did you save your core data entities with [managedObjectContext save:&error] at the appropriate places (e.g. before displaying the new table view data)? Did you check the error variable?
Did you migrate your model correctly with a new model version?
Are you reading the correct attributes and displaying them correctly (in UI or log statements)?
BTW, in your code you are setting the request entity twice.
Try saving the mananged object context before loading the table view.
The boolean deleted may be 0 before and not be changed or it may be auto-initialized (there is an field in the inspector to set default values) to 0. Date fields on the other hand are nil by default.
P.S. Use [NSNumber numberWithBoolean:YES] in Objective-C.

An easy, reliable way to get max value of a core data object's attribute?

Is there an easy (or just reliable) way to find the max value of a core data attribute? Apple's example just does not work (plus it is ridiculously long and complicated for such a simple task). I have spent almost a day on this and haven't been able to find a satisfactory answer. Please help!
I get the same error as in this question: -[NSCFNumber count]: unrecognized selector. Like him, I haven't been able to find a solution to the problem.
This asker thinks he solved the same problem but, like someone else commented, the answer here is apparently wrong.
This question also has trouble with exactly the same code but at least the asker actually didn't get at an exception. It appears that he couldn't get it to work properly though and ended up using a different solution but didn't say what.
One person here got around it by retrieving results sorted and using the top value. Not ideal! However, if I cannot find a solution soon, I think I will have to do the same, or restructure my model or business rules or create and maintain a MaxValue class in my model to get around this...
I found this in the core data programing guide under fetching specific values. It refers to a minimum, but it answers your question. Not as easy as one would hope, but it isn't that hard to follow.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[request setEntity:entity];
// Specify that the request should return dictionaries.
[request setResultType:NSDictionaryResultType];
// Create an expression for the key path.
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"creationDate"];
// Create an expression to represent the minimum value at the key path 'creationDate'
NSExpression *minExpression = [NSExpression expressionForFunction:#"min:" arguments:[NSArray arrayWithObject:keyPathExpression]];
// Create an expression description using the minExpression and returning a date.
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
// The name is the key that will be used in the dictionary for the return value.
[expressionDescription setName:#"minDate"];
[expressionDescription setExpression:minExpression];
[expressionDescription setExpressionResultType:NSDateAttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError *error = nil;
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
if (objects == nil) {
// Handle the error
}
else {
if ([objects count] > 0) {
NSLog(#"Minimum date: %#", [[objects objectAtIndex:0] valueForKey:#"minDate"]);
}
}
[expressionDescription release];
[request release];
have a NSManagedObject Subclass called TestEntity, which has one double property called tesetID.
-(double)getNextIndex
{
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"TestEntity" inManagedObjectContext:self.managedObjectContext]];
NSError * err = nil;
NSArray * array = [self.managedObjectContext executeFetchRequest:request error:&err];
NSNumber * value = [array valueForKeyPath:#"#max.testID"];
return [value doubleValue]+1;
}
-(void)test
{
for (int i = 0; i<25 ; i++) {
TestEntity * entity = [[TestEntity alloc] initWithEntity:[NSEntityDescription entityForName:#"TestEntity" inManagedObjectContext:self.managedObjectContext] insertIntoManagedObjectContext:self.managedObjectContext];
entity.testID = [NSNumber numberWithDouble:[self getNextIndex]];
NSLog(#"our testID is set as: %#",entity.testID);
}
}