AutoMapper / AutoMapper.Collection

AutoMapper support for updating existing collections by equivalency
MIT License
245 stars 59 forks source link

Expression of type '<Type>' cannot be used for parameter of type '<OtherType>' of method 'Void Add(<OtherType>)' (Parameter 'arg0') #175

Closed davidsavagejr closed 1 year ago

davidsavagejr commented 1 year ago

Upgrading From Automapper: 10.1.1 Automapper.Collection: 7.0.1 net 6.0

Upgrading To Automapper: 12.0.1 Automapper.Collection: 9.0.0 net 7.0

I have a working project right now that I'm upgrading so, all I've done so far is simply upgrade the packages (see above). I'm running into an issue when the Automapper configuration tries to get constructed:

OneTimeSetUp: System.ArgumentException : Expression of type 'Type1' cannot be used for parameter of type 'Type2' of method 'Void Add(Type2)' (Parameter 'arg0')

Stack Trace

at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0) at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable1 arguments) at AutoMapper.Internal.Mappers.CollectionMapper.<MapExpression>g__MapCollectionCore|3_1(Expression destExpression, <>c__DisplayClass3_0&) at AutoMapper.Internal.Mappers.CollectionMapper.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) at AutoMapper.Mappers.EquivalentExpressionAddRemoveCollectionMapper.MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) at AutoMapper.Execution.ExpressionBuilder.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, TypePair typePair, Expression source, MemberMap memberMap, Expression destination) at AutoMapper.Execution.TypeMapPlanBuilder.MapMember(MemberMap memberMap, ParameterExpression resolvedValue, Expression destinationMemberValue) at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember) at AutoMapper.Execution.TypeMapPlanBuilder.AddPropertyMaps(List1 actions) at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination) at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda() at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration) at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration) at AutoMapper.Execution.ExpressionBuilder.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, TypePair typePair, Expression source, MemberMap memberMap, Expression destination) at AutoMapper.Internal.Mappers.CollectionMapper.gMapCollectionCore|3_1(Expression destExpression, <>c__DisplayClass3_0&) at AutoMapper.Internal.Mappers.CollectionMapper.gMapReadOnlyCollection|3_0(Type genericCollectionType, Type genericReadOnlyCollectionType, <>cDisplayClass3_0&) at AutoMapper.Internal.Mappers.CollectionMapper.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) at AutoMapper.Mappers.EquivalentExpressionAddRemoveCollectionMapper.MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) at AutoMapper.Execution.ExpressionBuilder.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, TypePair typePair, Expression source, MemberMap memberMap, Expression destination) at AutoMapper.Execution.TypeMapPlanBuilder.MapMember(MemberMap memberMap, ParameterExpression resolvedValue, Expression destinationMemberValue) at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember) at AutoMapper.Execution.TypeMapPlanBuilder.AddPropertyMaps(List`1 actions) at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination) at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda() at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration) at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration) at AutoMapper.Execution.ExpressionBuilder.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, TypePair typePair, Expression source, MemberMap memberMap, Expression destination) at AutoMapper.Internal.Mappers.CollectionMapper.g__MapCollectionCore|3_1(Expression destExpression, <>cDisplayClass3_0&) at AutoMapper.Internal.Mappers.CollectionMapper.gMapReadOnlyCollection|3_0(Type genericCollectionType, Type genericReadOnlyCollectionType, <>c__DisplayClass3_0&) at AutoMapper.Internal.Mappers.CollectionMapper.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) at AutoMapper.Mappers.EquivalentExpressionAddRemoveCollectionMapper.MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) at AutoMapper.Execution.ExpressionBuilder.MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, TypePair typePair, Expression source, MemberMap memberMap, Expression destination) at AutoMapper.Execution.TypeMapPlanBuilder.MapMember(MemberMap memberMap, ParameterExpression resolvedValue, Expression destinationMemberValue) at AutoMapper.Execution.TypeMapPlanBuilder.CreatePropertyMapFunc(MemberMap memberMap, Expression destination, MemberInfo destinationMember) at AutoMapper.Execution.TypeMapPlanBuilder.AddPropertyMaps(List`1 actions) at AutoMapper.Execution.TypeMapPlanBuilder.CreateAssignmentFunc(Expression createDestination) at AutoMapper.Execution.TypeMapPlanBuilder.CreateMapperLambda() at AutoMapper.TypeMap.CreateMapperLambda(IGlobalConfiguration configuration) at AutoMapper.TypeMap.Seal(IGlobalConfiguration configuration) at AutoMapper.MapperConfiguration.<.ctor>gSeal|20_0() at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression) at AutoMapper.MapperConfiguration..ctor(Action`1 configure) at ArticulationMod.Common.CommonRegistry.<>c.<.ctor>b__0_0(IServiceContext ctx)

When I remove the following line: cfg.AddCollectionMappers();

The above exception goes away however, I obviously run into mapping issues in my application where collection mappings happen (incorrectly); My configuration is built by simply scanning all my assemblies for Profile and adding them all to the configuration.

I am using Lamar IoC:

For<IConfigurationProvider>().Use(ctx => new MapperConfiguration(cfg =>
        {           
            cfg.AddProfiles(ctx.GetAllInstances<Profile>());
            // Add-On to map collections without re-creating them
            cfg.AddCollectionMappers();
            cfg.ConstructServicesUsing(ctx.GetInstance);
            cfg.DisableConstructorMapping();

        })).Scoped();
For<IMapper>().Use(ctx => new Mapper(ctx.GetInstance<IConfigurationProvider>(), ctx.GetInstance)).Singleton();

The only other information I can think to offer are my class structures and mapping configurations

public class Type1: IType { }

public class Type2 : Child1 { }
public class Child1 : Child2 { }
public class Child2 : IChild { }

CreateMap<Child2, Child2>();
CreateMap<Child2, IChild>();
CreateMap<IChild, Child2>();
CreateMap<IType, IChild>();
CreateMap<IChild, IType>();

CreateMap<Type1, Type2>()
                .IncludeBase<IType, IChild>();

Based on the stack trace, I'm unsure if there is a mapping issue I'm missing or not

lbargaoanu commented 1 year ago

A PR is welcome.

davidsavagejr commented 1 year ago

This appears to be related to: https://github.com/AutoMapper/AutoMapper/issues/3526 except its popping up in the Collections init.

Honestly not sure where to slap the validation in for this but what it turned out to be was this:

public interface ISomeInterface {} 
public class SomeBaseClass : ISomeInterface {}

public class ProblemClass : SomeBaseClass {}

When the mapper initializes it tries to construct SomeBaseClass for the expression to add items to the collection; which obviously blows up because you can't add items of that type to a collection of ProblemClass

In previous versions of Automapper/Automapper Collection it appeared to allow you to do this and would pick up on your inheritance...

CreateMap<..., ProblemClass>()
                .IncludeBase<.., ISomeInterface>()

Solution There's actually a missing mapping:

CreateMap<SomeBaseClass, ProblemClass>();

Once this mapping is in place, I had no issues and my configuration would construct and assert as valid.

lbargaoanu commented 1 year ago

Good to know :)