Created
August 5, 2015 15:22
-
-
Save kenthumphries/438dabf757dfbb5ddae6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <CoreData/CoreData.h> | |
@implementation NSManagedObjectContext (FetchAggregates) | |
/** | |
* This method groups CoreData objects based on a list of user defined properties/relationships. | |
* Properties contain values, and Relationships are properties that specifically point to another NSManagedObject. | |
* Note: properties and relationships must be separated out as aggregate operations (count, max, min, avg) cannot be performed on relationships. | |
* | |
* NOTE: A common use for this method is finding duplicate NSManagedObject entities where 'duplicate' means exactly matching certain properties/relationships. | |
* | |
* This method finds all 'unique' groups of objects, where being unique means all objects in the group share the same values for the user defined properties/relationships. | |
* That is, if any two (or more) objects have the exact same value for each of the user defined properties/relationships | |
* (ie obj1.field1 == obj2.field1 && obj1.fieldN == obj2.fieldN), those two (or more) objects will be grouped together (obj1 & obj2 make one group). | |
* | |
* This method performs a count on the grouped objects and returns this count along with the property/relationship values that matched, making the group 'unique'. | |
* Example: | |
* Using a CarReservation entity, find any groups of CarReservations that have the same lastName for the same booked Car. | |
* This shows which Cars the user has decided to book more than once. | |
* Call the method with the input parameters: | |
* propertyNames = @["lastName"] | |
* relationshipNames = @["car"] | |
* entityName = @"CarReservation" | |
* The returned array could contain: | |
* @[ @{ "count"=2, "lastName"="Smith", "car"="<NSManagedObjectID:p1234>" }, | |
* @{ "count"=1, "lastName"="Brown", "car"="<NSManagedObjectID:p4321>" }, | |
* @{ "count"=1, "lastName"="Smith", "car"="<NSManagedObjectID:p4321>" }, | |
* @{ "count"=1, "lastName"="Winehouse", "car"=nil } ] | |
* | |
* This can be interpreted as the user having: | |
* 2 reservations for car 'p1234' under name Smith (this car booked > once by Ms Smith) | |
* 1 reservation for car 'p4321' under name Smith | |
* 1 reservation for car 'p4321' under name Brown | |
* 1 reservation under name Winehouse, but no car (this is an invalid CarReservation object!) | |
* | |
* | |
* @param propertyNames NSArray of property name strings (any property of the entity EXCLUDING relationships) to group by. Must be non-empty. | |
* @param relationshipNames NSArray of relationship name strings (properties pointing to another NSManagedObject) to group by. Optional. | |
* @param entityName Name of entity for which to count aggregate properties | |
* @param context Context to fetch properties from | |
* | |
* @return NSArray of dictionaries containing the count and property values shared for each object group | |
*/ | |
- (NSArray *)countGroupedByProperties:(NSArray *)propertyNames | |
groupedByRelationships:(NSArray *)relationshipNames | |
forEntity:(NSString *)entityName | |
{ | |
NSAssert(entityName.length, @"Cannout count aggregate properties if no entityName passed in"); | |
NSAssert(propertyNames.count, @"Cannot count aggregate properties if no propertyNames passed in"); | |
if (!propertyNames.count || !entityName.length) | |
{ | |
return nil; | |
} | |
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName]; | |
// Create the expression that gets the count of entities which are unique according to groupBy propertyNames | |
NSString *expressionString = [NSString stringWithFormat:@"%@:(%@)", @"count", propertyNames.firstObject]; | |
NSExpression *countExpr = [NSExpression expressionWithFormat:expressionString]; | |
NSExpressionDescription *countExprDesc = [[NSExpressionDescription alloc] init]; | |
countExprDesc.name = @"count"; | |
countExprDesc.expression = countExpr; | |
countExprDesc.expressionResultType = NSInteger64AttributeType; | |
// For each propertyName, get the corresponding NSAttributeDescription | |
NSMutableArray *desiredEntityProperties = [[NSMutableArray alloc] init]; | |
NSDictionary *allEntityProperties = [self.persistentStoreCoordinator.managedObjectModel.entitiesByName[entityName] propertiesByName]; | |
for (NSString *propertyName in propertyNames) | |
{ | |
NSAttributeDescription *propertyAttribute = allEntityProperties[propertyName]; | |
NSAssert(propertyAttribute, @"propertyName '%@' not found on entity '%@'. Returning nil", propertyName, entityName); | |
if (!propertyAttribute) | |
{ | |
return nil; | |
} | |
[desiredEntityProperties addObject:propertyAttribute]; | |
} | |
for (NSString *relationshipName in relationshipNames) | |
{ | |
NSRelationshipDescription *relationshipAttribute = allEntityProperties[relationshipName]; | |
NSAssert(relationshipAttribute, @"relationshipName '%@' not found on entity '%@'. Returning nil", relationshipName, entityName); | |
if (!relationshipAttribute) | |
{ | |
return nil; | |
} | |
[desiredEntityProperties addObject:relationshipAttribute]; | |
} | |
fetchRequest.propertiesToGroupBy = desiredEntityProperties; | |
fetchRequest.propertiesToFetch = [desiredEntityProperties arrayByAddingObject:countExprDesc]; | |
fetchRequest.resultType = NSDictionaryResultType; | |
NSError *error; | |
NSArray *entityPropertyCounts = [self executeFetchRequest:fetchRequest error:&error]; | |
return entityPropertyCounts; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment