Skip to content

fix(query): honor root-level OrderExpression.EntityName for linked-entity sorting#43

Merged
LarsBauer merged 2 commits into
betafrom
fix/link-entity-order-2
Jun 19, 2026
Merged

fix(query): honor root-level OrderExpression.EntityName for linked-entity sorting#43
LarsBauer merged 2 commits into
betafrom
fix/link-entity-order-2

Conversation

@LarsBauer

Copy link
Copy Markdown
Contributor

Root-level OrderExpression entries with a non-null EntityName (produced by FetchXML <order entityname="alias" attribute="..." /> or set directly on qe.Orders) were silently ignored. CollectOrders passed alias=null for all root orders, so the key resolver looked up the bare attribute name instead of "{alias}.{attribute}", returning rows in an unordered sequence and causing wrong results when combined with TopCount.

  • XDocumentExtensions.ToOrderExpressionList: read the entityname XML attribute and populate OrderExpression.EntityName
  • QueryProcessor.ConvertXmlDocumentToQueryExpression: add orders via query.Orders.Add instead of AddOrder so EntityName is not discarded
  • QueryProcessor.CollectOrders: use order.EntityName as the alias when set

…tity sorting

Root-level OrderExpression entries with a non-null EntityName (produced by FetchXML
`<order entityname="alias" attribute="..." />` or set directly on qe.Orders) were
silently ignored. CollectOrders passed alias=null for all root orders, so the key
resolver looked up the bare attribute name instead of "{alias}.{attribute}", returning
rows in an unordered sequence and causing wrong results when combined with TopCount.

- XDocumentExtensions.ToOrderExpressionList: read the `entityname` XML attribute and
  populate OrderExpression.EntityName
- QueryProcessor.ConvertXmlDocumentToQueryExpression: add orders via query.Orders.Add
  instead of AddOrder so EntityName is not discarded
- QueryProcessor.CollectOrders: use order.EntityName as the alias when set
@LarsBauer
LarsBauer requested a review from PepperPix June 18, 2026 16:02
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Qodana Community for .NET

It seems all right 👌

No new problems were found according to the checks applied

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2026.1.0
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

@LarsBauer
LarsBauer merged commit 102de5d into beta Jun 19, 2026
8 checks passed
@LarsBauer
LarsBauer deleted the fix/link-entity-order-2 branch June 19, 2026 05:38
PepperPix pushed a commit that referenced this pull request Jun 19, 2026
# [1.1.0-beta.1](v1.0.0...v1.1.0-beta.1) (2026-06-19)

### Bug Fixes

* **bulk-delete:** copy RecurrencePattern and StartDateTime to asyncoperation ([3cffda7](3cffda7))
* **bulk-delete:** omit recurrencepattern and recurrencestarttime when not set ([0c0a2e8](0c0a2e8))
* **bulk-delete:** populate name, ownerid, and operationtype on asyncoperation entity ([2ac6f28](2ac6f28))
* deep-clone OptionSetValue instances inside OptionSetValueCollection ([dd72956](dd72956))
* enforce single-child and entity type validation for N:1 deep insert ([7ae25aa](7ae25aa))
* ensure CloneEntity returns base Entity runtime type ([f393387](f393387))
* fall back to EntityLogicalNameAttribute in CreateQuery<T> when type is not in resolver cache ([5ba7140](5ba7140))
* merge attributes on Update instead of replacing the entire entity ([753ecd6](753ecd6))
* **query:** clone outer entity per row in LeftOuter join ([501db49](501db49))
* **query:** fix 4 critical query engine bugs ([ef0e7e4](ef0e7e4))
* **query:** guard against null Attributes array in PatchDateFormat ([3021776](3021776))
* **query:** handle null otherEntity in JoinAttributes for left outer joins ([a7c67cb](a7c67cb))
* **query:** honor LinkEntity.Orders in RetrieveMultiple ordering ([#42](#42)) ([4b363b1](4b363b1))
* **query:** honor root-level OrderExpression.EntityName for linked-entity sorting ([#43](#43)) ([102de5d](102de5d))
* **query:** match primary id attribute against Entity.Id in RetrieveMultiple ([5853fb6](5853fb6))
* **query:** materialize DateTime attributes before modifying collection in PatchDateFormat ([dd4d2e3](dd4d2e3))
* **query:** pass Guid directly for EqualUserId/EqualBusinessId conditions ([e7b8c77](e7b8c77))
* **query:** treat fetchxml bool condition values without metadata as bool ([8be757e](8be757e))
* **query:** use fiscalYear from condition as year for InFiscalYear date range ([e4677e6](e4677e6))
* **query:** use TimeOnly.MaxValue for end-of-day upper bound in date range operators ([49b1679](49b1679))
* **query:** validate actual values in aggregate FetchXml guards ([fe9cfcf](fe9cfcf))
* remove 14 redundant using directives ([1771050](1771050))
* remove 5 unused local variables ([a94494e](a94494e))
* remove redundant System.Reflection qualifier ([5367eb2](5367eb2))
* replace Activator.CreateInstance with plain Entity in ProjectAttributes ([a87bb5b](a87bb5b))
* replace generic exceptions with Dataverse-style FaultException ([fb21ac0](fb21ac0))
* replace obsolete HasCount() with Count().IsEqualTo() ([d8c5857](d8c5857))
* resolve ambiguous XML doc comment reference ([2a6be1b](2a6be1b))
* **retrieve:** translate QueryByAttribute Attributes/Values and Orders in RetrieveFake ([1bffa18](1bffa18))
* skip ownerid default when proxy type has no ownerid attribute ([714dd89](714dd89))
* support reverse-direction (ManyToOne) nested deep insert ([00df6e8](00df6e8))
* use Count property instead of Count() method ([49c69cd](49c69cd))
* validate maxRetrieveCount argument in WithMaxRetrieveCount ([3c7797d](3c7797d))
* validate relationship metadata matches parent entity in DeepInsertProcessor ([2a6952a](2a6952a))

### Features

* add FetchXmlToQueryExpression organization request fake ([5885467](5885467))
* add QueryExpressionToFetchXml organization request fake ([c0366cb](c0366cb))
* add RelatedEntitiesQuery support to RetrieveFake ([868a555](868a555))
* add RetrieveAllEntities organization request fake ([83db189](83db189))
* add RetrieveAttributeFake implementation ([e64ac18](e64ac18))
* **query:** implement CompareColumns and MatchFirstRowUsingCrossApply ([b4f9bd1](b4f9bd1))
* **query:** support EXISTS-style semi-joins via JoinOperator.Any/NotAny/Exists ([63df498](63df498))
* **query:** support FilterExpression.AnyAllFilterLinkEntity and JoinOperator.All/NotAll ([87704f4](87704f4))
* route Create/Update/Delete/Retrieve/Associate/Disassociate through Execute pipeline ([374604b](374604b))
* set audit fields and RowVersion on Create and Update ([f1c7f92](f1c7f92))
* support deep insert (RelatedEntities) in CreateFake and UpsertFake ([103f0de](103f0de))
* use Options class for explicit configuration ([8482732](8482732))

### Performance Improvements

* cache EntityLogicalNameAttribute lookup in CreateQuery<T> ([863ba8d](863ba8d))
* cache MethodInfo lookups in ConditionParser as static readonly fields ([0f89d70](0f89d70))
* compile proxy converter delegates via expression trees ([1c38463](1c38463))
* use pattern matching in XrmOrderByAttributeComparer ([f035a4a](f035a4a))
PepperPix pushed a commit that referenced this pull request Jun 19, 2026
# [1.1.0-beta.1](v1.0.0...v1.1.0-beta.1) (2026-06-19)

### Bug Fixes

* **bulk-delete:** copy RecurrencePattern and StartDateTime to asyncoperation ([3cffda7](3cffda7))
* **bulk-delete:** omit recurrencepattern and recurrencestarttime when not set ([0c0a2e8](0c0a2e8))
* **bulk-delete:** populate name, ownerid, and operationtype on asyncoperation entity ([2ac6f28](2ac6f28))
* deep-clone OptionSetValue instances inside OptionSetValueCollection ([dd72956](dd72956))
* enforce single-child and entity type validation for N:1 deep insert ([7ae25aa](7ae25aa))
* ensure CloneEntity returns base Entity runtime type ([f393387](f393387))
* fall back to EntityLogicalNameAttribute in CreateQuery<T> when type is not in resolver cache ([5ba7140](5ba7140))
* merge attributes on Update instead of replacing the entire entity ([753ecd6](753ecd6))
* **query:** clone outer entity per row in LeftOuter join ([501db49](501db49))
* **query:** fix 4 critical query engine bugs ([ef0e7e4](ef0e7e4))
* **query:** guard against null Attributes array in PatchDateFormat ([3021776](3021776))
* **query:** handle null otherEntity in JoinAttributes for left outer joins ([a7c67cb](a7c67cb))
* **query:** honor LinkEntity.Orders in RetrieveMultiple ordering ([#42](#42)) ([4b363b1](4b363b1))
* **query:** honor root-level OrderExpression.EntityName for linked-entity sorting ([#43](#43)) ([102de5d](102de5d))
* **query:** match primary id attribute against Entity.Id in RetrieveMultiple ([5853fb6](5853fb6))
* **query:** materialize DateTime attributes before modifying collection in PatchDateFormat ([dd4d2e3](dd4d2e3))
* **query:** pass Guid directly for EqualUserId/EqualBusinessId conditions ([e7b8c77](e7b8c77))
* **query:** treat fetchxml bool condition values without metadata as bool ([8be757e](8be757e))
* **query:** use fiscalYear from condition as year for InFiscalYear date range ([e4677e6](e4677e6))
* **query:** use TimeOnly.MaxValue for end-of-day upper bound in date range operators ([49b1679](49b1679))
* **query:** validate actual values in aggregate FetchXml guards ([fe9cfcf](fe9cfcf))
* remove 14 redundant using directives ([1771050](1771050))
* remove 5 unused local variables ([a94494e](a94494e))
* remove redundant System.Reflection qualifier ([5367eb2](5367eb2))
* replace Activator.CreateInstance with plain Entity in ProjectAttributes ([a87bb5b](a87bb5b))
* replace generic exceptions with Dataverse-style FaultException ([fb21ac0](fb21ac0))
* replace obsolete HasCount() with Count().IsEqualTo() ([d8c5857](d8c5857))
* resolve ambiguous XML doc comment reference ([2a6be1b](2a6be1b))
* **retrieve:** translate QueryByAttribute Attributes/Values and Orders in RetrieveFake ([1bffa18](1bffa18))
* skip ownerid default when proxy type has no ownerid attribute ([714dd89](714dd89))
* support reverse-direction (ManyToOne) nested deep insert ([00df6e8](00df6e8))
* use Count property instead of Count() method ([49c69cd](49c69cd))
* validate maxRetrieveCount argument in WithMaxRetrieveCount ([3c7797d](3c7797d))
* validate relationship metadata matches parent entity in DeepInsertProcessor ([2a6952a](2a6952a))

### Features

* add FetchXmlToQueryExpression organization request fake ([5885467](5885467))
* add QueryExpressionToFetchXml organization request fake ([c0366cb](c0366cb))
* add RelatedEntitiesQuery support to RetrieveFake ([868a555](868a555))
* add RetrieveAllEntities organization request fake ([83db189](83db189))
* add RetrieveAttributeFake implementation ([e64ac18](e64ac18))
* **query:** implement CompareColumns and MatchFirstRowUsingCrossApply ([b4f9bd1](b4f9bd1))
* **query:** support EXISTS-style semi-joins via JoinOperator.Any/NotAny/Exists ([63df498](63df498))
* **query:** support FilterExpression.AnyAllFilterLinkEntity and JoinOperator.All/NotAll ([87704f4](87704f4))
* route Create/Update/Delete/Retrieve/Associate/Disassociate through Execute pipeline ([374604b](374604b))
* set audit fields and RowVersion on Create and Update ([f1c7f92](f1c7f92))
* support deep insert (RelatedEntities) in CreateFake and UpsertFake ([103f0de](103f0de))
* use Options class for explicit configuration ([8482732](8482732))

### Performance Improvements

* cache EntityLogicalNameAttribute lookup in CreateQuery<T> ([863ba8d](863ba8d))
* cache MethodInfo lookups in ConditionParser as static readonly fields ([0f89d70](0f89d70))
* compile proxy converter delegates via expression trees ([1c38463](1c38463))
* use pattern matching in XrmOrderByAttributeComparer ([f035a4a](f035a4a))
PepperPix pushed a commit that referenced this pull request Jun 19, 2026
# [1.1.0-beta.1](v1.0.0...v1.1.0-beta.1) (2026-06-19)

### Bug Fixes

* **bulk-delete:** copy RecurrencePattern and StartDateTime to asyncoperation ([3cffda7](3cffda7))
* **bulk-delete:** omit recurrencepattern and recurrencestarttime when not set ([0c0a2e8](0c0a2e8))
* **bulk-delete:** populate name, ownerid, and operationtype on asyncoperation entity ([2ac6f28](2ac6f28))
* deep-clone OptionSetValue instances inside OptionSetValueCollection ([dd72956](dd72956))
* enforce single-child and entity type validation for N:1 deep insert ([7ae25aa](7ae25aa))
* ensure CloneEntity returns base Entity runtime type ([f393387](f393387))
* fall back to EntityLogicalNameAttribute in CreateQuery<T> when type is not in resolver cache ([5ba7140](5ba7140))
* merge attributes on Update instead of replacing the entire entity ([753ecd6](753ecd6))
* **query:** clone outer entity per row in LeftOuter join ([501db49](501db49))
* **query:** fix 4 critical query engine bugs ([ef0e7e4](ef0e7e4))
* **query:** guard against null Attributes array in PatchDateFormat ([3021776](3021776))
* **query:** handle null otherEntity in JoinAttributes for left outer joins ([a7c67cb](a7c67cb))
* **query:** honor LinkEntity.Orders in RetrieveMultiple ordering ([#42](#42)) ([4b363b1](4b363b1))
* **query:** honor root-level OrderExpression.EntityName for linked-entity sorting ([#43](#43)) ([102de5d](102de5d))
* **query:** match primary id attribute against Entity.Id in RetrieveMultiple ([5853fb6](5853fb6))
* **query:** materialize DateTime attributes before modifying collection in PatchDateFormat ([dd4d2e3](dd4d2e3))
* **query:** pass Guid directly for EqualUserId/EqualBusinessId conditions ([e7b8c77](e7b8c77))
* **query:** treat fetchxml bool condition values without metadata as bool ([8be757e](8be757e))
* **query:** use fiscalYear from condition as year for InFiscalYear date range ([e4677e6](e4677e6))
* **query:** use TimeOnly.MaxValue for end-of-day upper bound in date range operators ([49b1679](49b1679))
* **query:** validate actual values in aggregate FetchXml guards ([fe9cfcf](fe9cfcf))
* remove 14 redundant using directives ([1771050](1771050))
* remove 5 unused local variables ([a94494e](a94494e))
* remove redundant System.Reflection qualifier ([5367eb2](5367eb2))
* replace Activator.CreateInstance with plain Entity in ProjectAttributes ([a87bb5b](a87bb5b))
* replace generic exceptions with Dataverse-style FaultException ([fb21ac0](fb21ac0))
* replace obsolete HasCount() with Count().IsEqualTo() ([d8c5857](d8c5857))
* resolve ambiguous XML doc comment reference ([2a6be1b](2a6be1b))
* **retrieve:** translate QueryByAttribute Attributes/Values and Orders in RetrieveFake ([1bffa18](1bffa18))
* skip ownerid default when proxy type has no ownerid attribute ([714dd89](714dd89))
* support reverse-direction (ManyToOne) nested deep insert ([00df6e8](00df6e8))
* use Count property instead of Count() method ([49c69cd](49c69cd))
* validate maxRetrieveCount argument in WithMaxRetrieveCount ([3c7797d](3c7797d))
* validate relationship metadata matches parent entity in DeepInsertProcessor ([2a6952a](2a6952a))

### Features

* add FetchXmlToQueryExpression organization request fake ([5885467](5885467))
* add QueryExpressionToFetchXml organization request fake ([c0366cb](c0366cb))
* add RelatedEntitiesQuery support to RetrieveFake ([868a555](868a555))
* add RetrieveAllEntities organization request fake ([83db189](83db189))
* add RetrieveAttributeFake implementation ([e64ac18](e64ac18))
* **query:** implement CompareColumns and MatchFirstRowUsingCrossApply ([b4f9bd1](b4f9bd1))
* **query:** support EXISTS-style semi-joins via JoinOperator.Any/NotAny/Exists ([63df498](63df498))
* **query:** support FilterExpression.AnyAllFilterLinkEntity and JoinOperator.All/NotAll ([87704f4](87704f4))
* route Create/Update/Delete/Retrieve/Associate/Disassociate through Execute pipeline ([374604b](374604b))
* set audit fields and RowVersion on Create and Update ([f1c7f92](f1c7f92))
* support deep insert (RelatedEntities) in CreateFake and UpsertFake ([103f0de](103f0de))
* use Options class for explicit configuration ([8482732](8482732))

### Performance Improvements

* cache EntityLogicalNameAttribute lookup in CreateQuery<T> ([863ba8d](863ba8d))
* cache MethodInfo lookups in ConditionParser as static readonly fields ([0f89d70](0f89d70))
* compile proxy converter delegates via expression trees ([1c38463](1c38463))
* use pattern matching in XrmOrderByAttributeComparer ([f035a4a](f035a4a))
PepperPix pushed a commit that referenced this pull request Jun 19, 2026
# [1.1.0-beta.11](v1.1.0-beta.10...v1.1.0-beta.11) (2026-06-19)

### Bug Fixes

* **query:** honor root-level OrderExpression.EntityName for linked-entity sorting ([#43](#43)) ([102de5d](102de5d))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.1.0-beta.11 🎉

The release is available on:

Your semantic-release bot 📦🚀

PepperPix pushed a commit that referenced this pull request Jul 1, 2026
# [1.1.0](v1.0.0...v1.1.0) (2026-07-01)

### Bug Fixes

* **bulk-delete:** copy RecurrencePattern and StartDateTime to asyncoperation ([3cffda7](3cffda7))
* **bulk-delete:** omit recurrencepattern and recurrencestarttime when not set ([0c0a2e8](0c0a2e8))
* **bulk-delete:** populate name, ownerid, and operationtype on asyncoperation entity ([2ac6f28](2ac6f28))
* deep-clone OptionSetValue instances inside OptionSetValueCollection ([dd72956](dd72956))
* enforce single-child and entity type validation for N:1 deep insert ([7ae25aa](7ae25aa))
* ensure CloneEntity returns base Entity runtime type ([f393387](f393387))
* fall back to EntityLogicalNameAttribute in CreateQuery<T> when type is not in resolver cache ([5ba7140](5ba7140))
* merge attributes on Update instead of replacing the entire entity ([753ecd6](753ecd6))
* **query:** clone outer entity per row in LeftOuter join ([501db49](501db49))
* **query:** fix 4 critical query engine bugs ([ef0e7e4](ef0e7e4))
* **query:** guard against null Attributes array in PatchDateFormat ([3021776](3021776))
* **query:** handle null otherEntity in JoinAttributes for left outer joins ([a7c67cb](a7c67cb))
* **query:** honor LinkEntity.Orders in RetrieveMultiple ordering ([#42](#42)) ([4b363b1](4b363b1))
* **query:** honor root-level OrderExpression.EntityName for linked-entity sorting ([#43](#43)) ([102de5d](102de5d))
* **query:** match primary id attribute against Entity.Id in RetrieveMultiple ([5853fb6](5853fb6))
* **query:** materialize DateTime attributes before modifying collection in PatchDateFormat ([dd4d2e3](dd4d2e3))
* **query:** pass Guid directly for EqualUserId/EqualBusinessId conditions ([e7b8c77](e7b8c77))
* **query:** prefer OrderExpression.Alias over EntityName in CollectOrders ([#44](#44)) ([890d912](890d912))
* **query:** treat fetchxml bool condition values without metadata as bool ([8be757e](8be757e))
* **query:** use fiscalYear from condition as year for InFiscalYear date range ([e4677e6](e4677e6))
* **query:** use TimeOnly.MaxValue for end-of-day upper bound in date range operators ([49b1679](49b1679))
* **query:** validate actual values in aggregate FetchXml guards ([fe9cfcf](fe9cfcf))
* remove 14 redundant using directives ([1771050](1771050))
* remove 5 unused local variables ([a94494e](a94494e))
* remove redundant System.Reflection qualifier ([5367eb2](5367eb2))
* replace Activator.CreateInstance with plain Entity in ProjectAttributes ([a87bb5b](a87bb5b))
* replace generic exceptions with Dataverse-style FaultException ([fb21ac0](fb21ac0))
* replace obsolete HasCount() with Count().IsEqualTo() ([d8c5857](d8c5857))
* resolve ambiguous XML doc comment reference ([2a6be1b](2a6be1b))
* **retrieve:** translate QueryByAttribute Attributes/Values and Orders in RetrieveFake ([1bffa18](1bffa18))
* skip ownerid default when proxy type has no ownerid attribute ([714dd89](714dd89))
* support reverse-direction (ManyToOne) nested deep insert ([00df6e8](00df6e8))
* use Count property instead of Count() method ([49c69cd](49c69cd))
* validate maxRetrieveCount argument in WithMaxRetrieveCount ([3c7797d](3c7797d))
* validate relationship metadata matches parent entity in DeepInsertProcessor ([2a6952a](2a6952a))

### Features

* add FetchXmlToQueryExpression organization request fake ([5885467](5885467))
* add QueryExpressionToFetchXml organization request fake ([c0366cb](c0366cb))
* add RelatedEntitiesQuery support to RetrieveFake ([868a555](868a555))
* add RetrieveAllEntities organization request fake ([83db189](83db189))
* add RetrieveAttributeFake implementation ([e64ac18](e64ac18))
* **query:** implement CompareColumns and MatchFirstRowUsingCrossApply ([b4f9bd1](b4f9bd1))
* **query:** support EXISTS-style semi-joins via JoinOperator.Any/NotAny/Exists ([63df498](63df498))
* **query:** support FilterExpression.AnyAllFilterLinkEntity and JoinOperator.All/NotAll ([87704f4](87704f4))
* route Create/Update/Delete/Retrieve/Associate/Disassociate through Execute pipeline ([374604b](374604b))
* set audit fields and RowVersion on Create and Update ([f1c7f92](f1c7f92))
* support deep insert (RelatedEntities) in CreateFake and UpsertFake ([103f0de](103f0de))
* use Options class for explicit configuration ([8482732](8482732))

### Performance Improvements

* cache EntityLogicalNameAttribute lookup in CreateQuery<T> ([863ba8d](863ba8d))
* cache MethodInfo lookups in ConditionParser as static readonly fields ([0f89d70](0f89d70))
* compile proxy converter delegates via expression trees ([1c38463](1c38463))
* use pattern matching in XrmOrderByAttributeComparer ([f035a4a](f035a4a))
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.1.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant