NSFetch Request return sum over computed properties

Hi,


I have an iOS App and a Core Data Model with three entities.


Entity A:
     name: String
     value: Int64

Entity B:
     name: String
  
Entity C:
     relationship1: A
     relationship2: B
     value: Int64


Now I want to fetch all Entities of type "C" wich have a relationship to "A" for a given name of A

Instead of a list of those Cs I'd like to return a number: The sum of (C.value * C.relationship1.value) for all fetched Cs. (line 07)

Because I have no idea how to do this using a FetchRequest, I thought I could use a computed property for C ("product") - which works fine - but I can't access it during the FetchRequest. I think it crashes in the line with the "right here" comment.


*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'keypath sum not found in entity <NSSQLEntity C id=2>'


import Foundation
import CoreData
@objc(C)
public class C: NSManagedObject {
    @objc dynamic var product : Int64 {
        get {
            if let test = self.relationship1.value {
                return test * self.value
            } else {
                return 0
            }
        }
    }
}


let fetchRequest = NSFetchRequest<NSDictionary>(entityName: "C")
fetchRequest.resultType = .dictionaryResultType


let sumExpressionDesc = NSExpressionDescription()
sumExpressionDesc.name = "returnValue"

let predicate = NSPredicate(format: "C.relationship1.name == %@", name)

let expression = NSExpression(forKeyPath: #keyPath(C.product)) //right here
sumExpressionDesc.expression = NSExpression(forFunction: "sum:", arguments: [expression])
sumExpressionDesc.expressionResultType = .integer32AttributeType

fetchRequest.predicate = predicate
fetchRequest.propertiesToFetch = [sumExpressionDesc]

do {
    let results = try managedObjectContext.fetch(fetchRequest)
    let resultDict = results.first!
    if let value = resultDict["returnValue"], let count = value as? Int {
        return count
    }
} catch {
    print(error)
}

return -1


I'm glad about any refinements as well as for a solution.

Thanks in advance!

Replies

If you're using the SQLite persistent store, then that code won't work at all. The limitation is caused by CoreData pushing the processing of the request to the database engine but the database engine can't see the "software implemented" properties.


If you want to compute something from "software implemented" properties, you have to use the regular Swift approach to calculate the value. You could use fetches to compute partial results that don't involve the software implemented properties, though.


DIsclaimer: Theoretically, you could implement an incremental store that supported both "software implemented" properties and persisted properties, But that's a fair amount of work and you may just end up demonstrating why the CoreData engineers didn't do it that way. 😟

If you're using the SQLite persistent store, then that code won't work at all. The limitation is caused by CoreData pushing the processing of the request to the database engine but the database engine can't see the "software implemented" properties.

This took me several hours of searching to find, thank you for the explanation! For reference, I was attempting to create a SortDescriptor based on a computed property on a CoreData entity.

Here are a few breadcrumbs for the next person: