Evaluate CoreData entity type in predicate - core-data

I have a block predicate that I carefully crafted only to discover you can't use them in Core Data.
NSPredicate *rootContactPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
BOOL isPersonAndRoot = ([[[evaluatedObject entity] name] isEqualToString:#"Person"] && [[(Person*)evaluatedObject accounts] count] == 0);
BOOL isAccountAndRoot = ([[[evaluatedObject entity] name] isEqualToString:#"Account"] && [(Account*)evaluatedObject root] == nil);
return isPersonAndRoot || isAccountAndRoot;
}];
So I need to convert this into a standard String format predicate, but I am unclear on how to check the entity type for the evaluated object. The Person and Account entities are subclasses of a Contact entity which is the type being evaluated in the fetch request. I'm hoping it will see the sub-types.

Check the entity like this:
[NSPredicate predicateWithFormat:#"self.entity = %#", Person.entity];

Swift 4.0 way of checking for entity type using the entity property of the NSManagedObject as per malhal's suggestion.
let entityDescription = NSEntityDescription.entity(forEntityName: "Account", in: managedObjectContext)!
return NSPredicate(format: "entity = %#", entityDescription)

It seems that now you can now simply compare the entity in a predicate, supplying the managed objects entity as the value:
"entity = %#"
Previously:
You can't. The reason is that the predicate will need to be converted so that it can be run on the underlying data store (presumably SQLite). The SQLite database doesn't have any data on the type of the element, it only knows about the keys and values of the objects.
Depending on what you're trying to do, you'll either need to run a single fetch request against the keys known in the super entity. Or you'd need to have 2 fetch requests, separately executed and then combine the 2 result sets.

Related

NSFetchRequest predicate querying correctly, but resulting NSManagedObject showing incorrect fields [duplicate]

I am using UIManagedDocument with Parent Child context.
In my child context I do the following
Code 1
NSSet *results = [self.event.memberships filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return ([[evaluatedObject deleted] boolValue] == NO);
}]];
Above code returns the expected results (only Not deleted members for the event).
Code 2
But this code does not. It fetches all records.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"deleted == NO"];
NSSet *results = [self.event.memberships filteredSetUsingPredicate:predicate];
It seems confusing. Both should return same results, but predicateWithBlock returns correct results where as predicateWithFormat returns all records.
What are the pros and cons of using predicateWithBlock instead of predicateWithFormat?
The problem is that you have defined an attribute deleted for your entity. That conflicts with the isDeleted method of NSManagedObject, so you should rename that attribute.
The following "experiment" shows that strange things happen if you call your attribute "deleted" (c is a managed object with a custom deleted attribute):
// Set custom "deleted" property to YES:
c.deleted = #YES;
// Use the property, as your Code 1
NSLog(#"%#", [c deleted]);
// Output: 1
// Use Key-Value Coding, as your Code 2
NSLog(#"%#", [c valueForKey:#"deleted"]);
// Output: 0
// Now really delete the object and try again:
[context deleteObject:c];
NSLog(#"%#", [c valueForKey:#"deleted"]);
// Output: 1
Your "Code 1" refers to the property, therefore it returns the expected result. "Code 2" uses Key-Value Coding, and [c valueForKey:#"deleted"] returns YES if the object
actually has been deleted from the context!
So renaming that attribute should solve your problem. Unfortunately the compiler does not
emit warnings if an attribute name conflicts with a built-in method.
Use the formatting placeholder to replace the bool value:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#",
#"deleted", #(NO)];
Your use of the key path is probably ok, but the right-hand side probably doesn't look like "NO" to the parser.

How to get fields (attributes) out of a single CoreData record without using [index]?

I have one CoreData record that contains all of the app's settings. When I read that single record (using MagicalRecord), I get an array back. My question is: can I get addressabiltiy to the individual fields in the record without using "[0]" (field index), but rather using [#"shopOpens"]?
I was thinking something like this, but I don't think it's right:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"aMostRecentFlag == 1"]; // find old records
preferenceData = [PreferenceData MR_findAllWithPredicate:predicate inContext:defaultContext]; // source
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *preferencesDict = [[userDefaults dictionaryForKey:#"preferencesDictionary"] mutableCopy]; // target
// start filling the userDefaults from the last Preferences record
/*
Printing description of preferencesDict: {
apptInterval = 15;
colorScheme = Saori;
servicesType = 1;
shopCloses = 2000;
shopOpens = 900;
showServices = 0;
syncToiCloud = 0;
timeFormat = 12;
}
*/
[preferencesDict setObject: preferenceData.colorScheme forKey:#"shopOpens"];
UPDATE
This is how I finally figured it out, for those who have a similar question:
NSPredicate *filter = [NSPredicate predicateWithFormat:#"aMostRecentFlag == 0"]; // find old records
NSFetchRequest *freqest = [PreferenceData MR_requestAllWithPredicate: filter];
[freqest setResultType: NSDictionaryResultType];
NSDictionary *perferenceData = [PreferenceData MR_executeFetchRequest:freqest];
Disclaimer: I've never used magical record, so the very first part is just an educated guess.
I imagine that preferenceData is an instance of NSArray firstly because the method name uses findAll which indicates that it will return multiple instances. Secondly, a normal core data fetch returns an array, and there is no obvious reason for that find method to return anything different. Thirdly, you referenced using an index operation in your question.
So, preferenceData is most likely an array of all objects in the store that match the specified predicate. You indicated that there is only one such object, which means you can just grab the first one.
PreferenceData *preferenceData = [[PreferenceData
MR_findAllWithPredicate:predicate inContext:defaultContext] firstObject];
Now, unless it is nil, you have the object from the core data store.
You should be able to reference it in any way you like to access its attributes.
Note, however, that you can fetch objects from core data as dictionary using NSDictionaryResultType, which may be a better alternative for you.
Also, you can send dictionaryWithValuesForKeys: to a managed object to get a dictionary of specific attributes.

What is the correct way for quering a NSNumber in a NSArray within CoreData?

I have a Entity with a column of type ID named "responsibleUsers". In this column I store an Array containing NSNumbers.
I want to fetch all objects of this entity, that match my current User. Therefore i create following predicate:
[NSPredicate predicateWithFormat: #"%# IN responsibleUsers",[NSNumber numberWithInteger: curUser.oID.integerValue] ]
whatever I try, my App crashes. Once with a EXC_BAD_ACESS, once with "unimplemented SQL generation for predicate nsnumber"
What is the correct way to query my entity?
The query you are trying assumes that you have two entities: the entity you querying (let's call it Group) and another one, perhaps called User, which is set up as a to-many relationship from Group called responsibleUsers. You would then be able to use the predicate you suggest:
[NSPredicate predicateWithFormat:#"%# IN responsibleUsers, aUser];
This would be the recommended use of Core Data object graphs.
In your setup, it seems you have an array of NSNumber set as a property rather than a relationship. Therefore you cannot use such a query. You simply have to retrieve the array and query the array.
BOOL containsResponsibleUser = NO;
for (NSNumber *n in aGroup.responsibleUsers) {
if ([n isEqualTo:[NSNumber numberWithInteger: curUser.oID.integerValue]])
containsResponsibleUser = YES;
}
If you are indeed querying something like a group of users, I would recommend the first approach. If you are querying some kind of user, I would suggest a BOOL property responsible as the most efficient solution.

Core Data NSPredicate casting key value

I have a data model which has values of type id that I was planning on casting appropriately when needed.
Is it possible for me to cast these as strings and compare them to strings from a UISearchBar using NSPredicate or do I have to use another method?
Maybe something like this:
NSPredicate * predicate;
predicate = [NSPredicate predicateWithFormat:#"CAST(%K) contains[cd] %#", employeeID , theSearchBar.text];
No. The CAST() function doesn't work that way. I think you just have to assume that the id returned from -employeeID is comparable to a string.

Core Data fetches based on properties of 'ordered' relationships

My app has smart folder like functionality: a predicate is setup with a NSPredicateEditor and used to fill the folder with a fetch request.
The entity used in the search has a to-many relationship. The relationship is ordered, in the sense that an index is stored in the destination entity for sorting purposes.
My problem is that I would like to build in a rule based on the last values in the ordered relationship, but I can't figure out how to build a predicate to do this, because the relationship is not an array. Core data doesn't actually know about the order.
I have a readonly property on the class that returns the ordered items, but this doesn't seem to help with the fetch request because the property is not available in the core data store.
The only option I can think of is to de-normalize and store the last items in the relationship ordered in a separate property. Is that the only solution?
Well, assuming I have understood the problem correctly, I'd do it like this. Lets say you've got two entities, TopEntity has a (NSString *)name property and a to-many relationship to MyEntity which has a (NSString *)data property and (NSInteger)order property.
Lets say you want the TopEntity objects which match a given string, and whose MyEntity orders are satisfy a certain condition, then you can do it with two predicates and an NSFetchRequest like so....
NSManagedObjectContext *context = [self managedObjectContext];
// Create some top level entities
TopEntity *aTop = [TopEntity insertInManagedObjectContext:context];
aTop.name = #"This is Your Name";
TopEntity *bTop = [TopEntity insertInManagedObjectContext:context];
bTop.name = #"This aint a Name";
TopEntity *cTop = [TopEntity insertInManagedObjectContext:context];
cTop.name = #"This is My Name";
// Add some data
NSInteger i, len = 30;
for(i=0; i<len; i++) {
// Create a new object
MyEntity *entity = [MyEntity insertInManagedObjectContext:context];
entity.orderValue = i;
entity.data = [NSString stringWithFormat:#"This is some data: %d", i];
if(i < 10) {
[aTop addObjectsObject:entity];
[entity addTopObject:aTop];
} else if (i < 20) {
[bTop addObjectsObject:entity];
[entity addTopObject:bTop];
} else {
[cTop addObjectsObject:entity];
[entity addTopObject:cTop];
}
}
// Save the context
NSError *error = nil;
[context save:&error];
// A predicate to match against the top objects
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name BEGINSWITH %#", #"This is"];
// A predicate to match against the to-many objects
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"ANY objects.order < %d", 5];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"TopEntity" inManagedObjectContext:context]];
[fetch setPredicate:predicate];
NSArray *result = [[context executeFetchRequest:fetch error:&error] filteredArrayUsingPredicate:secondPredicate];
for(TopEntity *entity in result) {
NSLog(#"entity name: %#", entity.name);
}
So, essentially you can just wrap the results of your fetch request with another predicate and use the ANY keyword.
I've got no idea how efficient that is, but it works for this case. Running the above will output "This is Your Name" i.e. it matches the first TopEntity.
I don't think there's a way to limit to n results in a predicate, only at the fetch request level.
Aside from referencing the last n items in a relationship as you mentioned, you might try a boolean attribute "lastN" and flip them on/off when you curate the order of the list (say, during user-initiated sort or drag-and-drop reordering).
Alternatively, you could create a separate fetch request for each searched thing that sorts by your sort key, ordered descending, and is limited (via -setFetchLimit: ) to n results.
Tracking this as a relationship or an attribute is somewhat "messy" whereas the fetch limit is more expensive (because of multiple round trips). If your reordering is done by one-off user actions, it might be better performance-wise to use the relationship or attribute approach since the work is amortized rather than done all at once in a series of fetches. I haven't found a better way myself and will follow this one closely. :-)

Resources