AutoMapper / AutoMapper.Extensions.OData

Creates LINQ expressions from ODataQueryOptions and executes the query.
MIT License
140 stars 38 forks source link

Support for discriminators/TPH #198

Closed lenardchristopher closed 7 months ago

lenardchristopher commented 8 months ago

Is there support for Descriminators/TPH? When I use an OData query without AutoMapper/ODataQueryOptions, I get back @odata.type for my inherited entities. After I switched the DTOs, the responses didn't include type info anymore. Is this a limitation or something I'm doing wrong?

Source/destination types

[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)]
[JsonDerivedType(typeof(RegistrationDTO), "base")]
[JsonDerivedType(typeof(BarnRegistrationDTO), "barn")]
[JsonDerivedType(typeof(EntryRegistrationDTO), "entry")]
public class RegistrationDTO
{
    [Key]
    public Guid RegistrationId { get; set; }

    public HumanDTO Trainer { get; set; }

    public CompetitionDTO Competition { get; set; }
}

public class BarnRegistrationDTO : RegistrationDTO
{
    public required string Name { get; set; }
}

public class EntryRegistrationDTO : RegistrationDTO
{
    public HumanDTO Owner { get; set; }
}

Mapping configuration

        CreateMap<Registration, RegistrationDTO>()
            .ForAllMembers(x => x.ExplicitExpansion());

        CreateMap<BarnRegistration, BarnRegistrationDTO>()
            .IncludeBase<Registration, RegistrationDTO>()
            .ForAllMembers(x => x.ExplicitExpansion());

        CreateMap<EntryRegistration, EntryRegistrationDTO>()
            .IncludeBase<Registration, RegistrationDTO>()
            .ForAllMembers(x => x.ExplicitExpansion());

Version: x.y.z

Expected behavior

{
    "@odata.context": "http://localhost:5155/odata/$metadata#Registrations",
    "value": [
        {
            "@odata.type": "#NS.BarnRegistration",
            "registrationId": "4e09e3b5-accb-4d76-43bd-08dbedbf2aac"
        },
        {
            "@odata.type": "#NS.EntryRegistration",
            "registrationId": "b0175d98-9ed8-431d-43be-08dbedbf2aac",
        }
    ]
}

Actual behavior

http://localhost:5155/odata/Registrations

{
    "@odata.context": "http://localhost:5155/odata/$metadata#Registrations",
    "value": [
        {
            "registrationId": "4e09e3b5-accb-4d76-43bd-08dbedbf2aac"
        },
        {
            "registrationId": "b0175d98-9ed8-431d-43be-08dbedbf2aac"
        }
    ]
}

Steps to reproduce

RegistrationsController

    [HttpGet]
    public async Task<ActionResult> Get(ODataQueryOptions<RegistrationDTO> options)
    {
        return Ok(await _context.Registrations.GetQueryAsync(_mapper, options));
    }

EDMModel

modelBuilder.EntitySet<RegistrationDTO>("Registrations");
lenardchristopher commented 8 months ago

Researching this a bit more, I think it is a limitation of AutoMapper as a layer on top of the ORM. As far as I know, GetQueryAsync is using ProjectTo under the hood. ProjectTo produces a Queryable with no underlying knowledge of the data schema and doesn't know about anything beyond the base type.

In the mapping profile, IncludeBase/IncludeAllDerived only work one way --> exposing the base types mappings to the derived. It would be interesting if there was a way to expose the derived types to the base with attributes similar to JsonDerivedTypeAttribute.

Anyways, sounds like an issue for AutoMapper instead of this OData extension repo. Probably good to close, but would be interested to hear any thoughts.

Side-effect: I decided TPH just isn't worth it when working with DTOs. Broke everything up.

BlaiseD commented 7 months ago

You might want to include an example to make clear what you think is failing. At at high level I AutoMapper doesn't have any knowledge of a discriminator - not that I know of. AutoMapper will map properties between objects whether or not the ORM treats a property as a discriminator.

It sounds like your question is about mapping derived types rather than the ORMs TPH feature. AutoMapper does support adding derived types to the base mappings. See this example.