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

Conflict with AutoMapper v10.1.1, Throws Exception #56

Closed ComtelJeremy closed 3 years ago

ComtelJeremy commented 3 years ago

Hello Jon,

I received the following stack trace when I have AutoMapper 10.1.1 installed (latest).

System.MissingMethodException: Method not found: '!!1 AutoMapper.IMapper.Map(!!0, !!1)'.
   at GenericServices.Internal.MappingCode.CreateMapper+GenericMapper<TDto, TEntity>.MapDtoToEntity(TDto dto, object entity)
   at CallSite.Target(Closure , CallSite , object , ProjectCreateDto , object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid3<T0, T1, T2>(CallSite site, T0 arg0, T1 arg1, T2 arg2)
   at GenericServices.Internal.MappingCode.EntityCreateHandler<TDto>.CreateEntityAndFillFromDto(TDto dto, string methodCtorName)
   at GenericServices.PublicButHidden.CrudServicesAsync<TContext>+<CreateAndSaveAsync>d__9<T>.MoveNext()

I think it would be at this line in your code.

Apparently there is a missing method in the new version of AutoMapper, it seems specifically in the Mapper class. Maybe somewhere around here??? Rolling back to 9.0.0 seemed to fix it. Is there anything that can be done in the library here to allow it to work with either version? Or is there something else I am missing that would allow us to use the latest AutoMapper?

Thank you for your insights,

Jeremy

JonPSmith commented 3 years ago

Hi @ComtelJeremy,

That is strange. There is a call to CreateMapper().Map(dto, entity) which has to be fundamental methods in AutoMapper.

I'll have a look, but I'm very busy so it won't be for a week or more.

PS. Please let me know which version of EfCore.GenericServices you are using.

JonPSmith commented 3 years ago

Note to self. The AutoMapper 10.0 upgrade guide talks about a change to Context.Mapper.Map. I don't use that but it sounds like that effects the code _wrappedMapper.MapperSaveConfig.CreateMapper().Map(dto, entity); in the class CreateMapper.

ComtelJeremy commented 3 years ago

Hi @JonPSmith ,

I'm using 3.2.2, if that helps.

JonPSmith commented 3 years ago

I just updated version 3.2.2 to latest AutoMapper and all the tests works. Clearly there is a problem I I need more info.

I think you will have to provide a complete console app containing your problem and I will have a look.

JonPSmith commented 3 years ago

Hi @ComtelJeremy,

I haven't heard anything back and I can't fix it without some code from you that shows the problem. Therefore I'm closing this issue.

ComtelJeremy commented 3 years ago

@JonPSmith,

Sorry for the delay, and thanks for reminding me... We've been busy here rolling out our new release, and I didn't have a chance to try to repro it once I worked around it.

I can't recreate it now. My previous fix was simply to roll back AutoMapper, and all was good. But now when I update AutoMapper back (and expected it to cause the issue again) it doesn't throw... So strange, as nothing else changed. I even reverted to the commit where we had the issue, and no dice.

Since that issue I have cleared my local Visual Studio nuget cache (to fix a global tool), so the only thing I can think of was that I didn't have the updated AutoMapper (the nuget cache was off or something) and that's why it threw. In any case, if it ever shows up again and I can repro it, I'll let you know.

Sorry for the bother.

Jeremy

JonPSmith commented 3 years ago

Hi @ComtelJeremy,

No problem. Glad its sorted.

PS. One possible explanation is you had different AutoMapper versions across your projects.

ComtelJeremy commented 3 years ago

Hi Jon (@JonPSmith),

FYI, this bit us again, this time it made it (due to some missed testing by us) to our Staging and Production (which is built using dev ops - so I know it isn't a nuget cache issue). It again seems to be limited to CreateAndSaveAsync. In this release we updated AutoMapper from 9.0.0 to the latest (10.1.1). It only seems to affect CreateAndSave and only when a dto is passed, not an entity class, so we missed it in testing.

I narrowed it down to this line: https://github.com/JonPSmith/EfCore.GenericServices/blob/0e7e7e50a69124b77ceaecc79e8bd2cc75853594/GenericServices/Internal/MappingCode/CreateMapper.cs#L71

it throws this:

System.MissingMethodException: Method not found: '!!1 AutoMapper.IMapper.Map(!!0, !!1)'.
   at GenericServices.Internal.MappingCode.CreateMapper+GenericMapper<TDto, TEntity>.MapDtoToEntity(TDto dto, object entity)
   at CallSite.Target(Closure , CallSite , object , ProjectCreateDto , object )
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid3<T0, T1, T2>(CallSite site, T0 arg0, T1 arg1, T2 arg2)
   at GenericServices.Internal.MappingCode.EntityCreateHandler<TDto>.CreateEntityAndFillFromDto(TDto dto, string methodCtorName)
   at GenericServices.PublicButHidden.CrudServicesAsync<TContext>+<CreateAndSaveAsync>d__9<T>.MoveNext()

It appears that this is the case because there is no overload in Automapper that I could see that supports a typed dto with an object (boxed) entity as shown here: https://github.com/AutoMapper/AutoMapper/blob/bdc0120497d192a2741183415543f6119f50a982/src/AutoMapper/Mapper.cs#L156-L170

        public TDestination Map<TDestination>(object source) => Map(source, default(TDestination));
        public TDestination Map<TDestination>(object source, Action<IMappingOperationOptions<object, TDestination>> opts) => Map(source, default, opts);
        public TDestination Map<TSource, TDestination>(TSource source) => Map(source, default(TDestination));
        public TDestination Map<TSource, TDestination>(TSource source, Action<IMappingOperationOptions<TSource, TDestination>> opts) =>
            Map(source, default, opts);
        public TDestination Map<TSource, TDestination>(TSource source, TDestination destination) =>
            MapCore(source, destination, DefaultContext);
        public TDestination Map<TSource, TDestination>(TSource source, TDestination destination, Action<IMappingOperationOptions<TSource, TDestination>> opts) =>
            MapWithOptions(source, destination, opts);
        public object Map(object source, Type sourceType, Type destinationType) => Map(source, null, sourceType, destinationType);
        public object Map(object source, Type sourceType, Type destinationType, Action<IObjectMappingOperationOptions> opts) =>
            Map(source, null, sourceType, destinationType, opts);
        public object Map(object source, object destination, Type sourceType, Type destinationType) =>
            MapCore(source, destination, DefaultContext, sourceType, destinationType);
        public object Map(object source, object destination, Type sourceType, Type destinationType, Action<IObjectMappingOperationOptions> opts) =>

It seems to be an issue since you pass in an entity of type object, not the specific entity type (or use a generic overload). Is it possible for you to use a typed overload?

I can recreate the issue locally if you need me to try something. I have checked and all of the references to AutoMapper are to the latest across all our projects, and the Automapper.dll in the bin\debug is 10.1.1.

This shows up when we call CreateAndSaveAsync passing in a dto. If we pass in the entity type itself, this is not an issue and no error is thrown. I assume it calls a different method, as no mapping is needed?

For now we are manually mapping anywhere there is a dto... Fortunately there aren't too many maps to change, but it would be nice to get it auto-mapping again.

JonPSmith commented 3 years ago

Hi @ComtelJeremy,

OK, I'm busy at the moment so it won't be a quick reply.

One question: are you using the version 3.2.2 or the 5.0.0?

ComtelJeremy commented 3 years ago

Hi @JonPSmith, we are using 3.2.2, as we have not moved to NET 5 yet.

JonPSmith commented 3 years ago

Hi @ComtelJeremy,

I had a look at it and its a weird bug when an new version of AutoMapper overrides a old version of AutoMapper. This is confirmed by Version 5.0.0 which uses AutoMapper 10.1.1 with not problem. If the unit test had AutoMapper 10.1.1 and GenericServices had AutoMapper 9.0.0 it fails. If both have AutoMapper 10.1.1 it works.

I have released version 3.3.0 using AutoMapper 10.1.1. Please check this works for you.

ComtelJeremy commented 3 years ago

@JonPSmith Thank you for checking it out. We will try it shortly.

ComtelJeremy commented 3 years ago

@JonPSmith, this fixes the issue! Thank you again. And just in time... since your comment and release we had one other area where this showed up in Production, and the 3.3.0 corrected it with no other code changes! 👍🏻