I know how to use NSPredicate to perform a SQL SELECT-like operation. How can I perform something like DELETE WHERE? Do I have to call [NSManagedObjectContext deleteObject] for each fetched object? Thanks
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:TASK_ENTITY inManagedObjectContext:managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"label LIKE %#", label];
[request setPredicate:predicate];
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
[managedObjectContext deleteObject:[array objectAtIndex:0]];
I believe looping over the returned array and calling [NSManagedObjectContext deleteObject:] is the "correct"/idiomatic way to do it. It might seem inefficient, but remember that the fetch command doesn't actually fetch the objects' data, and the deleteObject: method just marks the object for deletion, which gets applied when you send [NSManagedObjectContext save:]. Not knowing the internals of Core Data I can't tell you whether it's as efficient as a DELETE WHERE query (presumably Core Data has the indexed primary keys in memory from the fetch, and uses those) but in my experience with profiling Core Data apps it's not significantly slower than saving new or updated objects.
You can use NSBatchDeleteRequest available on iOS 9.0+, macOS 10.11+, tvOS 9.0+, watchOS 2.0+
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"label LIKE %#", label];
NSFetchRequest *fetchRequest = [TaskEntity fetchRequest];
[fetchRequest setPredicate:predicate];
// Create batch delete request
NSBatchDeleteRequest *deleteReq = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetchRequest];
NSError *error = nil;
NSBatchDeleteResult *deletedResult = [appDelegate.persistentContainer.viewContext executeRequest:deleteReq error:&error];
if (error) {
NSLog(#"Unable to delete the data");
}
else {
NSLog(#"%# deleted", deleteReq.result);
}
Swift code (from the above link)
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Employee")
fetch.predicate = NSPredicate(format: "terminationDate < %#", NSDate())
let request = NSBatchDeleteRequest(fetchRequest: fetch)
do {
let result = try moc.execute(request)
} catch {
fatalError("Failed to execute request: \(error)")
}
NOTE:
I found below comment about execute of moc
Method to pass a request to the store without affecting the contents of the managed object context.
Which means any unsaved data in moc won't be affected. i.e. if you've created/updated entity that falls in the delete request criteria and don't called save on moc then that object won't be deleted.
I haven't found an other way than to use an NSArray method for deletion.
(If there is I want to know about it)
You could nest the call to have it on one line if you really don't wan't to store the array. But if you do so verify what is the return of the Fetch in case of an error.
makeObjectsPerformSelector :
Sends to each object in the array the message identified by a given selector, starting with the first object and continuing through the array to the last object.
- (void)makeObjectsPerformSelector:(SEL)aSelector
Or there is a block one to that is suppose to be faster.
I just tried this and it is successful:
NSError *error = nil;
NSFetchRequest* fetchrequest = [NSFetchRequest fetchRequestWithEntityName:#"EntityName"];
[request setPredicate:[NSPredicate predicateWithFormat:#"attribute == %#", variable]];
NSArray *deleteArray = [context executeFetchRequest:fetchrequest error:&error];
if (deleteArray != nil)
{
for (NSManagedObject* object in deleteArray)
{
[context deleteObject:object];
//Reload/refresh table or whatever view..
}
[context save:&error];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:TASK_ENTITY inManagedObjectContext:managedObjectContext];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"label LIKE%#", label.text]];
NSError* error = nil;
NSArray* results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if(![results count]==0)
{
[managedObjectContext deleteObject:[results objectAtIndex:0]];
}
if (![managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
Related
I am trying to do my Core-data operation on background context.
I insert all my data on the background context but when I fetch on main thread, it doesnt show any data which I already inserted.
I dont know, where am I doing the mistake...:(
Any help appreciate.
This Is what I have tried for insert and the second one is for fetch the data.
- (void)insertNameAndSurnameInDataBase:(NSString *)name surname:(NSString *)surname numbers:(NSArray *)numbers labels:(NSArray *)labels {
AppDelegate *appDel=(AppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *saveObjectContext = [appDel saveManagedObjectContext];
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.parentContext = saveObjectContext;
[bgContext performBlockAndWait:^{
NSError *error;
NameAndSurname *nameAndSurname = [NSEntityDescription insertNewObjectForEntityForName:#"NameAndSurname" inManagedObjectContext:bgContext];
NSString *nSurname = [NSString stringWithFormat:#"%# %#",name,surname];
if (nSurname.length == 0) {
nameAndSurname.nameSurname = [numbers objectAtIndex:0];
} else {
nameAndSurname.nameSurname = nSurname;
}
if ([bgContext save:&error]) {
} else {
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname" inManagedObjectContext:bgContext];
// predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"nameSurname =[c] %#", nSurname];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *fetchedObjects = [bgContext executeFetchRequest:fetchRequest error:&error];
if([fetchedObjects count] > 0){
[numbers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
id obj2 = [labels objectAtIndex:idx];
obj = [obj stringByReplacingOccurrencesOfString:#" " withString:#""];
ContactNumber *phoneNumber = [NSEntityDescription insertNewObjectForEntityForName:#"ContactNumber" inManagedObjectContext:bgContext];
phoneNumber.number = obj;
phoneNumber.label = obj2;
phoneNumber.nameAndSurname = fetchedObjects[0];
NSError *error;
if ([bgContext save:&error]) {
} else {
}
}];
}
}];
}
- (NSArray *)fetchNameAndSurname {
AppDelegate *appDel = (AppDelegate *)[UIApplication sharedApplication].delegate;
_managedObjectContext = [appDel managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"NameAndSurname"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setSortDescriptors:#[[[NSSortDescriptor alloc] initWithKey:#"nameSurname" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]]];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSError *error = nil;
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
return [fetchedObjects valueForKey:#"nameSurname"];
}
The mistake you are making is to not test the many little pieces of this code which could be not working as expected. To troubleshoot something like this, you must start at the beginning and test one piece at a time. The first issue I see is confusion between managed object contexts…
It appears that the method saveManagedObjectContext is a getter for your main managed object context. In English, save is a verb, so that this method name implies that it is saving some managed object context. If I am correct, you should change the name saveManagedObjectContext to maybe mainMangaedObjectContext.
The statement _managedObjectContext = [appDel managedObjectContext] is quite nonstandard, assigning to an instance variable from what appears to be its getter. If, as usual, _managedObjectContext is the instance variable backing the property managedObjectContext, this statement has no effect.
In your first method you use [appDel saveManagedObjectContext], and in your second method you use [appDel managedObjectContext]. It looks like these should be the same context in order for your fetch to work. Are they?
UPDATE:
As you stated in your comment, that fixes the saving, but now you have a performance problem – saving to the persistent store on disk blocks the user interface, which is what your original code was trying to fix.
This is a solved problem. The solution is that your main-thread context, which interfaces the user, should be a child of your background-thread context which interfaces to the persistent store. It is explained nicely with code in this 2015 blog post by Marcus Zarra. For further reading, Chad Wilken has published a slight variation. Both are written in Objective-C for you :)
I want to update the first record in the NSManagedObject. What I have here updates all of them which I realise is because I am selecting them all and updating them all using the for but how do I just update the first record?
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSError *errorLoading = nil;
self.users = [context executeFetchRequest:fetchRequestNext error:& errorLoading];
for (NSManagedObject *usersObject in [self users])
{
[usersObject setValue:#"*" forKey:#"currentUser"];
}
NSError *error;
[context save:&error3];
}
From the results (array) you simply select the one at index:0
NSArray *results = [context executeFetchRequest:fetchRequestNext error:& errorLoading];
if (results.count>0) {
NSManagedObject *userObject = results[0];
[usersObject setValue:#"*" forKey:#"currentUser"];
}
NSError *saveError = nil;
[context save:&saveError];
I hope this helps
Kind of like this:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
fetchRequest.fetchLimit = 1; //Fetch only one object (optional)
NSError *errorLoading = nil;
NSManagedObject *user = [[context executeFetchRequest:fetchRequestNext error:& errorLoading] firstObject];
if (errorLoading) {
//handle error
}
[user setValue:#"*" forKey:#"currentUser"];
NSError *error = nil;
if (![context save:&error]) {
//handle error
}
First of all, if you only need one object, you can set the fetchLimit to 1. It is not required, it's just a small optimization (it will make CoreData stop after fetching the first object). Then you execute the request just like you normally would and get the first object from the resulting array.
Another option is to use the method firstObject defined in NSArray.
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSError *errorLoading = nil;
NSArray *users = [context executeFetchRequest:fetchRequestNext error:& errorLoading];
NSManagedObject *singleUser = [users firstObject];
if(singleUser){
[singleUser setValue:#"*" forKey:#"currentUser"];
}
NSError *error;
if([context save:&error]){
}
Can anyone spot why this isn't returning any ManagedObjects? I'm trying to add to the ATNSManagedObject+EasyFetching class the following, but the fetch result returns nothing. If I fetch these outside of the EasyFetch class I have 100+ objects so I know it isn't CoreData being empty.
+ (void)deleteAllObjectsInContext;
{
NSManagedObjectContext *context = [NSManagedObjectContext defaultContext];
NSEntityDescription *entity = [self entityDescriptionInContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
//[request setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error != nil)
{
//handle errors
NSLog(#"delete error");
}
for (NSManagedObject *thing in results) { <--- |results shows 0 objects|
[context deleteObject:thing];
}
NSError *saveError = nil;
[context save:&saveError];
}
Try simplifying it to:
// first get the context or pass it in as an argument (this is usually what I do for
// a deleteAll class level method like this but your call
+ (void)deleteAllObjectsInContext:(NSManagedObjectContext*)context {
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:# "<yourEntity>"];
// no predicate
// no sortDescriptors
NSError* error = nil;
NSArray* results = [context executeFetchRequest:request error:&error];
if (!results || error) { // nil is an error
// handle error
}
// do something with results
}
this way you can avoid having to retrieve an NSEntityDescription object.
UPDATE:
Just wanted to add this passage:
Return Value
"An array of objects that meet the criteria specified by request fetched from the receiver and from the persistent stores associated with the receiver’s persistent store coordinator. If an error occurs, returns nil. If no objects match the criteria specified by request, returns an empty array".
One possibility is that your NSManagedObjectContext *context is actually nil.
In Objective-C, sending messages to nil is perfectly acceptable, and can make it hard to detect where an issue is.
I don't find documentation for [NSManagedObjectContext defaultContext], so I assume that is a category you wrote (or are using); and I suspect it is not always returning a valid context. Add some logging and see!
Try this method and watch the Log. It just fetches everything from the current entity.
+ (NSArray*) retrieveEntity:(NSString*) entityName {
// !!!Here you put your context
NSManagedObjectContext *context = appDelegate.managedObjectContext;
if (context == nil) {
NSLog(#"Error: No context");
return nil;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle the error
NSLog(#"Error: No fetched objects.");
return nil;
}
else
NSLog(#"Retrieved objects count:%d", [fetchedObjects count]);
return fetchedObjects;
}
This is an example, how to call it.
// Retrieve all products
NSArray *flXProducts = [DbConnection retrieveEntity:#"FLXProduct"];
If it returns 0, then there is a problem in your database. You can reveal a problem by finding sql file of your database and tring simple SQL on it in Terminal.
I would like to omitt the predicate in an NSFetchRequest in order to delete all managed objects for an entity.
However, when there is no predicate (according to the SQL debugger), the fetch request is not executed. According to Apple the predicate should be optional.
How would I need to change my code to remove the predicate? Any ideas? Thank you!
- (void)deleteEntity:(NSString*)entityName inContext:(NSManagedObjectContext *)context
{
NSFetchRequest * request= [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"TableStructure" inManagedObjectContext:context]];
//[entities setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"category = 'est'"];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *entitiesArray = [context executeFetchRequest:request error:&error];
if (error) {
NSLog(#"%#: Error fetching context: %#", [self class], [error localizedDescription]);
NSLog(#"entitiesArray: %#",entitiesArray);
return;
}
for(NSManagedObject *entity in entitiesArray) {
[context deleteObject:entity];
}
NSError *saveError = nil;
[context save:&saveError];
}
Update 2
It seems that I call my fetch request before the database is ready. How can I make sure that my request is not called before the core data database is ready?
You simply don't assign the predicate if you don't want it. Remove following lines:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"category = 'est'"];
[request setPredicate:predicate];
I issued my fetch request before the Core Data database was ready.
To solve this issue, I have now added the call ImportFormulasInRequest to the UIDocument openWithCompletion handler, which encapsulates my core data database:
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.myDatabase.fileURL path]]) {
// does not exist on disk, so create it
[self.myDatabase saveToURL:self.myDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.myDatabase.documentState == UIDocumentStateClosed) {
// exists on disk, but we need to open it
[self.myDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
[self ImportFormulasInContext:[self.myDatabase managedObjectContext]];
}];
} else if (self.myDatabase.documentState == UIDocumentStateNormal) {
// already open and ready to use
[self setupFetchedResultsController];
[self ImportFormulasInContext:[self.myDatabase managedObjectContext]];
}
}
I am wondering how I could delete an object depending on it's title for the CoreData 'name' property I have.
To Add an Object I use this code:
NSManagedObjectContext *moc = [self managedObjectContext];
JGManagedObject *theParent =
[NSEntityDescription insertNewObjectForEntityForName:#"projects"
inManagedObjectContext:moc];
[theParent setValue:nil forKey:#"parent"];
// This is where you add the title from the string array
[theParent setValue:#"myTitle" forKey:#"name"];
[theParent setValue:[NSNumber numberWithInt:0] forKey:#"position"];
But I can't seem to find an equivalent function to remove An object.
I don't know if you looked in the Core Data Programming guide in the section for adding and deleting objects.
Edit
I've modified this to delete from an array of names. Again; less then 5 minutes work with the Predicate Programming Guide.
- (void)removeObjectsWithNames:(NSArray *)nameArray {
// Get the moc and prepare a fetch request for the required entity
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Create a predicate for an array of names.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name IN %#", nameArray];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
// Execute the fetch request put the results into array
NSError *error = nil;
NSArray *resultArray = [moc executeFetchRequest:request error:&error];
if (resultArray == nil)
{
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
// Enumerate through the array deleting each object.
// WARNING, this will delete everything in the array, so you may want to put more checks in before doing this.
For (JGManagedObject *objectToDelete in resultArray ) {
// Delete the object.
[moc deleteObject:objectToDelete];
}
}
Edited 10/10/2009 - To add what Joshua has tried.
for(NSString *title in oldTasks) { // 1
// Get the moc and prepare a fetch request for the required entity
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"projects" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Create a predicate for an array of names.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"title IN %d", oldTasks]; // 2
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
// Execute the fetch request put the results into array
NSError *error = nil;
NSArray *resultArray = [moc executeFetchRequest:request error:&error];
if (resultArray == nil)
{
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
JGManagedObject *objectToDelete = [resultArray objectAtIndex:0];
// Delete the object.
[moc deleteObject:objectToDelete];
}
Notes
I've highlighted two lines.
You've pasted my example as a for loop rather than a function call. This is just taking the strings off one at a time and passing them into the method. In my example, I'm passing in an array of strings that you want to match.
This is where you are having your problem. If you'd bothered reading the the Predicate Programming Guide, right at the top, in the Predicates Basics section it says it expects the class that it is being used with should be KVC compliant. This is why you are getting the error about KVC compliance. You are trying to search for title IN... but title isn't a property of your model.
I think you may be confused about what a predicate does. Look at the exaple code that I wrote.
Firstly, I create a Fetch request which will select objects from the 'Projects' entity.
Secondly, I create a predicate which says for each object returned by the fetch request, get the value of the 'name' property and compare it to the values of the objects in the 'namesArray'
Thirdly, I'm creating a sort descriptor that will sort the results in ascending order based on the 'name' property.
Then, once I've set this fetch request up, I run it against the moc and it returns an array of objects that match these criteria.