Search code examples
swiftcore-datanspredicate

How do I create a FetchRequest predicate for a child entity where the parent entity satisfies a NSPredicate


I have two Core Data entities: Parent and Child. Parent has a property children for the set of children

Each Child has a parent property linking it to its parent, and an age property. (My data is more complex than this, but that's not relevant to the question)

I have a NSPredicate for the Parent entity that selects a set of parents and a NSPredicate for the Child entity that selects entities based on age

What I want to do is construct a single NSPredicate for a FetchRequest<Child> that returns the set of Child entities that meets both of the individual predicates

I know I can retrieve an array of Parent entities that meet their predicate ('parents') then have a child predicate something like NSPredicate(format: "parent IN %@", parents) and include that in a compound AND with the child predicate, but there could be hundreds of parents meeting their predicate and it looks to me a but of a kludge to have two separate queries.

The FetchRequest needs to be FetchRequest<Child> as it's used in a SwiftUI view, so I can't just build an array of the results.

I suspect that I can do this with a SUBQUERY predicate but I can't work out how to embed the parent predicate into the SUBQUERY. I get errors like Problem with subpredicate TRUEPREDICATE in the predicate string for the Parent entity.

Hopefully that makes sense? Any ideas or suggestions?

Thanks


Solution

  • If the relationship from Child to Parent is to-one, then there is no need for SUBQUERY. In the format string for the parent predicate (which you can obtain with predicateFormat), you can replace each parent attribute name in the the string with the keypath to that attribute from the child, ie

    attribute becomes parent.attribute

    The resulting predicate can then be combined with the child predicate using NSCompoundPredicate's andPredicateWithSubpredicates.

    If parsing the predicateFormat is impractical, another option is to use NSFetchRequestExpression to pass the results of one fetch as an argument to another (it gets converted to an SQL subselect). Something like this:

    let parentFetch = Parent.fetchRequest()
    parentFetch.predicate = parentPredicate // your existing predicate to fetch the parent objects
    parentFetch.resultType = .managedObjectIDResultType
    let ctxtExp = NSExpression(forConstantValue: managedObjectContext)
    let fetchExp = NSExpression(forConstantValue: parentFetch)
    let fre = NSFetchRequestExpression.expression(forFetch: fetchExp, context: ctxtExp, countOnly: false) 
    let childFetch = Child.fetchRequest()
    let subPredicate = NSPredicate(format:"parent IN %@",fre)
    let combinedPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [subPredicate, childPredicate]) // childPredicate is you existing predicate to fetch the correct Child objects
    childFetch.predicate = combinedPredicate
    ... proceed to execute the fetch against the relevant context