Search code examples
ioscore-datansmanagedobjectnsmanagedobjectcontextnsfetchrequest

Performing multiplication (aggregation) with CoreData: how to?


Following a fantastic tutorial by Jeff Lamarche, I'm trying to aggregate data for a specific subclass of NSManagedObject.

This is the scenario. I created a class named Product that extends NSManagedObject class. Product class has three properties like the following:

@property (nonatomic, retain) NSString* name;
@property (nonatomic, retain) NSNumber* quantity;
@property (nonatomic, retain) NSNumber* price;

I also created a category, called Product+Aggregate, where I perform a sum aggregation. In particular, following Jeff tutorial, I managed the sum for quantity attribute.

+(NSNumber *)aggregateOperation:(NSString *)function onAttribute:(NSString *)attributeName withPredicate:(NSPredicate *)predicate inManagedObjectContext:(NSManagedObjectContext *)context
{
    NSString* className = NSStringFromClass([self class]);

    NSExpression *ex = [NSExpression expressionForFunction:function 
        arguments:[NSArray arrayWithObject:[NSExpression expressionForKeyPath:attributeName]]];

    NSExpressionDescription *ed = [[NSExpressionDescription alloc] init];
    [ed setName:@"result"];
    [ed setExpression:ex];
    [ed setExpressionResultType:NSInteger64AttributeType];

    NSArray *properties = [NSArray arrayWithObject:ed];
    [ed release];

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setPropertiesToFetch:properties];
    [request setResultType:NSDictionaryResultType];

    if (predicate != nil)
        [request setPredicate:predicate];

    NSEntityDescription *entity = [NSEntityDescription entityForName:className
        inManagedObjectContext:context];
    [request setEntity:entity];

    NSArray *results = [context executeFetchRequest:request error:nil];
    NSDictionary *resultsDictionary = [results objectAtIndex:0];
    NSNumber *resultValue = [resultsDictionary objectForKey:@"result"];

    return resultValue;
}

This class method is called as follow from a specific UIViewController:

NSNumber *totalQuantity = [Product aggregateOperation:@"sum:" onAttribute:@"quantity" withPredicate:nil inManagedObjectContext:self.context];

The code works well. In fact, if I have say 3 product

NAME         QUANTITY      PRICE
PRODUCT 1    2             23.00 
PRODUCT 2    4             12.00
PRODUCT 3    1             2.00

The aggregateOperation method returns 7 as expected.

Now I would have one more step. Modifying that method, I need to return the total cost for product order. In other words, I need to calculate QUANTITY*PRICE value for each product and finally return the TOTAL.

Could you suggest me the right way? Thank you in advance.

EDIT This is the new code I use after Cyberfox suggestion but unfortunately it doesn't work.

NSString* className = NSStringFromClass([self class]);

NSArray *quantityPrice = [NSArray arrayWithObjects: [NSExpression expressionForKeyPath:@"quantity"], [NSExpression expressionForKeyPath:@"price"], nil];

NSArray *multiplyExpression = [NSArray arrayWithObject:[NSExpression expressionForFunction:@"multiply:by:" arguments:quantityPrice]];

NSExpression *ex = [NSExpression expressionForFunction:function arguments:multiplyExpression];

NSExpressionDescription *ed = [[NSExpressionDescription alloc] init];
[ed setName:@"result"];
[ed setExpression:ex];
[ed setExpressionResultType:NSInteger64AttributeType];

// same as before

Solution

  • For those interested in, I found the solution for the problem above.

    Here's the code:

    static NSString* exprName1 = @"val1";
    static NSString* exprName2 = @"val2";
    
    NSString *className = NSStringFromClass([self class]); 
    
    NSExpression *quantityPathExpression = [NSExpression expressionForKeyPath:firstAttribute]; //e.g. quantity    
    NSExpression *unitaryPricePathExpression = [NSExpression expressionForKeyPath:secondAttribute]; //e.g. price
    
    NSExpressionDescription *quantityED = [[NSExpressionDescription alloc] init];
    [quantityED setName:exprName1];
    [quantityED setExpression:quantityPathExpression];
    [quantityED setExpressionResultType:NSDictionaryResultType];    
    
    NSExpressionDescription *unitaryPriceED = [[NSExpressionDescription alloc] init];
    [unitaryPriceED setName:exprName2];
    [unitaryPriceED setExpression:unitaryPricePathExpression];
    [unitaryPriceED setExpressionResultType:NSDictionaryResultType];
    
    NSArray *properties = [NSArray arrayWithObjects:quantityED, unitaryPriceED, nil];
    [quantityED release];
    [unitaryPriceED release];
    
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setPropertiesToFetch:properties];
    [request setResultType:NSDictionaryResultType];
    
    if (predicate != nil)
       [request setPredicate:predicate];
    
    NSEntityDescription *entity = [NSEntityDescription entityForName:className inManagedObjectContext:context];
    [request setEntity:entity];
    
    NSError* error = nil;
    NSArray *results = [context executeFetchRequest:request error:&error];
    
    if(error != nil)
    {
       NSLog(@"An error occurred: %@", [error localizedDescription]);
       abort();
    }
    
    float total = 0;
    for (NSDictionary *resultDict in results) 
    {
       NSNumber* quantityNumber = [resultDict valueForKey:exprName1];
       NSNumber* unitaryPriceNumber = [resultDict valueForKey:exprName2];
    
       int moltVal = [quantityNumber intValue]*[unitaryPriceNumber intValue];
    
       total += moltVal;
    }    
    
    return [NSNumber numberWithInt:total];
    

    P.S. to use it create a Class method that returns a NSNumber and accepts as parameters the managed context and 2 attributes (NSString) where you want to perform data retrieving.

    Hope it helps!