6

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)
sschale
  • 5,168
  • 3
  • 29
  • 36
Marco Almeida
  • 1,285
  • 3
  • 17
  • 39
  • What is not working? – koen Jun 02 '16 at 16:35
  • Please clarify which entity you are trying to fetch - each fetch will return an array containing *either* Category objects, *or* Subcategory objects, *or* Item objects, but *not* a mix of different entities. – pbasdf Jun 02 '16 at 21:14
  • Sure. I want to fetch the Category entity and get results from Subcategory and Item entities that have the attribute called 'categoryName' the value "Badaue", for example. – Marco Almeida Jun 02 '16 at 21:16
  • Sorry, I'm still confused: if you fetch the Category entity, you will get results from the Category entity. You can *limit* the results using a predicate, but you cannot use a predicate to "get results" from a different entity. – pbasdf Jun 02 '16 at 22:29
  • Then what´s the use of having relationships? I though I could get data through the relationships. – Marco Almeida Jun 02 '16 at 22:34
  • @MarcoAlmeida See my answer below. Sorry if I've missed the point of your question, but I hope it clarifies... – pbasdf Jun 03 '16 at 13:16

3 Answers3

2

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)
}
sschale
  • 5,168
  • 3
  • 29
  • 36
  • 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 Jun 02 '16 at 18:37
  • 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' – Marco Almeida Jun 02 '16 at 18:41
  • 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. – sschale Jun 02 '16 at 21:17
1

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
}
pbasdf
  • 21,386
  • 4
  • 43
  • 75
1

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.

Asim Khan
  • 508
  • 1
  • 7
  • 21