JonPSmith / EfCore.GenericServices

A library to help you quickly code CRUD accesses for a web/mobile/desktop application using EF Core.
https://www.thereformedprogrammer.net/genericservices-a-library-to-provide-crud-front-end-services-from-a-ef-core-database/
MIT License
601 stars 94 forks source link

Using `ProjectFromEntityToDto` without `ILinkToEntity` on DTO #71

Open aaron-kruse opened 5 months ago

aaron-kruse commented 5 months ago

When using ProjectFromEntityToDto<TEntity, TDto>, both an entity type and DTO must be specified in the call, so technically it shouldn't be necessary to include ILinkToEntity on the DTO (the method already knows what entity we want the DTO to be "linked" to). However, because of the way all the read methods are implemented (using _configAndMapper.MapperReadConfig), this requirement also applies to ProjectFromEntityToDto.

How feasible would it be to remove this requirement so ProjectFromEntityToDto could be called on DTOs that don't include ILinkToEntity? Note that it would probably have to first try to use any mapping info that has already been setup (in case there's any "special" configuration, ex. from PerDtoConfig), but if a mapping doesn't already exist, then use a "temporary mapping" based on the TEntity and TDto passed in.

For some background, most of our DTOs do include ILinkToEntity, but we have some for entities using the TPH pattern where it would be nice to avoid this. We have a Note entity (abstract class) with some derived classes like CustomerNote, VendorNote, ShipToNote, etc. and as it currently stands, we would need DTOs for each derived class just so we can include ILinkToEntity (ex. ViewCustomerNoteDto : ILinkToEntity<CustomerNote>). If this requirement didn't apply to ProjectFromEntityToDto, then we could just use a single "base" ViewEntityNoteDto and not need entity-specific DTOs. I'm leaving out some of the details for brevity, we're also using methods with generic types for data access and have some other things going on to make this possible, but this is the general gist of it.

For reference, I tested and something like this does work, but seems a little sloppy:

public IQueryable<TDto> ProjectFromEntityToDto<TEntity,TDto>(Func<IQueryable<TEntity>, IQueryable<TEntity>> query) where TEntity : class
{
    var entities = query(_context.Set<TEntity>());

    try
    {
        return entities.ProjectTo<TDto>(_configAndMapper.MapperReadConfig);
    }
    catch (InvalidOperationException)
    {
        // Assume (hope?) we caught an AutoMapper exception due to a missing map
        var mapConfig = new MapperConfiguration(cfg => cfg.CreateMap<TEntity, TDto>());
        return entities.ProjectTo<TDto>(mapConfig);
    }
}

As the guy that wrote all the GenericServices code and originally figured all this out, I was hoping you could maybe provide some insight on how feasible this might be 🙂 If the code above could be updated to replace the try/catch with something that checks ReadMapperConfig for a mapping between TEntity and TDto, then that might be all that's really needed, but I'm not sure that anything currently exists to check for an existing mapping.