I am using magical record to handle core data. I have code that creates a Entity which looks as follows:
if ( gameEntity == nil )
{
gameEntity = [GameEntity MR_createEntity];
}
gameEntity.opponent = opponent;
gameEntity.me = me;
etc...
When I then query for game entities as follows:
NSPredicate* gamePredicate = [NSPredicate predicateWithFormat:#"SELF IN %# AND gameState != %#", me.myGames, GAME_ENTITY_STATE__OTHER];
NSFetchRequest* fetchRequest = [GameEntity MR_requestAllWithPredicate:gamePredicate];
NSArray * results = [[NSManagedObjectContext MR_defaultContext] executeFetchRequest:fetchRequest error:nil];
return results;
Results has a size of 1 (which makes sense).
However when I save the context as follows:
[[NSManagedObjectContext MR_defaultContext] MR_save];
And run the same fetchRequest above, all of a sudden the GameEntity is gone and there are no results in NSArray.
Even weirder when I shut down the app and run it again, it finds the GameEntity. So it was in fact saved. Why is MR_save essentially breaking my NSManagedObject context?
All the save and creates are being run on the main thread.
So it turns out that the problem was my predicate, which makes this more of a CoreData problem than a magical record.
I changed the predicate from this:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"SELF IN %# AND gameState != %#", me.myGames, GAME_ENTITY_STATE__OTHER];
To this:
predicate = [NSPredicate predicateWithFormat:#"me == %# AND gameState != %#", me, GAME_ENTITY_STATE__OTHER];
For some reason the first predicate is not always accurate. While the second one seems to always work.
While this does answer the question, I'd be curious why people believe why the first predicate doesn't always work...
Related
Hy,
I wish to fetch all the songs those having playlist_index = 1, suppose. I have created one to many relation and generated accessor.
and but when i am fetching songs through predicate it always give me only one song although i have added same playlist to many songs. This is how i am adding playlist to song by converting pure NSManagedObject to coredata generated model object.
-(void) addPlaylistToAudio {
// Audio Coredata Model
Audio *audioManagedObject = [self getAudioManagedObject:self.artist];
PlaylistModel *playlistObj = [self.arrayPlaylist objectAtIndex:indexPath.row];
// Playlist Coredata Model
Playlist *playlistManagedObject = [self getPlaylistManagedObject:playlistObj];
[audioManagedObject addPlaylist_relationObject:playlistManagedObject];
[playlistManagedObject setAudio_relation:audioManagedObject];
// PLEASE CHECK HERE IF I AM ADDING CORRECTLY
NSManagedObjectContext *context = [[CoreDataHandler sharedInstance]
managedObjectContext];
NSError *error = nil;
if (![context save:&error]) {
}
else {
}
}
- (Audio *)getAudioManagedObject:(Artist *)artist {
// Create Predicate to fetch managed object.
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"track_index == %ld",artist.track_index];
// Get Predicate array of managed object.
NSArray *arrayAudioManagedObject = [[CoreDataHandler sharedInstance]
fetchAudioManagedObjectsWithPredicate:predicate havePredicate:TRUE];
// Managed object.
Audio *audioManagedObject = [arrayAudioManagedObject lastObject];
return audioManagedObject;
}
-(Playlist *)getPlaylistManagedObject:(PlaylistModel *)playlist {
// Create Predicate to fetch managed object.
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"playlist_index == %ld",playlist.playlist_index];
// Get Predicate array of managed object.
NSArray *arrayPlaylistManagedObject = [[CoreDataHandler sharedInstance]
fetchPlaylistManagedObjectsWithPredicate:predicate havePredicate:TRUE];
// Managed object.
Playlist *playlistManagedObject = [arrayPlaylistManagedObject
lastObject];
return playlistManagedObject;
}
Fetching:
NSPredicate *predicate_audio = [NSPredicate predicateWithFormat:#"ANY playlist_relation.playlist_index == %ld",self.playlistInfo.playlist_index];
NSArray *arraySongsManagedObject = [[CoreDataHandler sharedInstance] fetchAudioManagedObjectsWithPredicate:predicate_audio havePredicate:TRUE];
I think ANY will return just one result that meets your criteria, what you really need is SUBQUERY where you can replace your delegate with something like:
let predicate_audio = NSPredicate(format: "SUBQUERY(playlist_realtion, $x, $x.playlist_index == %ld).#count > 0", self.playlistInfo.playlist_index)
or in Objective-C
NSPredicate *predicate_audio = [NSPredicate predicateWithFormat:#"SUBQUERY(playlist_realtion, $x, $x.playlist_index == %ld).#count > 0", self.playlistInfo.playlist_index]
Note that $x is just a placeholder to represent each element relationship.
Links:
NSPredicate with SubQuery
http://davidchuprogramming.blogspot.com/2016/10/one-to-many-relationship-and-subquery.html
Let me know if it worked for you
I encountered a weird issue in Core Data. Basically, I have an empty core data with several models like Student and Teacher. I tried to execute the code below:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Student"];
request.predicate = [NSPredicate predicateWithFormat:#"(username = %#) AND (password = %#)", username, password];
// Determines if there is a match for student
NSError *error;
NSArray *matches = [self.context executeFetchRequest:request error:&error];
if (error || !matches || [matches count] > 1) {
NSLog(#"Error in retrieving login match for student");
}
else if ([matches count]) {
student = [matches firstObject];
}
The weird thing is that matches turns out to be nil and the error message gets printed out. Shouldn't matches be an empty array since an empty core data would just mean that the context couldn't find the NSManagedObject that satisfied those conditions?
Short answer = yes, it should.
According to Apple doc:
Return Value of executeFetchRequest ... 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.
Therefore, check your NSError
Basically I got 3 entities in my data model : Brand, Model and Trim.
A brand has a one-to-many relationship with Model called "models". (one brand have multiple models but a model has only one brand)
A model has a many-to-many relationship with Trim called "trims". (a model can have multiple trims, and a trim can have multiple models)
Having an array of trims objects, I would like to get all the brands having a model which "contains" at least one trim contained inside this array.
So here is my predicate for the fetch request :
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Brand"];
[NSPredicate predicateWithFormat:#"ANY models.trims IN %#", arrayOfTrims];
And here is the error I'm getting :
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'unimplemented SQL generation for predicate : (ANY models.trims IN {<Trim: 0x8e60340> (entity: Trim; id: 0x8e62f10 <x-coredata://BCD28560-AED5-4409-9A35-1925001773E6/Trim/p8>
I'm kinda new to Core Data and I have no idea what I'm doing wrong.
Hope someone can help,
Thanks a lot.
"ANY" in a Core Data predicate works only for a single to-many relationship.
Since your query involves two to-many relationships, you have to use a SUBQUERY:
[NSPredicate predicateWithFormat:#"SUBQUERY(models, $m, ANY $m.trims IN %#).#count > 0",
arrayOfTrims];
This exception is also raised if one of the predicates uses a column (i.e. field) name that does not exist. Totally misleading error message...
When you use a predicate in a CoreData operation, the predicate gets translated into SQL. That translation is not possible for all NSPredicate operations, you've hit one that isn't. My suggestion would be something along the lines of:
NSMutableArray* predicates = [NSMutableArray new];
for(NSString* trim in arrayOfTrims)
{
[predicates addObject:[NSPredicate predicateWithFormat:#"%# IN models.trims", trim]];
}
NSPredicate* predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];
The keyword IN can be used but you cannot apply ANY at the same time as that does not make sense when you turn it into SQL.
The predicate you are most likely looking for is:
[NSPredicate predicateWithFormat:#"models.trims IN %#", arrayOfTrims];
But that isn't going to work in this case either because you are going across a relationship. So what you need to do is reverse the whole thing:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Model"];
[request setPredicate:[NSPredicate predicateWithFormat:#"trims in %#", arrayOfTrims]];
NSError *error = nil;
NSArray *modelArray = [moc executeFetchRequest:request error:&error];
if (!modelArray) {
NSLog(#"Error: %#\n%#", [error localizedDescription], [error userInfo]);
}
NSArray parentObjectArray = [modelArray valueForKey:#"${PARENT_RELATIONSHIP_NAME}"];
Basically you are fetching the child objects to satisfy your ANY and then using KVC to retrieve the parent objects that you care about.
In my case I got this error because I made a silly mistake and didn't have the argument in the right data type.
I was trying to do:
let firstPredicate = NSPredicate(format: "firstName BEGINSWITH[cd] %#", firstNameField.stringValue)
and forgot to put the ".stringValue."
This is the Core Data model. Image
DiaCD <--->> HoraCD <<---> ActividadCD
In the entity "Activity" is a category called attribute to filter the activities. My question is: How could I make a query to give me back the days with activities where the category is "X"?
Try one:
NSEntityDescription *entityDescriptionDia = [NSEntityDescription entityForName:#"DiaCD" inManagedObjectContext:managedObjectContext];
NSFetchRequest *requestDia = [[NSFetchRequest alloc] init];
NSPredicate *predicateDia = [NSPredicate predicateWithFormat:#"ANY relDiaHora.relHoraActividad.categoria == %#", categoria];
[requestDia setEntity:entityDescriptionDia];
[requestDia setPredicate:predicateDia];
NSError *errorDia;
NSArray *arrayDia = [managedObjectContext executeFetchRequest:requestDia error:&errorDia];
if ([arrayDia count] > 0) {
for (DiaCD *dia in arrayDia) {
NSSet *setHora = dia.relDiaHora;
HoraCD *horaQuery = [setHora anyObject];
ActividadCD *actividadQuery = horaQuery.relHoraActividad;
NSLog(#"Act --> %# y la categoria --> %# y la categoria --> %#", actividadQuery.titulo, actividadQuery.categoria, categoria);
}
}
If I do this query does not return good data that does not respect the category, I'm guessing, do not know why :S.
Try 2:
NSPredicate *predicateDia = [NSPredicate predicateWithFormat:#"relDiaHora.relHoraActividad.categoria == %#", categoria];
If I do the same query but only removing the "ANY" fails. Error: "reason: 'to-many key not allowed here'"
Your predicate
[NSPredicate predicateWithFormat:#"ANY relDiaHora.relHoraActividad.categoria == %#", categoria];
returns all days that have any hour with the given activity category. Your problem seems
to be that
NSSet *setHora = dia.relDiaHora;
returns all hours of the fetched day, not only the hours with the given activity category.
Therefore
HoraCD *horaQuery = [setHora anyObject];
is any hour of the fetched day, and need not have the given activity.
If you need the days and matching hours, you should execute a fetch request on the hours
instead:
NSEntityDescription *entityDescriptionHour = [NSEntityDescription entityForName:#"HourCD" inManagedObjectContext:managedObjectContext];
NSFetchRequest *requestHour = [[NSFetchRequest alloc] init];
NSPredicate *predicateHour = [NSPredicate predicateWithFormat:#"relHoraActividad.categoria == %#", categoria];
[requestHour setEntity:entityDescriptionHour];
[requestHour setPredicate:predicateHour];
NSError *errorHour;
NSArray *arrayHours = [managedObjectContext executeFetchRequest:requestHour error:&errorHour];
if ([arrayHours count] > 0) {
for (HourCD *hour in arrayHours) {
DiaCD *dia = hour.relHoraDia;
ActividadCD *actividad = hour.relHoraActividad;
// ...
}
}
OK, the first bit was wrong. However, still follow this note about naming.
A QUICK NOTE
You should not be calling your attributes "relItem1Item2". This is something that comes from relational databases. CoreData is not a relational database.
You should name them descriptively as to what they point to.
i.e.
relHoraActividad should be called actividad. As it is pointing to the actividad entity and it is a "to-one" relationship.
Also...
relActividadHora should be called horaCDs. It is pointing to the horaCD entity and it is a "to-many" relationship.
I think your setup is absurd. Why have complicated relationships to days and hours when you can simply use NSDate attributes?
From your image I see that you store other things in these entities that do not seem to have anything to do with hours or days.
To filter by day or time, you typically use a pattern like this
[NSPredicate predicateWithFormat:#"time > %# && time < %#", min, max];
I'm trying to update an existing object in my Core Data stack,
The object is being retrieved and i can change the value, but when saving the context , nothing is being saved. also the hasChanges is NO and updatedObjects is empty.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: #"MyModel"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(x == %#) AND (y == %#)", x, y];
request.returnsObjectsAsFaults = NO;
request.predicate = predicate;
NSArray *results = [self.managedObjectContext executeFetchRequest: request error: nil];
MyModel *model = results[0];
model.newvalue = "somenewvalue";
NSLog(#"%#", (self.managedObjectContext.hasChanges)?#"YES":#"NO"); // -> Shows NO
NSLog(#"%#", self.managedObjectContext.updatedObjects); // -> Shows empty array
[self saveContext]; // No error, seems to be working successfully but the updated objects is empty as well has "hasChanges", so something else is being missed here
This is a bit baffling and annoying. I would really appreciate any insights on this issue.
As suggested this was tried as well but didn't help
NSManagedObject *model = results[0];
[model setValue: #"somenewvalue" forKey: #"newvalue"];
Crazy answer but I can definitely imagine someone else making this error.
Inside my NSManagedObject instead of #dynamic for each property I had #synthesize. That was the issue.