Repository navigation
Minimal repro: GetAsync (Obsolete) vs GetQueryAsync (Bug or expected behaviour?) #252
Description
Activity
- changed the title
[-]Minimal repro: `GetAsync` (Obsolete) vs `GetQueryAsync` in `AutoMapper.AspNetCore.OData.EFCore`[/-][+]Minimal repro: `GetAsync` (Obsolete) vs `GetQueryAsync`[/+]on Aug 31, 2026 - changed the title
[-]Minimal repro: `GetAsync` (Obsolete) vs `GetQueryAsync`[/-][+]Minimal repro: `GetAsync` (Obsolete) vs `GetQueryAsync` (Bug?)[/+]on Aug 31, 2026 - changed the title
[-]Minimal repro: `GetAsync` (Obsolete) vs `GetQueryAsync` (Bug?)[/-][+]Minimal repro: `GetAsync` (Obsolete) vs `GetQueryAsync` (Bug or expected behaviour?)[/+]on Aug 31, 2026 This thread recommends separate DTO types for self referencing entities.
GetAsyncreferenced code (old ideas) which have been removed from the expression mapping library which this one depends on. If you get the same results using AutoMapper'sProjectTo(without this library) then it's probably the same issue. If you get it to succeed then we'll have a bug to address. In short we want to stay away fromGetAsyncand improveGetQueryAsyncif we have to.Reacted by Leo ErlandssonHi,
Thanks for you response. I can confirm that this is upstream in AutoMapper.
I've attached an updated repro project.However, switching from [Obsolete]
GetAsynctoGetQueryAsyncis not a 1:1 update. It introduces breaking changes (like this one), both for this example and some others we've found (where Includes() and included data are dropped from the result, also probably because the newGetQueryAsyncusesProjectTo).If this is intended behaviour, the Issue can be closed, and we'll have to find some kind of workarounds switching from
GetAsynctoGetQueryAsyncin our application.Reacted by Peter AnderssonRemoving the
MapIncludesbehavior from the expression mapping library andGetAsyncas a consequence is intended.MapIncludesderived the mapped parent of flattened DTO members for expansion.ProjectToexpansions handle flattened members without further mapping and has millions more users i.e. continuing to maintainMapIncludesisn't worth the trouble ifProjectTocan achieve the same behavior even though code changes will be needed for some use cases.Reacted by Leo ErlandssonOk, and thanks. The removal of
MapIncludesalso explains why we are missing the (on backend manually) included entities.I understand the behaviour of
GetQueryAsyncwill not be changed and we'll have to find a way around that going forward switching from[Obsolete] GetAsynctoGetQueryAsync.Thanks again for your prompt response.
Workaround / rewrite of code implemented for the removed
MapIncludes.The rewrite involves, in this case, separately loading the data and adding the
Includesneeded in a separate query and then populating the OData response with it before responding. Sure, two hits to the database, but for us an acceptable compromise.If anyone needs an example, just give me a ping.
You can close this Issue @BlaiseD. Thanks again.
Reacted by Blaise Taylor
GetAsync()is marked [Obsolete] and is replaced byGetQueryAsync().The behaviour however is different and we are missing 1:1 expands that were present with the Obsolete method. This breaks our application.
Please see attached repro project.
odata_repro.zip
Summary
In
AutoMapper.AspNetCore.OData.EFCore9.1.0 the extension methodGetAsync(...)was marked[Obsolete](seePR #250)
in favor of
GetQueryAsync(...). In 10.0.0 the method is removedentirely.
The problem — the two methods do not produce the same result. The
recommended
GetQueryAsyncfails to expand an optional 1:1 navigationproperty (same class of issue as
AutoMapper.Extensions.OData#150).
Project
AutoMapper.AspNetCore.OData.EFCore9.1.0 (last version where bothmethods coexist so they can be compared in one project)
Categoryentity with a self-referencing optional 1:1ParentCategory(nullable FKParentId)Category→CategoryDtowithForAllMembers(o => o.ExplicitExpansion())DbSet, mapper andODataQueryOptions:GET /odata/CategoriesObsolete→ usesGetAsync(Obsolete)GET /odata/CategoriesQuery→ usesGetQueryAsyncReproduce
Then issue the same OData query against both endpoints:
You can use the provided requests in OData.AutoMapper.Repro.http.
Please see that expanded ParentCategory is missing in the second response.
Actual output
GetAsync(Obsolete) — works:{ "value": [ { "Id": 2, "Name": "Child", "ParentId": 1, "ParentCategory": { "Id": 1, "Name": "Root", "ParentId": null } }, { "Id": 3, "Name": "Grandchild", "ParentId": 2, "ParentCategory": { "Id": 2, "Name": "Child", "ParentId": 1 } } ] }GetQueryAsync— broken, expandedParentCategoryisnull:{ "value": [ { "Id": 2, "Name": "Child", "ParentId": 1, "ParentCategory": null }, { "Id": 3, "Name": "Grandchild", "ParentId": 2, "ParentCategory": null } ] }Impact
Because the replacement method silently drops optional 1:1 expansions,
we cannot follow AutoMapper.OData.Extensions's guidance to migrate to
GetQueryAsync,and cannot upgrade to 10.0.0 (10.0.0 removes
GetAsync).Source/destination types
Mapping configuration
Version: 9.1.0
Expected behavior
GetQueryAsync should expand the optional 1:1 navigation property ParentCategory just like the obsolete GetAsync does, since both are given the same DbSet , IMapper , and ODataQueryOptions with $expand=parentCategory .
Actual behavior
GetAsync (Obsolete) correctly returns the expanded ParentCategory :
{ "value": [ { "Id": 2, "Name": "Child", "ParentId": 1, "ParentCategory": { "Id": 1, "Name": "Root", "ParentId": null } }, { "Id": 3, "Name": "Grandchild", "ParentId": 2, "ParentCategory": { "Id": 2, "Name": "Child", "ParentId": 1 } } ] }GetQueryAsync (recommended replacement) returns ParentCategory: null for every row, silently dropping the expand:
{ "value": [ { "Id": 2, "Name": "Child", "ParentId": 1, "ParentCategory": null }, { "Id": 3, "Name": "Grandchild", "ParentId": 2, "ParentCategory": null } ] }Steps to reproduce