I am used to working in C# using the linq extensions (list.select(...).where(...) ext), and I was wondering if there was some way of doing the same sort of thing in Objective-C. This would save me from building a number of rather complicated queries using Core Data, which is great for some things, but perhaps not the best for complex queries (or maybe I'm just uninformed).
Is there some kind of equivalent for linq in Objective-C/Core Data?
EDIT: More specifically, I would like to count the number of elements that fit some criteria. Say my model has a field called date. I am trying to select the distinct dates, and then calculate how many of each date there are. In SQL this would be like a group by, and a COUNT aggregate.
Your question goes from very general ("linq equivalent?") to very specific (computing count by date). I'll just answer your specific question.
Unfortunately, NSArray doesn't have a built-in map or select method, but it does offer NSCountedSet, which will compute what you want:
NSCountedSet *dateSet = [NSCountedSet set];
for (id thing in array) {
[dateSet addObject:[thing date]];
}
for (NSDate *date in dateSet) {
NSLog(#"There are %d instances of date %#", [dateSet countForObject:date], date);
}
Change predicate , and "Date" keys with your props
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"child" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:#"Date"]];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(start <= %# ) and (completion < 100)",sda ];
[fetchRequest setPredicate:predicate];
int c = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil];
Here was something posted with comes close
filtering NSArray into a new NSArray in objective-c
Anyway AFAIK you don't have some sort of linq in Objective-C but you have. Arrays and Blocks. And Blocks are functions. So you can really filter on anything in there.
Of course, Cora Data has many functions to make complex queries:
In example to get sum of elements, you have two major ways:
first - get your data to NSSet or NSArray and use #sum operator:
//assume that `childs` are NSArray of your child entities and ammount is attribute to sum, and has attributes start (date) and completion (integer)
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"child" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"(start <= %# ) and (completion < 100)", dzis];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
NSError *error = nil;
if ([afetchedResultsController performFetch:&error]) {
NSArray *childs = afetchedResultsController.fetchedObjects;
NSNumber *sum=[childs valueForKeyPath:"#sum.ammount"];
}
second is using specific fetch for specific value with added NSExpressionDescription with a sum. This way is harder but better for larger db's
suppose if you have an array of your model objects, you could that with the following statement,
NSArray *distintDatesArray = [array valueForKeyPath:#"#distinctUnionOfObjects.date"];
for ( NSDate *date in distintDatesArray)
{
NSLog (#"Date :%# ,count : %d",date,[[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"date = %#",date]] count]);
}
This will have same effect as the group by query.
Related
I have an Entity called AcademicYear that has a relationship 1:N with an entity called Subject which has a 1:N relationship with an Entity called SubjectLanguage:
AcademicYear<--->>Subject<--->>SubjectLanguage
I would like to filter the result of a query on AcademicYear in order to be able to gather Subject and SubjectLanguage already filtered by the parameter language.
On runtime i'll use such expression:
academicYear.hasSubjects.hasLanguages.subjectName
and i would like to be sure they are of a specific language filtered by the starting query.
i tried using during the fetch the following predicate (with no result):
NSPredicate *predicateLang = [NSPredicate predicateWithFormat:#"ALL hasSubjects.hasLanguages.language like %#", language];
NEW (Update)
Due to Marcus suggestion i changed the fetch request in this way:
NSError *error;
NSString * language = [[NSLocale preferredLanguages] objectAtIndex:0];
NSString * yearString = [NSString stringWithFormat:#"%d",year];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"belongToAcademicYear.yearId == %# and ANY hasLanguages.language CONTAINS[cd] %#", yearString, language];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Subject"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return [self arrayToMutableArrayConverter:fetchedObjects];
}
then i tested this solution with the following code:
NSArray * currentSubjects = [self.coordinator fetchSubjectThatBelongsToAcademicYear:1];
Subject * currentSubject = currentSubjects[0];
NSUInteger count = currentSubject.hasLanguages.count;
XCTAssertEqual(count, 1, #"riscontrati troppi languages. non è stato fatto il filtro su language");
I found the filter did not work correctly and i found two languages while i expect one. I'm pretty sure the issue is related with the predicate.
Any support is appreciated
kind regards
Nicolò
You should reverse this a little bit. Instead of fetching AcademicYear and then trying to reach over to SubjectLanguage, search for Subject.
Build your NSFetchRequest against Subject and change the predicate to:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"academicYear == %# and ANY languages.language CONTAINS[cd] %#", academicYear, language];
Some comments:
Your relationships should not start with "has". They should just be academicYear and languages. Singular for to-one and plural for to-many.
Your entity SubjectLanguage should not have a property called language. Something that relates to the entity can be less descriptive. name would work well so that it is the "name of the SubjectLanguage".
Update
It worked exactly right. You found the Subject that has the language and the year you are looking for. It returned that Subject and ALL of its relationships. Since there is more than one language associated with the subject it gave you access to all of the languages.
The predicate is correct, your test is flawed. Your test should be insuring that one of the languages is the one you are looking for.
I have Boolean values in a entity, the values are all show 0.
The values are fetched and no matter what I always get back a TRUE value.
Here is the code, the problem is probably at the last 2 lines of code.
What am I doing wrong?
-(NSInteger)getStatus:(NSString*)nameID;
{
**//Fetch Request**
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity =
[NSEntityDescription entityForName:#"status"
inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"id_Name = %#", nameID];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
}
**//Values**
status *ENT_status;
ENT_status = [fetchedObjects objectAtIndex:0];
//Either way, both these lines return YES when it would be NO
//------------------------------------------------------------------
BOOL one = ENT_status.status_one;
NSNumber *two = [NSNumber numberWithBool:ENT_status.status_two];
By default, boolean values are represented as NSNumber objects in the managed objects.
So you can either use it as it is:
NSNumber *one = ENT_status.status_one;
or convert it to a plain (scalar) BOOL:
BOOL one = [ENT_status.status_one boolValue];
(In your code, the pointer ENT_status.status_one is always interpreted as YES.)
Alternatively, you can use the option "Use scalar properties for primitive data types"
when creating the managed object subclass in Xcode.
I know its a bit late and probably no one is using Objective-C anymore but i came across this problem and what worked for me was:
to retrieve a boolean value from object:
BOOL myBool = [[myManagedObject valueForKey:#"myKey"] boolValue];
and to set the new value:
myManagedObject.myKey = #YES;
Scenario:
I have an expense tracking iOS Application and I have a view controller called "DashBoardViewController" (table view controller - with FRC) which would basically categorize my expenses/incomes for a given week, a month, or year and display it as the section header title for example : (Oct 1- Oct 7, 2012) and it shows expenses/incomes ROWS and related stuff according to that particular week or month or year.
My Question:
What I want to accomplish is :
Suppose I save 3 new expenses with SAME category named "Auto" on three different dates(11 nov, 14 nov, 16 nov, 2012 respectively).
In my view controller, I want to display that category "Auto" as a row in table view but it should appear only as ONE ROW and NOT THREE TIMES as I saved three expenses (with category "Auto") and the total amount should be added up for all the 3 expenses I saved (for that particular category). Something like the following screenshot.
I have written some code bit it gives me THREE ROWS for the SAME CATEGORY and not what I actually want (ONE ROW for SAME CATEGORY) and I don't know how I would calculate the total for them? Should be something related to NSPredicate here or fetched results controller.
Any help would be highly appreciated.
- (void)userDidSelectStartDate:(NSDate *)startDate andEndDate:(NSDate *)endDate
{
AppDelegate * applicationDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSManagedObjectContext * context = [applicationDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Money" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicateDate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", startDate, endDate];
// Edit the sort key as appropriate.
typeSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"type" ascending:YES]; // type refers to an expense or income.
dateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
if(self.choiceSegmentedControl.selectedIndex == 0) // UISegment Control for "Sorting Category"
{
NSPredicate *predicateCategory = [NSPredicate predicateWithFormat:#"cat == %#", #""];
NSArray * subPredicates = [NSArray arrayWithObjects:predicateCategory, predicateDate, nil];
NSPredicate * compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
[fetchRequest setPredicate:compoundPredicate];
choiceSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"cat" ascending:NO];
}
NSArray * descriptors = [NSArray arrayWithObjects:typeSortDescriptor, dateSortDescriptor, choiceSortDescriptor, nil];
[fetchRequest setSortDescriptors:descriptors];
[fetchRequest setIncludesSubentities:YES];
if(_fetchedResultsController)
{
[_fetchedResultsController release]; _fetchedResultsController = nil;
}
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"type" cacheName:nil];
_fetchedResultsController.delegate = self;
NSError *anyError = nil;
if(![_fetchedResultsController performFetch:&anyError])
{
NSLog(#"error fetching:%#", anyError);
}
__block double totalAmount = 0;
[[self.fetchedResultsController fetchedObjects] enumerateObjectsUsingBlock: ^void (Money *money, NSUInteger idx, BOOL *stop) {
totalAmount += [[money amount] doubleValue];
}];
[fetchRequest release];
//Finally you tell the tableView to reload it's data, it will then ask your NEW FRC for the new data
[self.dashBoardTblView reloadData];
self.startDate = startDate;
self.endDate = endDate;
}
I thought to use NSDictionaryResultType but that's giving a problem with the FRC i have used ( for section names, filling up the table view etc.)
The code where I loop through the FRC gives me the total amount (for income and expenses) BUT I want the total amount for each category (example: total for category "Auto", total for category "Entertainment"). Please help me, I am totally stucked up here.
I don't think you can massage your FRC into returning the kind of objects you need. NSPredicate just filters the kind of objects to return it does not create new ones from the data.
However, you can fetch the your money objects filtered by the date and then calculate the data from the array of money objects using KVC Collection Operators like so:
NSArray *moneyObjectsFilteredbyDate = [self.fetchedResultsController fetchedObjects]
NSArray *categoryStrings = [moneyObjectsFilteredbyDate valueForKeyPath:#"#distinctUnionOfObjects.cat"];
NSArray *sortedCategoryStrings = [categoryStrings sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSMutableArray *aggregatedDataObjects = [NSMutableArray array];
for (NSString *aCategoryString in sortedCategoryStrings) {
NSPredictate *categoryPredicate = [NSPredicate predicateWithFormat:#"cat == %#", aCategoryString];
NSArray *moneyWithThisCategory = [moneyObjectsFilteredByDate filteredArrayUsingPredicate:categoryPredicate];
NSNumber *sum = [moneyWithThisCategory valueForKeyPath:#"#sum.amount"];
[aggregatedDataObjects addObject:#{#"category" : aCategoryString, #"sum" : sum, #"individualExpenses" : moneyWithThisCategory}];
}
Of course, you could do parts of the in the method where you configure the table cell (like calculating the sum itself), but I hope it gives you an idea. But I don't think you can use the predicate in a form of an SQL query or similar to create new data structure.
Something else you could do: Make the category an individual object of your Core Data model and add a relationship between moneyobjects and Category objects. Then you can just fetch category objects. Although you would then have to filter the expense for a category by the dates.
I have three entities A, B, C.
The relationship between them is: A <-->>B, B<-->C.
A has a attribute called 'type'.
A and B relationship is a2b, B and C relationship is b2c. c_array is list of C object.
What I am trying to do is using NSPredicate to filter A by C and A's attribute 'type'.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"A" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSMutableArray *parr = [NSMutableArray array];
for (C *c in c_array) {
[parr addObject:[NSPredicate predicateWithFormat:#"ANY a2b.b2c = %#", c]];
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:[NSCompoundPredicate orPredicateWithSubpredicates:parr], [NSPredicate predicateWithFormat:#"type = %i", 0], nil]];
[fetchRequest setPredicate:predicate];
But what I get is not I expected. So I tried other as well.
predicate = [NSPredicate predicateWithFormat:#"type=%i AND (0!=SUBQUERY(a2b,$a2b,$a2b.b2c IN %#).#count)", 0, c_array];
Unexpected result happened again! Can somebody help me out? T T
Sounds like you want to do it the other way around. You already have the C's, and based on the relationships you've described, your C class would have a B property, and your B class would have an A property. So assuming you've used c2b and b2a something like this should work:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"c2b.b2a.type == %#",[NSNumber numberWithInt:0]];
NSArray *result = [c_array filteredArrayUsingPredicate:predicate];
Also I notice you're comparing the type attribute of A to an int in your predicate. Just looking for type==%i where i is 0 will return all objects that have no value in type. So either compare type to an NSNumber object or type.intValue to the int.
I have a Core Data entity which has a date attribute. I would like to write a predicate to extract all dates within a specific month, e.g. July, irrespective of year. How can this be achieved? Thanks
You can create a new method on your entity, which we'll call monthOfDate. This method simply uses [self dateAttribute] and NSDateComponents to extract what the month of the date is.
Then you can write a predicate that does:
//assuming 1-based month indexing
NSPredicate * julyEntities = [NSPredicate predicateWithFormat:#"monthOfDate = 7"];
The trick here is realizing that the left keypath of your predicate will result in a method invocation. So if you set the left keypath to "monthOfDate", it will end up invoking your monthOfDate method and using the return value of the method as the comparison value in the predicate. Neat!
can't comment yet so asking this way.
How did you end up implementing this? I tried adding a method in the managed object class but once the predicate fires it says it can't find the keypath monthOfDate?
Updated with both files:
.h file:
#property (nonatomic,retain) NSNumber *monthOfDate;
-(NSNumber *)monthOfDate;
.m file:
#synthesize monthOfDate;
-(NSNumber *) monthOfDate
{
NSDate *date = [self start];
NSDateComponents *components = [[NSCalendar currentCalendar]components: NSCalendarUnitMonth fromDate:date];
NSNumber *month = [NSNumber numberWithInteger:[components month]];
return month;
}
Update 2
The code above is in the auto-generated Event class (NSmanaged object). I might move my custom code to a category later but so far no model revisions are necessary.
Below is the FetchedResultsController (in a uiviewcontroller class) setup with mentioned predicate:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Event" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortfix = [[NSSortDescriptor alloc]initWithKey:#"timeStamp" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortfix,nil]];
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"client == %# AND monthOfDate = %d", clientMO,[NSNumber numberWithInteger:6]];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:sectionKeyPathProperty
cacheName:nil];
//sectionKeyPathProperty is a variable that let's me choose one of multiple transient properties I have created for creating relevant sections.
self.fetchedResultsControllerClient = theFetchedResultsController;
_fetchedResultsControllerClient.delegate = self;
return _fetchedResultsControllerClient;