Query Attributes from Subentities #69

Closed
opened 2025-12-29 15:23:29 +01:00 by adam · 18 comments
Owner

Originally created by @ghost on GitHub (Jul 20, 2016).

I'd like to query attributes from my entity and one of it's sub-entities. I've tried few tweaks, but with no luck. This is what I have so far:

        // Rank List
        guard let aggregate = CoreStore.queryAttributes(
            From(CDCrystal),
            Select("employeeId",
                "employee.firstName",
                "employee.lastName",
                .Sum("amount", As: "crystals")),
            Tweak { (fetchRequest) -> Void in
                fetchRequest.returnsObjectsAsFaults = false
                fetchRequest.includesSubentities = true
            }
            )
        else {
            return 0
        }

However in console I'm getting:

Crystals Relation Error: Employee with ID: Optional(3995) not found
.... (Same for other employees)

and in the result array I've got dictionaries with only two properties (employeeId and crystals).


Just to complete my idea - next I want to sort results like this:

        let ordered = aggregate.sort {
            if (($0["crystals"] as! NSNumber).integerValue == ($1["crystals"] as! NSNumber).integerValue) {
                // TODO: Sort By name
            } else {
                return ($0["crystals"] as! NSNumber).integerValue < ($1["crystals"] as! NSNumber).integerValue
            }
        }


Maybe I'm missing something. I'll appreciate any suggestions.

Originally created by @ghost on GitHub (Jul 20, 2016). I'd like to query attributes from my entity and one of it's sub-entities. I've tried few tweaks, but with no luck. This is what I have so far: ``` // Rank List guard let aggregate = CoreStore.queryAttributes( From(CDCrystal), Select("employeeId", "employee.firstName", "employee.lastName", .Sum("amount", As: "crystals")), Tweak { (fetchRequest) -> Void in fetchRequest.returnsObjectsAsFaults = false fetchRequest.includesSubentities = true } ) else { return 0 } ``` However in console I'm getting: > Crystals Relation Error: Employee with ID: Optional(3995) not found > .... (Same for other employees) **and in the result array I've got dictionaries with only two properties (employeeId and crystals).** --- Just to complete my idea - next I want to sort results like this: ``` let ordered = aggregate.sort { if (($0["crystals"] as! NSNumber).integerValue == ($1["crystals"] as! NSNumber).integerValue) { // TODO: Sort By name } else { return ($0["crystals"] as! NSNumber).integerValue < ($1["crystals"] as! NSNumber).integerValue } } ``` --- Maybe I'm missing something. I'll appreciate any suggestions.
adam added the fixedcorestore bug labels 2025-12-29 15:23:29 +01:00
adam closed this issue 2025-12-29 15:23:29 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Jul 20, 2016):

A couple of things I'd like to ask,

Crystals Relation Error: Employee with ID: Optional(3995) not found
This doesn't seem to be a CoreStore error message. Are you sure that the query method produces this message?

Another thing, is "amount" a property of "CDCrystal" or of the relationship employee?

You also mentioned

in the result array I've got dictionaries with only two properties.
Does that pertain to "employee.firstName" and "employee.lastName"?

@JohnEstropia commented on GitHub (Jul 20, 2016): A couple of things I'd like to ask, > Crystals Relation Error: Employee with ID: Optional(3995) not found > This doesn't seem to be a CoreStore error message. Are you sure that the query method produces this message? Another thing, is "amount" a property of "CDCrystal" or of the relationship employee? You also mentioned > in the result array I've got dictionaries with only two properties. > Does that pertain to "employee.firstName" and "employee.lastName"?
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

Thanks for quick response.

Crystals Relation Error: Employee with ID: Optional(3995) not found anymore. ...
Is shown once after restart, not in my code also, could be something in CoreData.

Yes "amount" is a property of "CDCrystal"

The result array contains dictionary with "employeeId" and "crystals", employee.firstName and employee.lastName are missing.

@ghost commented on GitHub (Jul 20, 2016): Thanks for quick response. > Crystals Relation Error: Employee with ID: Optional(3995) not found anymore. ... > Is shown once after restart, not in my code also, could be something in CoreData. Yes "amount" is a property of "CDCrystal" The result array contains dictionary with "employeeId" and "crystals", employee.firstName and employee.lastName are missing.
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

Ignore,

Crystals Relation Error: Employee with ID: Optional(3995) not found anymore. ...

In fact there are few crystals pointing to deactivated employees

@ghost commented on GitHub (Jul 20, 2016): Ignore, > Crystals Relation Error: Employee with ID: Optional(3995) not found anymore. ... In fact there are few crystals pointing to deactivated employees
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

I removed crystals with deactivated employees, in order to not have nil relations, but still I'm not able to query attributes of the existing related employees.

@ghost commented on GitHub (Jul 20, 2016): I removed crystals with deactivated employees, in order to not have nil relations, but still I'm not able to query attributes of the existing related employees.
Author
Owner

@JohnEstropia commented on GitHub (Jul 20, 2016):

Do all subentities of CDCrystal have "employee.firstName" and "employee.lastName" keypaths? I'm not sure if CoreData allows querying a keypath if it doesn't exist on any object

@JohnEstropia commented on GitHub (Jul 20, 2016): Do all subentities of CDCrystal have "employee.firstName" and "employee.lastName" keypaths? I'm not sure if CoreData allows querying a keypath if it doesn't exist on any object
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

After my last change yes, all CDCrystals are related to employee in CDEmployee

@ghost commented on GitHub (Jul 20, 2016): After my last change yes, all CDCrystals are related to employee in CDEmployee
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

I reversed the query:

 guard let aggregate = CoreStore.queryAttributes(
            From(CDEmployee),
            Select("id",
                "firstName",
                "lastName",
                .Sum("crystals.amount", As: "crystals"))
//            ,
//            Tweak { (fetchRequest) -> Void in
//                fetchRequest.returnsObjectsAsFaults = false
//                fetchRequest.includesSubentities = true
//            }
            )
            else {
                return 0
        }

And now in result array I have dictionaries with the following attributes: "id", "firstName", "lastName".


I also leaved only one employee to test with and it got 3 related CDCrystal objects:

"0xd0000000015c0000 x-coredata://50D6FBBD-46B9-4007-B2A2-4CD5DFDE78FB/Crystal/p87",
"0xd000000001600000 x-coredata://50D6FBBD-46B9-4007-B2A2-4CD5DFDE78FB/Crystal/p88",
"0xd000000001580000 x-coredata://50D6FBBD-46B9-4007-B2A2-4CD5DFDE78FB/Crystal/p86"

Still the same.

@ghost commented on GitHub (Jul 20, 2016): I reversed the query: ``` guard let aggregate = CoreStore.queryAttributes( From(CDEmployee), Select("id", "firstName", "lastName", .Sum("crystals.amount", As: "crystals")) // , // Tweak { (fetchRequest) -> Void in // fetchRequest.returnsObjectsAsFaults = false // fetchRequest.includesSubentities = true // } ) else { return 0 } ``` And now in result array I have dictionaries with the following attributes: "id", "firstName", "lastName". --- I also leaved only one employee to test with and it got 3 related CDCrystal objects: > "0xd0000000015c0000 x-coredata://50D6FBBD-46B9-4007-B2A2-4CD5DFDE78FB/Crystal/p87", > "0xd000000001600000 x-coredata://50D6FBBD-46B9-4007-B2A2-4CD5DFDE78FB/Crystal/p88", > "0xd000000001580000 x-coredata://50D6FBBD-46B9-4007-B2A2-4CD5DFDE78FB/Crystal/p86" Still the same.
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

In Select.swift in internal func applyToFetchRequest(fetchRequest: NSFetchRequest) this check if let propertyDescription = propertiesByName[keyPath] { ignores all properties which are not part of the entity.

So I don't know is it a bug or it's by design. Anyway thank you for your time.

@ghost commented on GitHub (Jul 20, 2016): In `Select.swift` in `internal func applyToFetchRequest(fetchRequest: NSFetchRequest)` this check `if let propertyDescription = propertiesByName[keyPath] {` ignores all properties which are not part of the entity. So I don't know is it a bug or it's by design. Anyway thank you for your time.
Author
Owner

@JohnEstropia commented on GitHub (Jul 20, 2016):

propertyDescription = propertiesByName[keyPath]
This looks like the cause. Thanks for investigating!
I'll see if we can make this work with relationships' keypaths

@JohnEstropia commented on GitHub (Jul 20, 2016): `propertyDescription = propertiesByName[keyPath]` This looks like the cause. Thanks for investigating! I'll see if we can make this work with relationships' keypaths
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

Here is my dirty temporary solution. It may help you:

          for term in self.selectTerms {

            switch term {

            case ._Attribute(let keyPath):
                if keyPath.containsString(".") {
                    // TODO: Check for "to-many" relation it leads to core data exception
                    let components = keyPath.componentsSeparatedByString(".")
                    let relationshipName = components[0]
                    let subentityPropertyKeyPath = components[1]
                    let relationshipsByName = entityDescription.relationshipsByName
                    if let destinationEntity = relationshipsByName[relationshipName]?.destinationEntity {
                        if let propertyDescription = destinationEntity.propertiesByName[subentityPropertyKeyPath] {
                            propertiesToFetch.append(keyPath) // !!!: Make it work with propertyDescription
                        } else {
                            // TODO: Handle error
                        }
                    } else {
                        // TODO: Handle error
                    }                    
                }
                else if let propertyDescription = propertiesByName[keyPath] {

                    propertiesToFetch.append(propertyDescription)
                }
                else {

                    CoreStore.log(
                        .Warning,
                        message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
                    )
                }

            case ._Aggregate(let function, let keyPath, let alias, let nativeType):
                // TODO: Same for attributes
...

Thank you!

@ghost commented on GitHub (Jul 20, 2016): Here is my dirty temporary solution. It may help you: ``` for term in self.selectTerms { switch term { case ._Attribute(let keyPath): if keyPath.containsString(".") { // TODO: Check for "to-many" relation it leads to core data exception let components = keyPath.componentsSeparatedByString(".") let relationshipName = components[0] let subentityPropertyKeyPath = components[1] let relationshipsByName = entityDescription.relationshipsByName if let destinationEntity = relationshipsByName[relationshipName]?.destinationEntity { if let propertyDescription = destinationEntity.propertiesByName[subentityPropertyKeyPath] { propertiesToFetch.append(keyPath) // !!!: Make it work with propertyDescription } else { // TODO: Handle error } } else { // TODO: Handle error } } else if let propertyDescription = propertiesByName[keyPath] { propertiesToFetch.append(propertyDescription) } else { CoreStore.log( .Warning, message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause." ) } case ._Aggregate(let function, let keyPath, let alias, let nativeType): // TODO: Same for attributes ... ``` Thank you!
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

Or, please excuse this hack:

        let expressionDesc = NSExpressionDescription()
        expressionDesc.name = "crystals"
        expressionDesc.expression = NSExpression(forFunction: "sum:",
                                                 arguments:[NSExpression(forKeyPath: "amount")])
        expressionDesc.expressionResultType = .Integer64AttributeType

        guard let aggregate = CoreStore.queryAttributes(
            From(CDCrystal),
            Select("employeeId"), // !!! Dummy
            Tweak { (fetchRequest) -> Void in
                fetchRequest.propertiesToFetch = ["employeeId", "employee.firstName",
                    "employee.lastName", expressionDesc]
                fetchRequest.returnsObjectsAsFaults = false
                fetchRequest.includesSubentities = true
            }
            )
        else {
            return 0
        }
@ghost commented on GitHub (Jul 20, 2016): Or, please excuse this hack: ``` let expressionDesc = NSExpressionDescription() expressionDesc.name = "crystals" expressionDesc.expression = NSExpression(forFunction: "sum:", arguments:[NSExpression(forKeyPath: "amount")]) expressionDesc.expressionResultType = .Integer64AttributeType guard let aggregate = CoreStore.queryAttributes( From(CDCrystal), Select("employeeId"), // !!! Dummy Tweak { (fetchRequest) -> Void in fetchRequest.propertiesToFetch = ["employeeId", "employee.firstName", "employee.lastName", expressionDesc] fetchRequest.returnsObjectsAsFaults = false fetchRequest.includesSubentities = true } ) else { return 0 } ```
Author
Owner

@JohnEstropia commented on GitHub (Jul 20, 2016):

@AleksandarPetrov I think you can actually just insert the compound keypath into the propertiesToFetch:

        guard let aggregate = CoreStore.queryAttributes(
            From(CDCrystal),
            Select("employeeId",
                // "employee.firstName",
                // "employee.lastName",
                .Sum("amount", As: "crystals")),
            Tweak { (fetchRequest) -> Void in
                fetchRequest.propertiesToFetch?.append(contentsOf: ["employee.firstName",
                    "employee.lastName"])
                fetchRequest.returnsObjectsAsFaults = false
                fetchRequest.includesSubentities = true
            }
            )
        else {
            return 0
        }
@JohnEstropia commented on GitHub (Jul 20, 2016): @AleksandarPetrov I think you can actually just insert the compound keypath into the `propertiesToFetch`: ``` swift guard let aggregate = CoreStore.queryAttributes( From(CDCrystal), Select("employeeId", // "employee.firstName", // "employee.lastName", .Sum("amount", As: "crystals")), Tweak { (fetchRequest) -> Void in fetchRequest.propertiesToFetch?.append(contentsOf: ["employee.firstName", "employee.lastName"]) fetchRequest.returnsObjectsAsFaults = false fetchRequest.includesSubentities = true } ) else { return 0 } ```
Author
Owner

@ghost commented on GitHub (Jul 20, 2016):

Great minds think alike

@ghost commented on GitHub (Jul 20, 2016): Great minds think alike
Author
Owner

@JohnEstropia commented on GitHub (Jul 21, 2016):

Hi, did the last solution work for you? If so I'll incorporate a similar fix in the next update

@JohnEstropia commented on GitHub (Jul 21, 2016): Hi, did the last solution work for you? If so I'll incorporate a similar fix in the next update
Author
Owner

@ghost commented on GitHub (Jul 21, 2016):

Yes, it did the job.

@ghost commented on GitHub (Jul 21, 2016): Yes, it did the job.
Author
Owner

@JohnEstropia commented on GitHub (Jul 21, 2016):

@AleksandarPetrov Cool, thanks :)

@JohnEstropia commented on GitHub (Jul 21, 2016): @AleksandarPetrov Cool, thanks :)
Author
Owner

@julien1619 commented on GitHub (Jan 3, 2017):

I tried to query attributes of a sub-entity and it didn't found the property in this sub-entity. It seems that the NSRelationshipDescription was not used correctly, you try to access the corresponding entity using entity, instead of destinationEntity.

When I use destinationEntity here, it succeeds to find the property but crashes with this error: Uncaught exception: NSInvalidArgumentException: Attribute/relationship description names passed to setPropertiesToFetch: must match name on fetch entity. I think that both errors are linked but I cannot test it more right now.

Do you have any suggestions?

@julien1619 commented on GitHub (Jan 3, 2017): I tried to query attributes of a sub-entity and it didn't found the property in this sub-entity. It seems that the NSRelationshipDescription was not used correctly, you try to access the corresponding entity using `entity`, instead of `destinationEntity`. When I use `destinationEntity` [here](https://github.com/JohnEstropia/CoreStore/blob/develop/Sources/Fetching%20and%20Querying/Concrete%20Clauses/Select.swift#L736), it succeeds to find the property but crashes with this error: `Uncaught exception: NSInvalidArgumentException: Attribute/relationship description names passed to setPropertiesToFetch: must match name on fetch entity`. I think that both errors are linked but I cannot test it more right now. Do you have any suggestions?
Author
Owner

@JohnEstropia commented on GitHub (Jan 3, 2017):

@julien1619 Can you post your query code?

@JohnEstropia commented on GitHub (Jan 3, 2017): @julien1619 Can you post your query code?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore#69