-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
After upgrading from 12.0.0 to 12.0.1 we have been unable to even construct a MapperConfiguration from our existing profiles. I've been able to isolate the problem down to a combination of:
- An
enumthat usesConvertUsingin combination withCreateProjection - A call to
CreateProjectionfor a class that has a property of theenum - A call to
ForAllMaps
The exception does not occur if you remove any one of the above items. It also does not occur if you remove the enum property from the class in 2.
See the More Details section below for an explanation.
Source/destination types
public class Source
{
public string Name { get; set; }
public SourceEnum Value { get; set; }
}
public class Dest
{
public string Name { get; set; }
public DestEnum Value { get; set; }
}
public enum SourceEnum { A, B }
public enum DestEnum { A, B }Mapping configuration
var cfgExp = new MapperConfigurationExpression();
cfgExp.AddProfile<ExampleProfile>();
_ = new MapperConfiguration(cfgExp); // exception thrown here, but see below
public class ExampleProfile : Profile
{
public ExampleProfile()
{
CreateProjection<SourceEnum, DestEnum>() // 1; Necessary for Postgres-style Enums which are not int-based
.ConvertUsing(
src => src == SourceEnum.A ? DestEnum.A : DestEnum.B
);
CreateProjection<Source, Dest>(); // 2
this.Internal().ForAllMaps(static (_, _) => { }); // 3; don't need to do anything in the callback
}
}Version: 12.0.1
Fails on Version 12.0.1
Works on Version 12.0.0
Also using EF7 but that really doesn't matter as the exception can be reproduced without it.
Expected behavior
Does not throw an exception creating the map/configuration and can be successfully used. Works correctly in 12.0.0
Actual behavior
An Exception is thrown when constructing the mapper expression. It's impossible to use this profile to create a Mapper. The Exception occurs after the final invocation of the ForAllMaps method.
System.InvalidOperationException
$(String[] args) in C:\Users\pinkfloyx33\source\repos\AutoMapperBug\AutoMapperBug\Program.cs:line 7
HResult=0x80131509
Message=Incorrect number of arguments supplied for lambda invocation
Source=System.Linq.Expressions
StackTrace:
at System.Dynamic.Utils.ExpressionUtils.ValidateArgumentCount(MethodBase method, ExpressionType nodeKind, Int32 count, ParameterInfo[] pis)
at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression arg0, Expression arg1, Expression arg2)
at System.Linq.Expressions.Expression.Invoke(Expression expression, IEnumerable`1 arguments)
at AutoMapper.TypeMap.Invoke(Expression source, Expression destination)
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>g__Seal|20_0()
at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression configurationExpression)
at Program.
Note that the Exception is not thrown if you remove any of lines 1, 2, or 3 in the ExampleProfile above.
Steps to reproduce
Create MapperConfiguration from attached profile (see above)
More Details
We are using native Postgres enums in our project which is based on EFcore7. A native Postgres enum is not number-based and as such a direct projection cannot be performed (based on the name/value) and will throw an error at runtime (from npgsql). To get around this you need to create a projection for the enum types and use ConvertUsing with (nested) ternary expressions to account for all cases. This has always worked properly for us.
ForAllMaps - I realize this in the Internal namespace, so why do we use it? We have a common set of audit fields (create/modify date/user) across all of our Db and API models. Unfortunately:
- the names aren't exact matches to each other (even accounting for prefixes) thanks to different sets of company-wide conventions
- the date fields are
DateTimeOffsetin the API model andDateTimeat the db-level- Postgres doesn't support
DateTimeOffsetin the classic sense (ie. time+offset) and will normalize the offset on read to the server's local time.DateTimeis preferred and always tracks UTC - Additionally, the modified date is nullable which requires a cast in the mapping/projection expressions
- Postgres doesn't support
Rather than specifying the two sets of member mappings/nullable casts for every map, in every profile, we have an extension method that calls ForAllMaps which in turn checks the target/destination types for the audit-related interfaces and adds the 4 member maps as required. This has cleaned up our profiles a great deal and we'd prefer to keep it this way if possible.
For the purposes of this report however, it is not necessary to perform any action within the callback. Calling the method is enough to trigger the exception.
Removing any one line from the profile will prevent the exception--at the expense of an incorrect profile. Removing the Value property from the Source/Dest classes will also prevent the exception, though this is obviously also incorrect.