Commit ee2ce9e
Bug fix for pagination nested entities resulting key not found error. (#3029)
## Why make this change?
We are addressing 2 related issues in this PR:
Issue #2374 – Nested sibling relationships under books
(websiteplacement, reviews, authors)
**Problem**: A nested query on books where a parent has multiple sibling
relationships (for example, websiteplacement, reviews, and authors)
could throw a `KeyNotFoundException` when RBAC or shape changes were
involved. Pagination metadata was stored using only the root and the
depth in the path, so different sibling relationships at the same depth
could overwrite each other or look up the wrong entry.
**Solution**: We now key pagination metadata by both depth and the full
relationship path (for example, “books → items → reviews” vs “books →
items → authors”), so each sibling branch gets its own unique entry.
Reads use the same full-path key, and if metadata for a branch is
missing, we return an “empty” `PaginationMetadata` instead of throwing.
This prevents collisions between sibling relationships and avoids
runtime errors when a particular branch has no metadata.
Issue #3026 – Person's graph (AddressType / PhoneNumberType)
**Problem**: In the persons graph, a query selecting persons →
addresses.items.AddressType and persons →
phoneNumbers.items.PhoneNumberType could also throw a
`KeyNotFoundException`. In some cases (for example, when RBAC removes a
relationship or when that relationship is not paginated at all), there
is legitimately no pagination metadata for that nested field, but the
code assumed it always existed and indexed into the dictionary directly.
**Solution**: Metadata handling is now defensive in two places:
In the GraphQL execution helper, metadata lookups for object and list
fields use safe TryGet-style access; if an entry isn’t present, we fall
back to an empty PaginationMetadata instead of failing.
In the SQL query engine’s object resolver, we first check whether there
is a subquery metadata entry for the field. If there isn’t, we treat the
field as non‑paginated and return the JSON as-is rather than throwing.
Together, these changes fix both issues by
(a) using full path-based keys, so sibling branches don’t conflict,
(b) treating missing metadata as “no pagination here” rather than as a
fatal error.
## What is this change?
1. In `SqlQueryEngine.ResolveObject`, instead of always doing
`parentMetadata.Subqueries[fieldName]` (which crashed when RBAC caused
that entry to be missing), it now uses `TryGetValue` and:
- If metadata exists and `IsPaginated` is true -> wrap the JSON as a
pagination connection.
- If metadata is missing -> just return the JSON as-is (no exception).
2. Introduced `GetRelationshipPathSuffix(HotChocolate.Path path)` to
build a relationship path suffix like:
- `rel1` for `/entity/items[0]/rel1`
- `rel1::nested` for `/entity/items[0]/rel1/nested`
3. `SetNewMetadataChildren`, now stores child metadata under keys of the
form
- `root_PURE_RESOLVER_CTX::depth::relationshipPath`, ensuring siblings
at the same depth get distinct entries.
5. `GetMetadata` (used for list items fields):
- For `Selection.ResponseName == "items"` and non-root paths, now looks
up:
a. `GetMetadataKey(context.Path) + "::" + context.Path.Parent.Depth()`
plus the relationship suffix from
`GetRelationshipPathSuffix(context.Path.Parent)`.
b. Uses `ContextData.TryGetValue(...)` and falls back to
`PaginationMetadata.MakeEmptyPaginationMetadata()` when metadata is
missing (e.g. Cosmos, pruned relationships).
6. `GetMetadataObjectField` (used for object fields like addresses,
AddressType, PhoneNumberType):
Updated all branches (indexer, nested non-root, root) to:
- Append the relationship suffix to the base key (so keys align with
`SetNewMetadataChildren`).
- Use `ContextData.TryGetValue(...)` instead of direct indexing, return
`PaginationMetadata.MakeEmptyPaginationMetadata()` when no metadata
exists, instead of throwing.
7. Added a new test case in `MsSqlGraphQLQueryTests`, an integration
test which queries books with multiple sibling nested relationships
(websiteplacement, reviews, authors) under the authenticated role to:
- Assert no KeyNotFoundException,
- Verify all nested branches return data.
## How was this tested?
Tested both manually and added an integration test
(NestedReviewsConnection_WithSiblings_PaginatesMoreThanHundredItems).
Manually if we run this query without the bug fix:
`query {
persons {
items {
PersonID
FirstName
LastName
addresses {
items {
AddressID
City
AddressType {
AddressTypeID
TypeName
}
}
}
phoneNumbers {
items {
PhoneNumberID
PhoneNumber
PhoneNumberType {
PhoneNumberTypeID
TypeName
}
}
}
}
}
}`
We get the following response:
`{
"errors": [
{
"message": "The given key 'AddressType' was not present in the
dictionary.",
"locations": [
{
"line": 11,
"column": 11
}
],
"path": [
"persons",
"items",
0,
"addresses",
"items",
1,
"AddressType"
]
},
{
"message": "The given key 'AddressType' was not present in the
dictionary.",
"locations": [
{
"line": 11,
"column": 11
}
],
"path": [
"persons",
"items",
0,
"addresses",
"items",
0,
"AddressType"
]
},
{
"message": "The given key 'AddressType' was not present in the
dictionary.",
"locations": [
{
"line": 11,
"column": 11
}
],
"path": [
"persons",
"items",
1,
"addresses",
"items",
0,
"AddressType"
]
}
],
"data": {
"persons": {
"items": [
{
"PersonID": 1,
"FirstName": "John",
"LastName": "Doe",
"addresses": {
"items": [
{
"AddressID": 1,
"City": "New York",
"AddressType": null
},
{
"AddressID": 2,
"City": "New York",
"AddressType": null
}
]
},
"phoneNumbers": {
"items": [
{
"PhoneNumberID": 1,
"PhoneNumber": "123-456-7890",
"PhoneNumberType": {
"PhoneNumberTypeID": 1,
"TypeName": "Mobile"
}
},
{
"PhoneNumberID": 2,
"PhoneNumber": "111-222-3333",
"PhoneNumberType": {
"PhoneNumberTypeID": 3,
"TypeName": "Work"
}
}
]
}
},
{
"PersonID": 2,
"FirstName": "Jane",
"LastName": "Smith",
"addresses": {
"items": [
{
"AddressID": 3,
"City": "Los Angeles",
"AddressType": null
}
]
},
"phoneNumbers": {
"items": [
{
"PhoneNumberID": 3,
"PhoneNumber": "987-654-3210",
"PhoneNumberType": {
"PhoneNumberTypeID": 2,
"TypeName": "Home"
}
}
]
}
}
]
}
}
}`
After the bug fix, we get,
`{
"data": {
"persons": {
"items": [
{
"PersonID": 1,
"FirstName": "John",
"LastName": "Doe",
"addresses": {
"items": [
{
"AddressID": 1,
"City": "New York",
"AddressType": {
"AddressTypeID": 1,
"TypeName": "Home"
}
},
{
"AddressID": 2,
"City": "New York",
"AddressType": {
"AddressTypeID": 2,
"TypeName": "Work"
}
}
]
},
"phoneNumbers": {
"items": [
{
"PhoneNumberID": 1,
"PhoneNumber": "123-456-7890",
"PhoneNumberType": {
"PhoneNumberTypeID": 1,
"TypeName": "Mobile"
}
},
{
"PhoneNumberID": 2,
"PhoneNumber": "111-222-3333",
"PhoneNumberType": {
"PhoneNumberTypeID": 3,
"TypeName": "Work"
}
}
]
}
},
{
"PersonID": 2,
"FirstName": "Jane",
"LastName": "Smith",
"addresses": {
"items": [
{
"AddressID": 3,
"City": "Los Angeles",
"AddressType": {
"AddressTypeID": 1,
"TypeName": "Home"
}
}
]
},
"phoneNumbers": {
"items": [
{
"PhoneNumberID": 3,
"PhoneNumber": "987-654-3210",
"PhoneNumberType": {
"PhoneNumberTypeID": 2,
"TypeName": "Home"
}
}
]
}
}
]
}
}
}`
## Sample Request(s)
- Example REST and/or GraphQL request to demonstrate modifications
- Example of CLI usage to demonstrate modifications
---------
Co-authored-by: Copilot <[email protected]>
Co-authored-by: RubenCerna2079 <[email protected]>1 parent 5b43e2b commit ee2ce9e
3 files changed
Lines changed: 331 additions & 10 deletions
File tree
- src
- Core
- Resolvers
- Services
- Service.Tests/SqlTests/GraphQLQueryTests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
227 | | - | |
228 | | - | |
229 | | - | |
230 | | - | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
231 | 232 | | |
232 | | - | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
233 | 239 | | |
234 | 240 | | |
235 | 241 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
534 | 535 | | |
535 | 536 | | |
536 | 537 | | |
537 | | - | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
538 | 556 | | |
539 | 557 | | |
540 | 558 | | |
| |||
582 | 600 | | |
583 | 601 | | |
584 | 602 | | |
585 | | - | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
586 | 621 | | |
587 | 622 | | |
588 | 623 | | |
| |||
592 | 627 | | |
593 | 628 | | |
594 | 629 | | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
595 | 637 | | |
596 | | - | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
597 | 650 | | |
598 | 651 | | |
599 | 652 | | |
600 | | - | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
601 | 659 | | |
602 | 660 | | |
603 | 661 | | |
| |||
614 | 672 | | |
615 | 673 | | |
616 | 674 | | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
617 | 719 | | |
618 | 720 | | |
619 | 721 | | |
| |||
655 | 757 | | |
656 | 758 | | |
657 | 759 | | |
658 | | - | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
659 | 779 | | |
660 | 780 | | |
661 | 781 | | |
| |||
0 commit comments