How to fetch core data relationships in one single NSFetchRequest?

12,362

Solution 1

To avoid confusion, I'd suggest renaming item to items to reflect that it's a to-many relationship. Same goes for subcategory.

EDIT2 - You are asking to get the Subcategory and Item objects that have a certain categoryName, correct? The query you've written above doesn't do that at all.

First, yes, you will need 2 queries, as you can only fetch 1 Entity type at a time.

Second, let's re-write your syntax because right now it's super dangerous.

do {
  let predicate = NSPredicate(format: "categoryName == %@", "yourCategoryHere")
  let fetchSubcategory = NSFetchRequest(entityName: "Subcategory")
  fetchSubcategory.predicate = predicate
  if let subCategoryResults = try context.executeFetchRequest(fetchSubcategory) as? [Subcategory] {
    //do stuff
  }
  let fetchItem = NSFetchRequest(entityName: "Item")
  fetchItem.predicate = predicate
  if let itemResults = try context.executeFetchRequest(fetchItem) as? [Item] {
    //do stuff
  }
}catch {
  print(error)
}

Solution 2

If you fetch Categories you will get an array of Category objects. Each of those objects will have a property, subcategory, which is a set containing all the related Subcategory objects. And each of those objects will have a property, item, which is a set containing all the Item objects related to that Subcategory. The related objects are "nested" within the Category and Subcategory objects.

When you fetch the Categories, you do not need to specify a predicate to get the related objects. They will be automatically fetched as soon as you access the subcategory or item properties. So, for example, if myCategory is a Category object you have fetched,

let mySubcategories = myCategory.subcategory
for subcat in mySubcategories {
    print("\(subcat)")
}

should print each of the related subcategories.

If those properties are nil, it might be that you have not yet established the relationships. That is normally done when you first create the objects. For example, if you create a Subcategory with categoryName = "Badaue", you might establish the relationship with the Category with name = "Badaue" like this:

let newSubcategory = ....
newSubcategory.categoryName = "Badaue"
let fetchCategory = NSFetchRequest(entityName: "Category")
fetchCategory.predicate = NSPredicate(format: "name == %@", newSubcategory.categoryName)
let categories = try! appDel.managedObjectContext.executeFetchRequest(fetchNote) as? [Category]
if (categories!.count > 0) {
    let firstCategory = categories[0]
    newSubCategory.category = firstCategory
} else {
    // no existing Category with the correct name, so create one....
    let newCategory = ....
    // ... and then establish the relationship to the Subcategory
    newSubCategory.category = newCategory
}

Solution 3

Should try like this: I've two entities 1. Trigger 2. Numbers Relation is Many-to-Many :- A trigger could have one or many Mobile Numbers attached, whereas the number may be part of more than one triggers in the Trigger table.

Step: 1

enter image description here

Step: 2

enter image description here

Now you would have situation Like this:

enter image description here

enter image description here

The code generated by Xcode is like this.

import Foundation
import CoreData


extension MobileNumber {

@nonobjc public class func fetchRequest() ->  NSFetchRequest<MobileNumber> {
    return NSFetchRequest<MobileNumber>(entityName: "MobileNumber");
}

@NSManaged public var mNumber: String?
@NSManaged public var title: String?
@NSManaged public var triggers: NSSet?

}

// MARK: Generated accessors for triggers
extension MobileNumber {

@objc(addTriggersObject:)
@NSManaged public func addToTriggers(_ value: Trigger)

@objc(removeTriggersObject:)
@NSManaged public func removeFromTriggers(_ value: Trigger)

@objc(addTriggers:)
@NSManaged public func addToTriggers(_ values: NSSet)

@objc(removeTriggers:)
@NSManaged public func removeFromTriggers(_ values: NSSet)

}

Similarly, you will have generated classes for other entities as well. I have defined a function for fetching an Entity with UUID, which is always unique for all the rows of my Trigger table.

 func fetchTriggerWithUUID(identifier: String) -> [Trigger]
  {
    let managedObjectContext =  (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Trigger")
    let predicate = NSPredicate(format: "uuid = %@", identifier)

    fetchRequest.predicate = predicate

    do {

        let results = try managedObjectContext.fetch(fetchRequest) as! [Trigger]

        return results

    } catch let error as NSError {

        print(error)

    }

    return []
}

Now wherever I need to play with data of any specific [row] or you can even with a set of [rows], this is so simple here in my case.

    let result = AppDelegate.getAppDelegate().fetchTriggerWithUUID(identifier: notifIdentifier).first

        numbers.removeAll()

        for num in (result?.numbers)!
        {
            let numHere = num as! MobileNumber
            numbers.append(numHere.mNumber!)


        }

        let messageHere = "Wanted to inform you! \n" +  (result?.title)! + "\n" + (result?.desc)!

You can do with as much entities you have in your [.xcdatamodelID] file.

Share:
12,362
Marco Almeida
Author by

Marco Almeida

I am passionate for app and web design development and want to bring that passion to your business. My mission is to offer outstanding, innovative app or web solutions for any demographic or clientele. My education in developing has given me a solid foundation in creating app solutions. In addition, I have over 4 years been working on app development which has enhanced my technical and professional approach to my projects. I am highly proficient with both Objective-C and Swift languages and Xcode, the software used to create iOS/OS X applications and I use RapidWeaver for website creation, and I am dedicated to staying informed of current trends and developments in the field.

Updated on June 29, 2022

Comments

  • Marco Almeida
    Marco Almeida almost 2 years

    I have a core data app with the model setup just like the image below:

    Core Data Model

    The relationships are setup like this:

    Category<-->>Subcategory<-->>Item

    Now, is it possible in one single fetch to get objects from Subcategory and Item entities that have the same attribute called categoryName? I think it can be done by using relationships, but the code I am using is not working which is:

    let fetchNote = NSFetchRequest(entityName: "Category")
        fetchNote.predicate = NSPredicate(format: "ANY subcategory.name == %@ AND item.name == %@", "Badaue", "Badaue")
        let notes = try! appDel.managedObjectContext.executeFetchRequest(fetchNote) as? [Category]
        print(notes)
    
  • Marco Almeida
    Marco Almeida almost 8 years
    The app is halting and when I set an exception breakpoint it points to this line of code: let notes = try! appDel.managedObjectContext.executeFetchRequest(fetchNote) as? [Category] but it outputs nothings on the log screen.
  • Marco Almeida
    Marco Almeida almost 8 years
    And if I disable the exception breakpoint the output log is: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
  • sschale
    sschale almost 8 years
    It is as I suspected - you can't query a to-many relationship of a to-many relationship. You need to write two separate queries.