diff --git a/AutoMapper.sln b/AutoMapper.sln index 194e366c0c..92bcf049c6 100644 --- a/AutoMapper.sln +++ b/AutoMapper.sln @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\ci.yml = .github\workflows\ci.yml CONTRIBUTING.md = CONTRIBUTING.md Directory.Build.props = Directory.Build.props + icon.png = icon.png ISSUE_TEMPLATE.md = ISSUE_TEMPLATE.md nuget.config = nuget.config Push.ps1 = Push.ps1 diff --git a/docs/12.0-Upgrade-Guide.md b/docs/12.0-Upgrade-Guide.md new file mode 100644 index 0000000000..b454ada3f8 --- /dev/null +++ b/docs/12.0-Upgrade-Guide.md @@ -0,0 +1,20 @@ +# 12.0 Upgrade Guide + +[Release notes](https://github.com/AutoMapper/AutoMapper/releases/tag/v12.0.0). + +## Equivalent settings overwrite each other + +That applies per map and also per member. For example, you can have only one type converter per map and only one resolver per member. + +It might not be obvious that some settings are equivalent. For example, a value converter is a special kind of resolver, so a `ConvertUsing` will overwrite a `MapFrom` +for the same member. + +You also cannot have for the same map/member separate configurations for `Map` and `ProjectTo`. + +Another possible occurence is with `ForAllMaps` and `ForAllPropertyMaps` when it's possible to overwrite things already set in a particular map. + +## `ResolutionContext.Options` was removed + +You should use `ResolutionContext.Items` to access the items passed in the `Map` call. + +Instead of `ServiceCtor` you should use dependency injection or pass the needed objects in the `Map` call. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 0e254e2392..24f0f4d003 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -72,6 +72,7 @@ New to AutoMapper? Check out the :doc:`Getting-started` page first. :caption: Upgrading API-Changes + 12.0-Upgrade-Guide 11.0-Upgrade-Guide 10.0-Upgrade-Guide 9.0-Upgrade-Guide diff --git a/src/AutoMapper/PreBuild.ps1 b/src/AutoMapper/ApiCompat/PreBuild.ps1 similarity index 100% rename from src/AutoMapper/PreBuild.ps1 rename to src/AutoMapper/ApiCompat/PreBuild.ps1 diff --git a/src/AutoMapper/PreBuild.sh b/src/AutoMapper/ApiCompat/PreBuild.sh old mode 100755 new mode 100644 similarity index 100% rename from src/AutoMapper/PreBuild.sh rename to src/AutoMapper/ApiCompat/PreBuild.sh diff --git a/src/AutoMapper/ApiCompatBaseline.txt b/src/AutoMapper/ApiCompatBaseline.txt index 125a1cf915..203ce3e707 100644 --- a/src/AutoMapper/ApiCompatBaseline.txt +++ b/src/AutoMapper/ApiCompatBaseline.txt @@ -3,6 +3,8 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe InterfacesShouldHaveSameMembers : Interface member 'public TMappingExpression AutoMapper.IMappingExpressionBase.AsProxy()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void AutoMapper.IMappingExpressionBase.AsProxy()' is present in the contract but not in the implementation. MembersMustExist : Member 'public void AutoMapper.IMappingExpressionBase.AsProxy()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public AutoMapper.IMappingOperationOptions AutoMapper.ResolutionContext.Options.get()' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'AutoMapper.ValueResolverConfiguration' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void AutoMapper.Configuration.MappingExpressionBase.AsProxy()' does not exist in the implementation but it does exist in the contract. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.IgnoreAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.MapAtRuntimeAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. @@ -12,4 +14,7 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.UseExistingValueAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.ValueConverterAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.ValueResolverAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. -Total Issues: 13 +MembersMustExist : Member 'public System.Linq.IQueryable AutoMapper.QueryableExtensions.Extensions.Map(System.Linq.IQueryable, System.Linq.IQueryable, AutoMapper.IConfigurationProvider)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Collections.Generic.IReadOnlyCollection AutoMapper.QueryableExtensions.MemberVisitor.MemberPath.get()' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'AutoMapper.QueryableExtensions.Impl.MemberAccessQueryMapperVisitor' does not exist in the implementation but it does exist in the contract. +Total Issues: 18 diff --git a/src/AutoMapper/AutoMapper.csproj b/src/AutoMapper/AutoMapper.csproj index 079f9d533c..97045c73a6 100644 --- a/src/AutoMapper/AutoMapper.csproj +++ b/src/AutoMapper/AutoMapper.csproj @@ -32,8 +32,8 @@ - - + + @@ -44,8 +44,8 @@ - - + + diff --git a/src/AutoMapper/Configuration/Annotations/SourceMemberAttribute.cs b/src/AutoMapper/Configuration/Annotations/SourceMemberAttribute.cs index fc74b0e0f3..de1d7cbcdf 100644 --- a/src/AutoMapper/Configuration/Annotations/SourceMemberAttribute.cs +++ b/src/AutoMapper/Configuration/Annotations/SourceMemberAttribute.cs @@ -1,4 +1,6 @@ -using System; +using AutoMapper.Internal; +using System; +using System.Reflection; namespace AutoMapper.Configuration.Annotations { @@ -17,6 +19,11 @@ public sealed class SourceMemberAttribute : Attribute, IMemberConfigurationProvi public void ApplyConfiguration(IMemberConfigurationExpression memberConfigurationExpression) { + var destinationMember = memberConfigurationExpression.DestinationMember; + if (destinationMember.Has() || destinationMember.Has()) + { + return; + } memberConfigurationExpression.MapFrom(Name); } } diff --git a/src/AutoMapper/Configuration/ConfigurationValidator.cs b/src/AutoMapper/Configuration/ConfigurationValidator.cs index 43f701a72e..cf889c3d2c 100644 --- a/src/AutoMapper/Configuration/ConfigurationValidator.cs +++ b/src/AutoMapper/Configuration/ConfigurationValidator.cs @@ -117,7 +117,6 @@ private void DryRunTypeMap(ICollection typeMapsChecked, TypePair types, } CheckPropertyMaps(typeMapsChecked, typeMap); - typeMap.IsValid = true; } else { @@ -143,7 +142,7 @@ private void CheckPropertyMaps(ICollection typeMapsChecked, TypeMap typ { foreach (var memberMap in typeMap.MemberMaps) { - if(memberMap.Ignored || memberMap.ValueConverterConfig != null || memberMap.ValueResolverConfig != null) + if(memberMap.Ignored) { continue; } diff --git a/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs b/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs index 83f121e3e6..5a9aeb6799 100644 --- a/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs +++ b/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs @@ -1,4 +1,5 @@ -using AutoMapper.Internal; +using AutoMapper.Execution; +using AutoMapper.Internal; using System; using System.Collections.Generic; using System.ComponentModel; @@ -51,12 +52,12 @@ public CtorParamConfigurationExpression(string ctorParamName, Type sourceType) } public void MapFrom(Expression> sourceMember) => - _ctorParamActions.Add(cpm => cpm.CustomMapExpression = sourceMember); + _ctorParamActions.Add(cpm => cpm.SetResolver(sourceMember)); public void MapFrom(Func resolver) { Expression> resolverExpression = (src, dest, destMember, ctxt) => resolver(src, ctxt); - _ctorParamActions.Add(cpm => cpm.CustomMapFunction = resolverExpression); + _ctorParamActions.Add(cpm => cpm.Resolver = new FuncResolver(resolverExpression)); } public void MapFrom(string sourceMembersPath) @@ -75,13 +76,12 @@ public void Configure(TypeMap typeMap) var parameter = ctorMap[CtorParamName]; if (parameter == null) { - throw new AutoMapperConfigurationException($"{typeMap.DestinationType.Name} does not have a constructor with a parameter named '{CtorParamName}'.\n{typeMap.DestinationType.FullName}"); + throw new AutoMapperConfigurationException($"{typeMap.DestinationType.Name} does not have a matching constructor with a parameter named '{CtorParamName}'.\n{typeMap.DestinationType.FullName}"); } foreach (var action in _ctorParamActions) { action(parameter); } - parameter.CanResolveValue = true; } } } \ No newline at end of file diff --git a/src/AutoMapper/Configuration/MapperConfiguration.cs b/src/AutoMapper/Configuration/MapperConfiguration.cs index c5def1e112..d378775cc4 100644 --- a/src/AutoMapper/Configuration/MapperConfiguration.cs +++ b/src/AutoMapper/Configuration/MapperConfiguration.cs @@ -444,7 +444,7 @@ TypeMap GetIncludedTypeMap(TypePair pair) // we want the exact map the user included, but we could instantiate an open generic if (typeMap?.Types != pair) { - throw QueryMapperHelper.MissingMapException(pair); + throw TypeMap.MissingMapException(pair); } return typeMap; } diff --git a/src/AutoMapper/Configuration/MappingExpression.cs b/src/AutoMapper/Configuration/MappingExpression.cs index e8c717728e..371b118c3c 100644 --- a/src/AutoMapper/Configuration/MappingExpression.cs +++ b/src/AutoMapper/Configuration/MappingExpression.cs @@ -36,8 +36,7 @@ public IMappingExpression IncludeMembers(params string[] memberNames) IncludedMembersNames = memberNames; foreach(var memberName in memberNames) { - SourceType.GetFieldOrProperty(memberName); - ForSourceMemberCore(memberName, o => o.DoNotValidate()); + SourceType.GetFieldOrProperty(memberName); } TypeMapActions.Add(tm => tm.IncludedMembersNames = memberNames); return this; @@ -79,66 +78,32 @@ internal MemberConfigurationExpression ForMember(MemberInfo destinationProperty, public class MemberConfigurationExpression : MemberConfigurationExpression, IMemberConfigurationExpression { - public MemberConfigurationExpression(MemberInfo destinationMember, Type sourceType) - : base(destinationMember, sourceType) + public MemberConfigurationExpression(MemberInfo destinationMember, Type sourceType) : base(destinationMember, sourceType) { } - - public void MapFrom(Type valueResolverType) - { - var config = new ValueResolverConfiguration(valueResolverType, valueResolverType.GetGenericInterface(typeof(IValueResolver<,,>))); - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void MapFrom(Type valueResolverType, string sourceMemberName) - { - var config = new ValueResolverConfiguration(valueResolverType, valueResolverType.GetGenericInterface(typeof(IMemberValueResolver<,,,>))) + public void MapFrom(Type valueResolverType) => MapFromCore(new(valueResolverType, valueResolverType.GetGenericInterface(typeof(IValueResolver<,,>)))); + public void MapFrom(Type valueResolverType, string sourceMemberName) => + MapFromCore(new(valueResolverType, valueResolverType.GetGenericInterface(typeof(IMemberValueResolver<,,,>))) { SourceMemberName = sourceMemberName - }; - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void MapFrom(IMemberValueResolver resolver, string sourceMemberName) - { - var config = new ValueResolverConfiguration(resolver, typeof(IMemberValueResolver)) + }); + public void MapFrom(IMemberValueResolver resolver, string sourceMemberName) => + MapFromCore(new(resolver, typeof(IMemberValueResolver)) { SourceMemberName = sourceMemberName - }; - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void ConvertUsing(Type valueConverterType) - => PropertyMapActions.Add(pm => ConvertUsing(pm, valueConverterType)); - - public void ConvertUsing(Type valueConverterType, string sourceMemberName) - => PropertyMapActions.Add(pm => ConvertUsing(pm, valueConverterType, sourceMemberName)); - - public void ConvertUsing(IValueConverter valueConverter, string sourceMemberName) - { - PropertyMapActions.Add(pm => + }); + public void ConvertUsing(Type valueConverterType) => ConvertUsingCore(valueConverterType); + public void ConvertUsing(Type valueConverterType, string sourceMemberName) => ConvertUsingCore(valueConverterType, sourceMemberName); + public void ConvertUsing(IValueConverter valueConverter, string sourceMemberName) => + base.ConvertUsingCore(new(valueConverter, typeof(IValueConverter)) { - var config = new ValueResolverConfiguration(valueConverter, typeof(IValueConverter)) - { - SourceMemberName = sourceMemberName - }; - - pm.ValueConverterConfig = config; + SourceMemberName = sourceMemberName }); - } - - private static void ConvertUsing(PropertyMap propertyMap, Type valueConverterType, string sourceMemberName = null) - { - var config = new ValueResolverConfiguration(valueConverterType, valueConverterType.GetGenericInterface(typeof(IValueConverter<,>))) + private void ConvertUsingCore(Type valueConverterType, string sourceMemberName = null) => + base.ConvertUsingCore(new(valueConverterType, valueConverterType.GetGenericInterface(typeof(IValueConverter<,>))) { SourceMemberName = sourceMemberName - }; - - propertyMap.ValueConverterConfig = config; - } + }); } } @@ -176,14 +141,7 @@ public IMappingExpression ForMember(Expression memberExpression.GetMember()).Where(member => member != null)) - { - ForSourceMemberCore(member, o => o.DoNotValidate()); - } - TypeMapActions.Add(tm => tm.IncludedMembers = memberExpressions); - } + private void IncludeMembersCore(LambdaExpression[] memberExpressions) => TypeMapActions.Add(tm => tm.IncludedMembers = memberExpressions); public IMappingExpression IncludeMembers(params Expression>[] memberExpressions) { diff --git a/src/AutoMapper/Configuration/MappingExpressionBase.cs b/src/AutoMapper/Configuration/MappingExpressionBase.cs index 11d05fd780..5c8cb30a3a 100644 --- a/src/AutoMapper/Configuration/MappingExpressionBase.cs +++ b/src/AutoMapper/Configuration/MappingExpressionBase.cs @@ -166,29 +166,23 @@ private void GlobalIgnores(TypeMap typeMap, IReadOnlyCollection globalIg private void MapDestinationCtorToSource(TypeMap typeMap) { var sourceMembers = new List(); + ConstructorMap ctorMap = new(); + typeMap.ConstructorMap = ctorMap; foreach (var destCtor in typeMap.DestinationConstructors) { var constructor = destCtor.Constructor; - var ctorMap = new ConstructorMap(constructor, typeMap); + ctorMap.Reset(constructor); bool canMapResolve = true; foreach (var parameter in destCtor.Parameters) { sourceMembers.Clear(); var canResolve = typeMap.Profile.MapDestinationPropertyToSource(typeMap.SourceTypeDetails, constructor.DeclaringType, parameter.ParameterType, parameter.Name, sourceMembers, IsReverseMap); - if (!canResolve) + if (!canResolve && !parameter.IsOptional && !IsConfigured(parameter)) { - if (parameter.IsOptional || IsConfigured(parameter)) - { - canResolve = true; - } - else - { - canMapResolve = false; - } + canMapResolve = false; } - ctorMap.AddParameter(parameter, sourceMembers, canResolve); + ctorMap.AddParameter(parameter, sourceMembers, typeMap); } - typeMap.ConstructorMap = ctorMap; if (canMapResolve) { ctorMap.CanResolve = true; @@ -234,7 +228,7 @@ private void ReverseSourceMembers(MemberPath memberPath, LambdaExpression custom var pathMap = reverseTypeMap.FindOrCreatePathMapFor(forPathLambda, memberPath, reverseTypeMap); - pathMap.CustomMapExpression = customExpression; + pathMap.SetResolver(customExpression); }); } @@ -307,37 +301,22 @@ public TMappingExpression MaxDepth(int depth) public TMappingExpression ConstructUsingServiceLocator() { - TypeMapActions.Add(tm => tm.ConstructDestinationUsingServiceLocator = true); + TypeMapActions.Add(tm => tm.ConstructUsingServiceLocator()); return this as TMappingExpression; } - public TMappingExpression BeforeMap(Action beforeFunction) - { - TypeMapActions.Add(tm => - { - Expression> expr = - (src, dest, ctxt) => beforeFunction(src, dest); - - tm.AddBeforeMapAction(expr); - }); - - return this as TMappingExpression; - } + public TMappingExpression BeforeMap(Action beforeFunction) => BeforeMapCore((src, dest, ctxt) => beforeFunction(src, dest)); - public TMappingExpression BeforeMap(Action beforeFunction) + private TMappingExpression BeforeMapCore(Expression> expr) { - TypeMapActions.Add(tm => - { - Expression> expr = - (src, dest, ctxt) => beforeFunction(src, dest, ctxt); - - tm.AddBeforeMapAction(expr); - }); - + TypeMapActions.Add(tm => tm.AddBeforeMapAction(expr)); return this as TMappingExpression; } + public TMappingExpression BeforeMap(Action beforeFunction) => + BeforeMapCore((src, dest, ctxt) => beforeFunction(src, dest, ctxt)); + public TMappingExpression BeforeMap() where TMappingAction : IMappingAction => BeforeMap(CallMapAction); public TMappingExpression AfterMap() where TMappingAction : IMappingAction => @@ -345,32 +324,17 @@ public TMappingExpression AfterMap() where TMappingAction : IMap private static void CallMapAction(TSource source, TDestination destination, ResolutionContext context) => ((IMappingAction)context.CreateInstance(typeof(TMappingAction))).Process(source, destination, context); - public TMappingExpression AfterMap(Action afterFunction) - { - TypeMapActions.Add(tm => - { - Expression> expr = - (src, dest, ctxt) => afterFunction(src, dest); + public TMappingExpression AfterMap(Action afterFunction) => AfterMapCore((src, dest, ctxt) => afterFunction(src, dest)); - tm.AddAfterMapAction(expr); - }); - - return this as TMappingExpression; - } - - public TMappingExpression AfterMap(Action afterFunction) + private TMappingExpression AfterMapCore(Expression> expr) { - TypeMapActions.Add(tm => - { - Expression> expr = - (src, dest, ctxt) => afterFunction(src, dest, ctxt); - - tm.AddAfterMapAction(expr); - }); - + TypeMapActions.Add(tm => tm.AddAfterMapAction(expr)); return this as TMappingExpression; } + public TMappingExpression AfterMap(Action afterFunction) => + AfterMapCore((src, dest, ctxt) => afterFunction(src, dest, ctxt)); + public TMappingExpression PreserveReferences() { TypeMapActions.Add(tm => tm.PreserveReferences = true); @@ -434,62 +398,44 @@ public void As(Type typeOverride) TypeMapActions.Add(tm => tm.DestinationTypeOverride = typeOverride); } - public TMappingExpression ConstructUsing(Expression> ctor) - { - TypeMapActions.Add(tm => tm.CustomCtorExpression = ctor); + public TMappingExpression ConstructUsing(Expression> ctor) => ConstructUsingCore(ctor); + private TMappingExpression ConstructUsingCore(LambdaExpression ctor) + { + TypeMapActions.Add(tm => tm.CustomCtorFunction = ctor); return this as TMappingExpression; } public TMappingExpression ConstructUsing(Func ctor) { - TypeMapActions.Add(tm => - { - Expression> expr = (src, ctxt) => ctor(src, ctxt); - - tm.CustomCtorFunction = expr; - }); - - return this as TMappingExpression; + Expression> expr = (src, ctxt) => ctor(src, ctxt); + return ConstructUsingCore(expr); } public void ConvertUsing(Type typeConverterType) { HasTypeConverter = true; - TypeMapActions.Add(tm => tm.TypeConverterType = typeConverterType); + TypeMapActions.Add(tm => tm.TypeConverter = new ClassTypeConverter(typeConverterType, tm.Types.ITypeConverter())); } - public void ConvertUsing(Func mappingFunction) - { - HasTypeConverter = true; - TypeMapActions.Add(tm => - { - Expression> expr = - (src, dest, ctxt) => mappingFunction(src, dest); + public void ConvertUsing(Func mappingFunction) => ConvertUsingCore((src, dest, ctxt) => mappingFunction(src, dest)); - tm.CustomMapFunction = expr; - }); - } + private void ConvertUsingCore(Expression> expr) => + SetTypeConverter(new LambdaTypeConverter(expr)); - public void ConvertUsing(Func mappingFunction) + private void SetTypeConverter(TypeConverter typeConverter) { HasTypeConverter = true; - TypeMapActions.Add(tm => - { - Expression> expr = - (src, dest, ctxt) => mappingFunction(src, dest, ctxt); - - tm.CustomMapFunction = expr; - }); + TypeMapActions.Add(tm => tm.TypeConverter = typeConverter); } + public void ConvertUsing(Func mappingFunction) => + ConvertUsingCore((src, dest, ctxt) => mappingFunction(src, dest, ctxt)); + public void ConvertUsing(ITypeConverter converter) => ConvertUsing(converter.Convert); - public void ConvertUsing() where TTypeConverter : ITypeConverter - { - HasTypeConverter = true; - TypeMapActions.Add(tm => tm.TypeConverterType = typeof(TTypeConverter)); - } + public void ConvertUsing() where TTypeConverter : ITypeConverter => + SetTypeConverter(new ClassTypeConverter(typeof(TTypeConverter), typeof(ITypeConverter))); public TMappingExpression ForCtorParam(string ctorParamName, Action> paramOptions) { @@ -522,19 +468,15 @@ public TMappingExpression IgnoreAllSourcePropertiesWithAnInaccessibleSetter() private static IEnumerable PropertiesWithAnInaccessibleSetter(Type type) => type.GetRuntimeProperties().Where(p => p.GetSetMethod() == null); - public void ConvertUsing(Expression> mappingFunction) - { - HasTypeConverter = true; - TypeMapActions.Add(tm => tm.CustomMapExpression = mappingFunction); - } - + public void ConvertUsing(Expression> mappingFunction) => SetTypeConverter(new ExpressionTypeConverter(mappingFunction)); + public TMappingExpression AsProxy() { if (!DestinationType.IsInterface) { throw new InvalidOperationException("Only interfaces can be proxied. " + DestinationType); } - TypeMapActions.Add(tm => tm.AsProxy = true); + TypeMapActions.Add(tm => tm.AsProxy()); return this as TMappingExpression; } } diff --git a/src/AutoMapper/Configuration/MemberConfigurationExpression.cs b/src/AutoMapper/Configuration/MemberConfigurationExpression.cs index 4f72361f6c..513b40ae4a 100644 --- a/src/AutoMapper/Configuration/MemberConfigurationExpression.cs +++ b/src/AutoMapper/Configuration/MemberConfigurationExpression.cs @@ -6,6 +6,7 @@ using System.Reflection; namespace AutoMapper.Configuration { + using Execution; using static AutoMapper.Execution.ExpressionBuilder; public interface IPropertyMapConfiguration { @@ -20,228 +21,75 @@ public class MemberConfigurationExpression : IMe private MemberInfo[] _sourceMembers; private readonly Type _sourceType; protected List> PropertyMapActions { get; } = new List>(); - public MemberConfigurationExpression(MemberInfo destinationMember, Type sourceType) { DestinationMember = destinationMember; _sourceType = sourceType; } - public MemberInfo DestinationMember { get; } - - public void MapAtRuntime() - { - PropertyMapActions.Add(pm => pm.Inline = false); - } - - public void NullSubstitute(object nullSubstitute) - { - PropertyMapActions.Add(pm => pm.NullSubstitute = nullSubstitute); - } - - public void MapFrom() - where TValueResolver : IValueResolver - { - var config = new ValueResolverConfiguration(typeof(TValueResolver), typeof(IValueResolver)); - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - + public void MapAtRuntime() => PropertyMapActions.Add(pm => pm.Inline = false); + public void NullSubstitute(object nullSubstitute) => PropertyMapActions.Add(pm => pm.NullSubstitute = nullSubstitute); + public void MapFrom() where TValueResolver : IValueResolver => + MapFromCore(new(typeof(TValueResolver), typeof(IValueResolver))); + protected void MapFromCore(ClassValueResolver config) => SetResolver(config); + protected void SetResolver(IValueResolver config) => PropertyMapActions.Add(pm => pm.Resolver = config); public void MapFrom(Expression> sourceMember) - where TValueResolver : IMemberValueResolver - { - var config = new ValueResolverConfiguration(typeof(TValueResolver), typeof(IMemberValueResolver)) - { - SourceMember = sourceMember - }; - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void MapFrom(string sourceMemberName) - where TValueResolver : IMemberValueResolver - { - var config = new ValueResolverConfiguration(typeof(TValueResolver), typeof(IMemberValueResolver)) - { - SourceMemberName = sourceMemberName - }; - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void MapFrom(IValueResolver valueResolver) - { - var config = new ValueResolverConfiguration(valueResolver, typeof(IValueResolver)); - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void MapFrom(IMemberValueResolver valueResolver, Expression> sourceMember) - { - var config = new ValueResolverConfiguration(valueResolver, typeof(IMemberValueResolver)) - { - SourceMember = sourceMember - }; - - PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); - } - - public void MapFrom(Func mappingFunction) - { - PropertyMapActions.Add(pm => - { - Expression> expr = (src, dest, destMember, ctxt) => mappingFunction(src, dest); - - pm.CustomMapFunction = expr; - }); - } - - public void MapFrom(Func mappingFunction) - { - PropertyMapActions.Add(pm => - { - Expression> expr = (src, dest, destMember, ctxt) => mappingFunction(src, dest, destMember); - - pm.CustomMapFunction = expr; + where TValueResolver : IMemberValueResolver => + MapFromCore(sourceMember); + public void MapFrom(string sourceMemberName) where TValueResolver : IMemberValueResolver => + MapFromCore(null, sourceMemberName); + private void MapFromCore(Expression> sourceMember, string sourceMemberName = null) where TValueResolver : IMemberValueResolver => + MapFromCore(new(typeof(TValueResolver), typeof(IMemberValueResolver)) + { + SourceMemberName = sourceMemberName, + SourceMemberLambda = sourceMember }); - } - - public void MapFrom(Func mappingFunction) - { - PropertyMapActions.Add(pm => + public void MapFrom(IValueResolver valueResolver) => + MapFromCore(new(valueResolver, typeof(IValueResolver))); + public void MapFrom(IMemberValueResolver valueResolver, Expression> sourceMember) => + MapFromCore(new(valueResolver, typeof(IMemberValueResolver)) { - Expression> expr = (src, dest, destMember, ctxt) => mappingFunction(src, dest, destMember, ctxt); - - pm.CustomMapFunction = expr; + SourceMemberLambda = sourceMember }); - } - - public void MapFrom(Expression> mapExpression) - { - MapFromUntyped(mapExpression); - } - + public void MapFrom(Func mappingFunction) => + MapFromResult((src, dest, destMember, ctxt) => mappingFunction(src, dest)); + public void MapFrom(Func mappingFunction) => + MapFromResult((src, dest, destMember, ctxt) => mappingFunction(src, dest, destMember)); + public void MapFrom(Func mappingFunction) => + MapFromResult((src, dest, destMember, ctxt) => mappingFunction(src, dest, destMember, ctxt)); + private void MapFromResult(Expression> expr) => + SetResolver(new FuncResolver(expr)); + public void MapFrom(Expression> mapExpression) => MapFromUntyped(mapExpression); internal void MapFromUntyped(LambdaExpression sourceExpression) { SourceExpression = sourceExpression; PropertyMapActions.Add(pm => pm.MapFrom(sourceExpression)); } - public void MapFrom(string sourceMembersPath) { _sourceMembers = ReflectionHelper.GetMemberPath(_sourceType, sourceMembersPath); PropertyMapActions.Add(pm => pm.MapFrom(sourceMembersPath)); } - - public void Condition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, srcMember, destMember, ctxt) => condition(src, dest, srcMember, destMember, ctxt); - - pm.Condition = expr; - }); - } - - public void Condition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, srcMember, destMember, ctxt) => condition(src, dest, srcMember, destMember); - - pm.Condition = expr; - }); - } - - public void Condition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, srcMember, destMember, ctxt) => condition(src, dest, srcMember); - - pm.Condition = expr; - }); - } - - public void Condition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, srcMember, destMember, ctxt) => condition(src, dest); - - pm.Condition = expr; - }); - } - - public void Condition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, srcMember, destMember, ctxt) => condition(src); - - pm.Condition = expr; - }); - } - - public void PreCondition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, ctxt) => condition(src); - - pm.PreCondition = expr; - }); - } - - public void PreCondition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, ctxt) => condition(ctxt); - - pm.PreCondition = expr; - }); - } - - public void PreCondition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, ctxt) => condition(src, ctxt); - - pm.PreCondition = expr; - }); - } - - public void PreCondition(Func condition) - { - PropertyMapActions.Add(pm => - { - Expression> expr = - (src, dest, ctxt) => condition(src, dest, ctxt); - - pm.PreCondition = expr; - }); - } - + public void Condition(Func condition) => + ConditionCore((src, dest, srcMember, destMember, ctxt) => condition(src, dest, srcMember, destMember, ctxt)); + public void Condition(Func condition) => + ConditionCore((src, dest, srcMember, destMember, ctxt) => condition(src, dest, srcMember, destMember)); + public void Condition(Func condition) => + ConditionCore((src, dest, srcMember, destMember, ctxt) => condition(src, dest, srcMember)); + public void Condition(Func condition) => ConditionCore((src, dest, srcMember, destMember, ctxt) => condition(src, dest)); + public void Condition(Func condition) => ConditionCore((src, dest, srcMember, destMember, ctxt) => condition(src)); + private void ConditionCore(Expression> expr) => + PropertyMapActions.Add(pm => pm.Condition = expr); + public void PreCondition(Func condition) => PreConditionCore((src, dest, ctxt) => condition(src)); + public void PreCondition(Func condition) => PreConditionCore((src, dest, ctxt) => condition(ctxt)); + public void PreCondition(Func condition) => PreConditionCore((src, dest, ctxt) => condition(src, ctxt)); + public void PreCondition(Func condition) => PreConditionCore((src, dest, ctxt) => condition(src, dest, ctxt)); + private void PreConditionCore(Expression> expr) => + PropertyMapActions.Add(pm => pm.PreCondition = expr); public void AddTransform(Expression> transformer) => PropertyMapActions.Add(pm => pm.AddValueTransformation(new ValueTransformerConfiguration(pm.DestinationType, transformer))); - - public void ExplicitExpansion() - { - PropertyMapActions.Add(pm => pm.ExplicitExpansion = true); - } - + public void ExplicitExpansion() => PropertyMapActions.Add(pm => pm.ExplicitExpansion = true); public void Ignore() => Ignore(ignorePaths: true); - public void Ignore(bool ignorePaths) => PropertyMapActions.Add(pm => { @@ -251,84 +99,47 @@ public void Ignore(bool ignorePaths) => pm.TypeMap.IgnorePaths(DestinationMember); } }); - public void AllowNull() => SetAllowNull(true); - public void DoNotAllowNull() => SetAllowNull(false); - private void SetAllowNull(bool value) => PropertyMapActions.Add(pm => pm.AllowNull = value); - public void UseDestinationValue() => SetUseDestinationValue(true); - private void SetUseDestinationValue(bool value) => PropertyMapActions.Add(pm => pm.UseDestinationValue = value); - - public void SetMappingOrder(int mappingOrder) - { - PropertyMapActions.Add(pm => pm.MappingOrder = mappingOrder); - } - - public void ConvertUsing() - where TValueConverter : IValueConverter - => PropertyMapActions.Add(pm => ConvertUsing(pm)); - - public void ConvertUsing(Expression> sourceMember) - where TValueConverter : IValueConverter - => PropertyMapActions.Add(pm => ConvertUsing(pm, sourceMember)); - - public void ConvertUsing(string sourceMemberName) - where TValueConverter : IValueConverter - => PropertyMapActions.Add(pm => ConvertUsing(pm, sourceMemberName: sourceMemberName)); - - public void ConvertUsing(IValueConverter valueConverter) - => PropertyMapActions.Add(pm => ConvertUsing(pm, valueConverter)); - - public void ConvertUsing(IValueConverter valueConverter, Expression> sourceMember) - => PropertyMapActions.Add(pm => ConvertUsing(pm, valueConverter, sourceMember)); - - public void ConvertUsing(IValueConverter valueConverter, string sourceMemberName) - => PropertyMapActions.Add(pm => ConvertUsing(pm, valueConverter, sourceMemberName: sourceMemberName)); - - private static void ConvertUsing(PropertyMap propertyMap, - Expression> sourceMember = null, - string sourceMemberName = null) - { - var config = new ValueResolverConfiguration(typeof(TValueConverter), - typeof(IValueConverter)) - { - SourceMember = sourceMember, + public void SetMappingOrder(int mappingOrder) => PropertyMapActions.Add(pm => pm.MappingOrder = mappingOrder); + public void ConvertUsing() where TValueConverter : IValueConverter => + ConvertUsingCore(); + public void ConvertUsing(Expression> sourceMember) where TValueConverter : IValueConverter => + ConvertUsingCore(sourceMember); + public void ConvertUsing(string sourceMemberName) where TValueConverter : IValueConverter => + ConvertUsingCore(null, sourceMemberName); + public void ConvertUsing(IValueConverter valueConverter) => ConvertUsingCore(valueConverter); + public void ConvertUsing(IValueConverter valueConverter, Expression> sourceMember) => + ConvertUsingCore(valueConverter, sourceMember); + public void ConvertUsing(IValueConverter valueConverter, string sourceMemberName) => + ConvertUsingCore(valueConverter, null, sourceMemberName); + private void ConvertUsingCore(Expression> sourceMember = null, string sourceMemberName = null) => + ConvertUsingCore(new(typeof(TValueConverter), typeof(IValueConverter)) + { + SourceMemberLambda = sourceMember, SourceMemberName = sourceMemberName - }; - - propertyMap.ValueConverterConfig = config; - } - - private static void ConvertUsing(PropertyMap propertyMap, IValueConverter valueConverter, - Expression> sourceMember = null, string sourceMemberName = null) - { - var config = new ValueResolverConfiguration(valueConverter, - typeof(IValueConverter)) + }); + protected void ConvertUsingCore(ValueConverter converter) => SetResolver(converter); + private void ConvertUsingCore(IValueConverter valueConverter, + Expression> sourceMember = null, string sourceMemberName = null) => + ConvertUsingCore(new(valueConverter, typeof(IValueConverter)) { - SourceMember = sourceMember, + SourceMemberLambda = sourceMember, SourceMemberName = sourceMemberName - }; - - propertyMap.ValueConverterConfig = config; - } - + }); public void Configure(TypeMap typeMap) { var destMember = DestinationMember; - if(destMember.DeclaringType.ContainsGenericParameters) { destMember = typeMap.DestinationSetters.Single(m => m.Name == destMember.Name); } - var propertyMap = typeMap.FindOrCreatePropertyMapFor(destMember, typeof(TMember) == typeof(object) ? destMember.GetMemberType() : typeof(TMember)); - Apply(propertyMap); } - private void Apply(PropertyMap propertyMap) { foreach(var action in PropertyMapActions) @@ -336,10 +147,8 @@ private void Apply(PropertyMap propertyMap) action(propertyMap); } } - public LambdaExpression SourceExpression { get; private set; } public LambdaExpression GetDestinationExpression() => DestinationMember.Lambda(); - public IPropertyMapConfiguration Reverse() { var destinationType = DestinationMember.DeclaringType; @@ -359,7 +168,6 @@ public IPropertyMapConfiguration Reverse() } return PathConfigurationExpression.Create(SourceExpression, GetDestinationExpression()); } - public void DoNotUseDestinationValue() => SetUseDestinationValue(false); } } \ No newline at end of file diff --git a/src/AutoMapper/Configuration/PathConfigurationExpression.cs b/src/AutoMapper/Configuration/PathConfigurationExpression.cs index 82c0b0f317..6185cd46c4 100644 --- a/src/AutoMapper/Configuration/PathConfigurationExpression.cs +++ b/src/AutoMapper/Configuration/PathConfigurationExpression.cs @@ -60,11 +60,7 @@ public PathConfigurationExpression(LambdaExpression destinationExpression, Stack public void MapFromUntyped(LambdaExpression sourceExpression) { _sourceExpression = sourceExpression ?? throw new ArgumentNullException(nameof(sourceExpression), $"{nameof(sourceExpression)} may not be null when mapping {DestinationMember.Name} from {typeof(TSource)} to {typeof(TDestination)}."); - PathMapActions.Add(pm => - { - pm.CustomMapExpression = sourceExpression; - pm.Ignored = false; - }); + PathMapActions.Add(pm => pm.MapFrom(sourceExpression)); } public void Configure(TypeMap typeMap) { diff --git a/src/AutoMapper/ConstructorMap.cs b/src/AutoMapper/ConstructorMap.cs index 3273f2118d..946bae36e1 100644 --- a/src/AutoMapper/ConstructorMap.cs +++ b/src/AutoMapper/ConstructorMap.cs @@ -12,13 +12,13 @@ public class ConstructorMap { private bool? _canResolve; private readonly Dictionary _ctorParams = new(StringComparer.OrdinalIgnoreCase); - public ConstructorInfo Ctor { get; } - public TypeMap TypeMap { get; } + public ConstructorInfo Ctor { get; private set; } public IReadOnlyCollection CtorParams => _ctorParams.Values; - public ConstructorMap(ConstructorInfo ctor, TypeMap typeMap) + public void Reset(ConstructorInfo ctor) { Ctor = ctor; - TypeMap = typeMap; + _ctorParams.Clear(); + _canResolve = null; } public bool CanResolve { @@ -37,13 +37,13 @@ private bool ParametersCanResolve() return true; } public ConstructorParameterMap this[string name] => _ctorParams.GetValueOrDefault(name); - public void AddParameter(ParameterInfo parameter, IEnumerable sourceMembers, bool canResolve) + public void AddParameter(ParameterInfo parameter, IEnumerable sourceMembers, TypeMap typeMap) { if (parameter.Name == null) { return; } - _ctorParams.Add(parameter.Name, new ConstructorParameterMap(TypeMap, parameter, sourceMembers.ToArray(), canResolve)); + _ctorParams.Add(parameter.Name, new ConstructorParameterMap(typeMap, parameter, sourceMembers.ToArray())); } public bool ApplyIncludedMember(IncludedMember includedMember) { @@ -74,32 +74,29 @@ public bool ApplyIncludedMember(IncludedMember includedMember) [EditorBrowsable(EditorBrowsableState.Never)] public class ConstructorParameterMap : MemberMap { - private readonly MemberInfo[] _sourceMembers; private Type _sourceType; - public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberInfo[] sourceMembers, bool canResolveValue) : base(typeMap) + public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberInfo[] sourceMembers) : base(typeMap) { Parameter = parameter; - _sourceMembers = sourceMembers; - CanResolveValue = canResolveValue; + if (sourceMembers.Length > 0) + { + MapByConvention(sourceMembers); + } + else + { + SourceMembers = Array.Empty(); + } } public ConstructorParameterMap(ConstructorParameterMap parameterMap, IncludedMember includedMember) : - this(includedMember.TypeMap, parameterMap.Parameter, parameterMap._sourceMembers, parameterMap.CanResolveValue) => + this(includedMember.TypeMap, parameterMap.Parameter, parameterMap.SourceMembers) => IncludedMember = includedMember.Chain(parameterMap.IncludedMember); public ParameterInfo Parameter { get; } - public override Type SourceType - { - get => _sourceType ??= - CustomMapExpression?.ReturnType ?? - CustomMapFunction?.ReturnType ?? - (_sourceMembers.Length > 0 ? _sourceMembers[_sourceMembers.Length - 1].GetMemberType() : Parameter.ParameterType); - protected set => _sourceType = value; - } + public override Type SourceType => _sourceType ??= GetSourceType(); public override Type DestinationType => Parameter.ParameterType; - public override MemberInfo[] SourceMembers => _sourceMembers; + public override IncludedMember IncludedMember { get; } + public override MemberInfo[] SourceMembers { get; set; } public override string DestinationName => Parameter.Name; - public override LambdaExpression CustomMapFunction { get; set; } - public override bool CanResolveValue { get; set; } - public Expression DefaultValue() => Parameter.GetDefaultValue(); + public Expression DefaultValue() => Parameter.IsOptional ? Parameter.GetDefaultValue() : Expression.Default(DestinationType); public override string ToString() => Parameter.Member.DeclaringType + "." + Parameter.Member + ".parameter " + Parameter.Name; } } \ No newline at end of file diff --git a/src/AutoMapper/Execution/ExpressionBuilder.cs b/src/AutoMapper/Execution/ExpressionBuilder.cs index 29910d79c8..e5a91285ff 100644 --- a/src/AutoMapper/Execution/ExpressionBuilder.cs +++ b/src/AutoMapper/Execution/ExpressionBuilder.cs @@ -26,11 +26,12 @@ public static class ExpressionBuilder public static readonly MethodInfo IListAdd = typeof(IList).GetMethod(nameof(IList.Add)); public static readonly MethodInfo IncTypeDepthInfo = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.IncrementTypeDepth)); public static readonly MethodInfo DecTypeDepthInfo = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.DecrementTypeDepth)); - public static readonly MethodInfo ContextCreate = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.CreateInstance)); + private static readonly MethodInfo ContextCreate = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.CreateInstance)); public static readonly MethodInfo OverTypeDepthMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.OverTypeDepth)); public static readonly MethodInfo CacheDestinationMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.CacheDestination)); public static readonly MethodInfo GetDestinationMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.GetDestination)); - private static readonly MethodInfo CheckContextMethod = typeof(ResolutionContext).GetStaticMethod(nameof(ResolutionContext.CheckContext)); + private static readonly MethodCallExpression CheckContextCall = Expression.Call( + typeof(ResolutionContext).GetStaticMethod(nameof(ResolutionContext.CheckContext)), ContextParameter); private static readonly MethodInfo ContextMapMethod = typeof(ResolutionContext).GetInstanceMethod(nameof(ResolutionContext.MapInternal)); private static readonly MethodInfo ArrayEmptyMethod = typeof(Array).GetStaticMethod(nameof(Array.Empty)); private static readonly ParameterExpression Disposable = Variable(typeof(IDisposable), "disposableEnumerator"); @@ -133,6 +134,7 @@ Expression DefaultDestination() return ObjectFactory.GenerateConstructorExpression(destinationType); } } + public static Expression ServiceLocator(Type type) => Expression.Call(ContextParameter, ContextCreate, Constant(type)); public static Expression ContextMap(TypePair typePair, Expression sourceParameter, Expression destinationParameter, MemberMap memberMap) { var mapMethod = ContextMapMethod.MakeGenericMethod(typePair.SourceType, typePair.DestinationType); @@ -142,7 +144,7 @@ public static Expression CheckContext(TypeMap typeMap) { if (typeMap.MaxDepth > 0 || typeMap.PreserveReferences) { - return Expression.Call(CheckContextMethod, ContextParameter); + return CheckContextCall; } return null; } diff --git a/src/AutoMapper/Execution/ObjectFactory.cs b/src/AutoMapper/Execution/ObjectFactory.cs index fd933f9e82..b4c49443e6 100644 --- a/src/AutoMapper/Execution/ObjectFactory.cs +++ b/src/AutoMapper/Execution/ObjectFactory.cs @@ -12,7 +12,7 @@ namespace AutoMapper.Execution [EditorBrowsable(EditorBrowsableState.Never)] public static class ObjectFactory { - private static readonly LockingConcurrentDictionary> CtorCache = new LockingConcurrentDictionary>(GenerateConstructor); + private static readonly LockingConcurrentDictionary> CtorCache = new(GenerateConstructor); public static object CreateInstance(Type type) => CtorCache.GetOrAdd(type)(); private static Func GenerateConstructor(Type type) => Lambda>(GenerateConstructorExpression(type).ToObject()).Compile(); @@ -32,16 +32,14 @@ private static Expression CallConstructor(Type type) { return New(defaultCtor); } - //find a ctor with only optional args - var ctorWithOptionalArgs = type.GetDeclaredConstructors().FirstOrDefault(c => c.GetParameters().All(p => p.IsOptional)); - if (ctorWithOptionalArgs == null) + var ctorWithOptionalArgs = + (from ctor in type.GetDeclaredConstructors() let args = ctor.GetParameters() where args.All(p => p.IsOptional) select (ctor, args)).FirstOrDefault(); + if (ctorWithOptionalArgs.args == null) { return InvalidType(type, $"{type} needs to have a constructor with 0 args or only optional args. Validate your configuration for details."); } - //get all optional default values - var args = ctorWithOptionalArgs.GetParameters().Select(p=>ToType(p.GetDefaultValue(), p.ParameterType)); - //create the ctor expression - return New(ctorWithOptionalArgs, args); + var arguments = ctorWithOptionalArgs.args.Select(p => p.GetDefaultValue()); + return New(ctorWithOptionalArgs.ctor, arguments); } private static Expression CreateInterfaceExpression(Type type) => type.IsGenericType(typeof(IDictionary<,>)) ? CreateCollection(type, typeof(Dictionary<,>)) : diff --git a/src/AutoMapper/Execution/ProxyGenerator.cs b/src/AutoMapper/Execution/ProxyGenerator.cs index ceb6b3758c..140440052b 100644 --- a/src/AutoMapper/Execution/ProxyGenerator.cs +++ b/src/AutoMapper/Execution/ProxyGenerator.cs @@ -16,7 +16,7 @@ public static class ProxyGenerator private static readonly EventInfo PropertyChanged = typeof(INotifyPropertyChanged).GetEvent(nameof(INotifyPropertyChanged.PropertyChanged)); private static readonly ConstructorInfo ProxyBaseCtor = typeof(ProxyBase).GetConstructor(Type.EmptyTypes); private static readonly ModuleBuilder ProxyModule = CreateProxyModule(); - private static readonly LockingConcurrentDictionary ProxyTypes = new LockingConcurrentDictionary(EmitProxy); + private static readonly LockingConcurrentDictionary ProxyTypes = new(EmitProxy); private static ModuleBuilder CreateProxyModule() { var assemblyName = typeof(Mapper).Assembly.GetName(); diff --git a/src/AutoMapper/Execution/TypeMapPlanBuilder.cs b/src/AutoMapper/Execution/TypeMapPlanBuilder.cs index 0342540dd8..c2efa3a7c0 100644 --- a/src/AutoMapper/Execution/TypeMapPlanBuilder.cs +++ b/src/AutoMapper/Execution/TypeMapPlanBuilder.cs @@ -9,9 +9,10 @@ namespace AutoMapper.Execution using static Expression; using static ExpressionBuilder; using Internal; + using AutoMapper.Configuration; + public class TypeMapPlanBuilder { - private static readonly MethodInfo CreateProxyMethod = typeof(ObjectFactory).GetStaticMethod(nameof(ObjectFactory.CreateInterfaceProxy)); private static readonly MethodInfo MappingError = typeof(TypeMapPlanBuilder).GetStaticMethod(nameof(MemberMappingError)); private readonly IGlobalConfiguration _configurationProvider; private readonly ParameterExpression _destination; @@ -24,7 +25,7 @@ public TypeMapPlanBuilder(IGlobalConfiguration configurationProvider, TypeMap ty _configurationProvider = configurationProvider; _typeMap = typeMap; Source = Parameter(typeMap.SourceType, "source"); - _initialDestination = Parameter(typeMap.DestinationTypeToUse, "destination"); + _initialDestination = Parameter(typeMap.DestinationType, "destination"); _destination = Variable(_initialDestination.Type, "typeMapDestination"); } public Type DestinationType => _destination.Type; @@ -33,7 +34,7 @@ public TypeMapPlanBuilder(IGlobalConfiguration configurationProvider, TypeMap ty public LambdaExpression CreateMapperLambda(HashSet typeMapsPath) { var parameters = new[] { Source, _initialDestination, ContextParameter }; - var customExpression = TypeConverter(parameters) ?? (_typeMap.CustomMapFunction ?? _typeMap.CustomMapExpression)?.ConvertReplaceParameters(parameters); + var customExpression = _typeMap.TypeConverter?.GetExpression(parameters); if (customExpression != null) { return Lambda(customExpression, parameters); @@ -48,7 +49,7 @@ public LambdaExpression CreateMapperLambda(HashSet typeMapsPath) { variables.AddRange(_typeMap.IncludedMembersTypeMaps.Select(i => i.Variable)); statements.AddRange(variables.Zip(_typeMap.IncludedMembersTypeMaps, (v, i) => - Assign(v, i.MemberExpression.ReplaceParameters(Source).NullCheck()))); + Assign(v, i.MemberExpression.ReplaceParameters(parameters).NullCheck()))); } var createDestinationFunc = CreateDestinationFunc(); var assignmentFunc = CreateAssignmentFunc(createDestinationFunc); @@ -61,16 +62,6 @@ public LambdaExpression CreateMapperLambda(HashSet typeMapsPath) statements.Add(mapperFunc); variables.Add(_destination); return Lambda(Block(variables, statements), parameters); - Expression TypeConverter(ParameterExpression[] parameters) - { - if (_typeMap.TypeConverterType == null) - { - return null; - } - var converterInterfaceType = typeof(ITypeConverter<,>).MakeGenericType(_typeMap.SourceType, DestinationType); - var converter = ServiceLocator(_typeMap.TypeConverterType); - return Call(ToType(converter, converterInterfaceType), "Convert", parameters); - } static void Clear(ref HashSet typeMapsPath) { if (typeMapsPath == null) @@ -170,7 +161,8 @@ private Expression CreateAssignmentFunc(Expression createDestination) { var actions = new List { createDestination }; Expression typeMapExpression = null; - if (_typeMap.MaxDepth > 0) + var hasMaxDepth = _typeMap.MaxDepth > 0; + if (hasMaxDepth) { typeMapExpression = Constant(_typeMap); actions.Add(Call(ContextParameter, IncTypeDepthInfo, typeMapExpression)); @@ -179,7 +171,7 @@ private Expression CreateAssignmentFunc(Expression createDestination) { actions.Add(beforeMapAction.ReplaceParameters(Source, _destination, ContextParameter)); } - foreach (var propertyMap in _typeMap.PropertyMaps) + foreach (var propertyMap in _typeMap.OrderedPropertyMaps()) { if (propertyMap.CanResolveValue) { @@ -202,7 +194,7 @@ private Expression CreateAssignmentFunc(Expression createDestination) { actions.Add(afterMapAction.ReplaceParameters(Source, _destination, ContextParameter)); } - if (_typeMap.MaxDepth > 0) + if (hasMaxDepth) { actions.Add(Call(ContextParameter, DecTypeDepthInfo, typeMapExpression)); } @@ -260,12 +252,9 @@ private Expression CheckReferencesCache(Expression valueBuilder) } private Expression CreateNewDestinationFunc() => _typeMap switch { - { CustomCtorExpression: LambdaExpression constructUsing } => constructUsing.ReplaceParameters(Source), { CustomCtorFunction: LambdaExpression constructUsingFunc } => constructUsingFunc.ReplaceParameters(Source, ContextParameter), { ConstructorMap: { CanResolve: true } constructorMap } => ConstructorMapping(constructorMap), - { DestinationTypeToUse: { IsInterface: true } interfaceType } => _typeMap.AsProxy ? - Call(CreateProxyMethod, Constant(interfaceType)) : Throw(Constant(new AutoMapperMappingException("Cannot create interface "+interfaceType, null, _typeMap)), interfaceType), - { ConstructDestinationUsingServiceLocator: true } => ServiceLocator(DestinationType), + { DestinationType: { IsInterface: true } interfaceType } => Throw(Constant(new AutoMapperMappingException("Cannot create interface "+interfaceType, null, _typeMap)), interfaceType), _ => ObjectFactory.GenerateConstructorExpression(DestinationType) }; private Expression ConstructorMapping(ConstructorMap constructorMap) @@ -278,8 +267,9 @@ private Expression ConstructorMapping(ConstructorMap constructorMap) } private Expression CreateConstructorParameterExpression(ConstructorParameterMap ctorParamMap) { - var defaultValue = ctorParamMap.Parameter.IsOptional ? ctorParamMap.DefaultValue() : Default(ctorParamMap.DestinationType); - var resolvedExpression = BuildValueResolverFunc(ctorParamMap, defaultValue); + var defaultValue = ctorParamMap.DefaultValue(); + var customSource = GetCustomSource(ctorParamMap); + var resolvedExpression = BuildValueResolverFunc(ctorParamMap, customSource, defaultValue); var resolvedValue = Variable(resolvedExpression.Type, "resolvedValue"); var tryMap = Block(new[] { resolvedValue }, Assign(resolvedValue, resolvedExpression), @@ -313,7 +303,8 @@ private Expression CreatePropertyMapFunc(MemberMap memberMap, Expression destina destinationMemberReadOnly = destinationField.IsInitOnly; destinationMemberGetter = destinationMemberAccess; } - var valueResolver = BuildValueResolverFunc(memberMap, destinationMemberGetter); + var customSource = GetCustomSource(memberMap); + var valueResolver = BuildValueResolverFunc(memberMap, customSource, destinationMemberGetter); var resolvedValueVariable = Variable(valueResolver.Type, "resolvedValue"); var destinationMemberValue = DestinationMemberValue(memberMap, destinationMemberGetter, destinationMemberReadOnly); var mappedMember = MapMember(memberMap, destinationMemberValue, resolvedValueVariable); @@ -322,7 +313,7 @@ private Expression CreatePropertyMapFunc(MemberMap memberMap, Expression destina if (memberMap.Condition != null) { _propertyMapExpressions.Add(IfThen( - memberMap.Condition.ConvertReplaceParameters(GetCustomSource(memberMap), _destination, mappedMemberVariable, destinationMemberGetter, ContextParameter), + memberMap.Condition.ConvertReplaceParameters(customSource, _destination, mappedMemberVariable, destinationMemberGetter, ContextParameter), mapperExpr)); } else if (!destinationMemberReadOnly) @@ -331,7 +322,7 @@ private Expression CreatePropertyMapFunc(MemberMap memberMap, Expression destina } if (memberMap.PreCondition != null) { - Precondition(memberMap); + Precondition(memberMap, customSource); } return Block(_propertyMapVariables, _propertyMapExpressions); Expression DestinationMemberValue(MemberMap memberMap, Expression destinationMemberGetter, bool destinationMemberReadOnly) @@ -349,9 +340,9 @@ Expression DestinationMemberValue(MemberMap memberMap, Expression destinationMem return Condition(ReferenceEqual(_initialDestination, Null), Default(memberMap.DestinationType), destinationMemberGetter); } } - void Precondition(MemberMap memberMap) + void Precondition(MemberMap memberMap, Expression customSource) { - var preCondition = memberMap.PreCondition.ConvertReplaceParameters(GetCustomSource(memberMap), _destination, ContextParameter); + var preCondition = memberMap.PreCondition.ConvertReplaceParameters(customSource, _destination, ContextParameter); var ifThen = IfThen(preCondition, Block(_propertyMapExpressions)); _propertyMapExpressions.Clear(); _propertyMapExpressions.Add(ifThen); @@ -382,27 +373,16 @@ Expression MapMember(MemberMap memberMap, Expression destinationMemberValue, Par var mapMember = memberMap.Inline ? _configurationProvider.MapExpression(_typeMap.Profile, typePair, resolvedValue, memberMap, destinationMemberValue) : ContextMap(typePair, resolvedValue, destinationMemberValue, memberMap); - mapMember = memberMap.ApplyTransformers(mapMember); - return mapMember; + return memberMap.ApplyTransformers(mapMember); } - private Expression BuildValueResolverFunc(MemberMap memberMap, Expression destValueExpr) + private Expression BuildValueResolverFunc(MemberMap memberMap, Expression customSource, Expression destValueExpr) { - var customSource = GetCustomSource(memberMap); - var destinationPropertyType = memberMap.DestinationType; - var valueResolverFunc = memberMap switch - { - { ValueConverterConfig: { } } => ToType(BuildConvertCall(memberMap, customSource, destValueExpr), destinationPropertyType), - { ValueResolverConfig: { } } => BuildResolveCall(memberMap, customSource, destValueExpr), - { CustomMapFunction: LambdaExpression function } => function.ConvertReplaceParameters(customSource, _destination, destValueExpr, ContextParameter), - { CustomMapExpression: LambdaExpression mapFrom } => CustomMapExpression(mapFrom.ReplaceParameters(customSource), destinationPropertyType, destValueExpr), - { SourceMembers.Length: > 0 } => memberMap.ChainSourceMembers(customSource, destinationPropertyType, destValueExpr), - _ => destValueExpr - }; + var valueResolverFunc = memberMap.Resolver?.GetExpression(memberMap, customSource, _destination, destValueExpr) ?? destValueExpr; if (memberMap.NullSubstitute != null) { valueResolverFunc = memberMap.NullSubstitute(valueResolverFunc); } - else if (!memberMap.AllowsNullDestinationValues()) + else if (!memberMap.AllowsNullDestinationValues) { var toCreate = memberMap.SourceType; if (!toCreate.IsAbstract && toCreate.IsClass && !toCreate.IsArray) @@ -411,59 +391,146 @@ private Expression BuildValueResolverFunc(MemberMap memberMap, Expression destVa } } return valueResolverFunc; - static Expression CustomMapExpression(Expression mapFrom, Type destinationPropertyType, Expression destValueExpr) + } + private Expression GetCustomSource(MemberMap memberMap) => memberMap.IncludedMember?.Variable ?? Source; + } + public interface IValueResolver + { + Expression GetExpression(MemberMap memberMap, Expression source, Expression destination, Expression destinationMember); + MemberInfo GetSourceMember(MemberMap memberMap); + Type ResolvedType { get; } + string SourceMemberName => null; + LambdaExpression ProjectToExpression => null; + } + public abstract class LambdaValueResolver + { + public LambdaExpression Lambda { get; } + public Type ResolvedType => Lambda.ReturnType; + protected LambdaValueResolver(LambdaExpression lambda) => Lambda = lambda; + } + public class FuncResolver : LambdaValueResolver, IValueResolver + { + public FuncResolver(LambdaExpression lambda) : base(lambda) { } + public Expression GetExpression(MemberMap memberMap, Expression source, Expression destination, Expression destinationMember) => + Lambda.ConvertReplaceParameters(source, destination, destinationMember, ContextParameter); + public MemberInfo GetSourceMember(MemberMap _) => null; + } + public class ExpressionResolver : LambdaValueResolver, IValueResolver + { + public ExpressionResolver(LambdaExpression lambda) : base(lambda) { } + public Expression GetExpression(MemberMap memberMap, Expression source, Expression _, Expression destinationMember) + { + var mapFrom = Lambda.ReplaceParameters(source); + var nullCheckedExpression = mapFrom.NullCheck(memberMap.DestinationType, destinationMember); + if (nullCheckedExpression != mapFrom) { - var nullCheckedExpression = mapFrom.NullCheck(destinationPropertyType, destValueExpr); - if (nullCheckedExpression != mapFrom) - { - return nullCheckedExpression; - } - var defaultExpression = Default(mapFrom.Type); - return TryCatch(mapFrom, Catch(typeof(NullReferenceException), defaultExpression), Catch(typeof(ArgumentNullException), defaultExpression)); + return nullCheckedExpression; } + var defaultExpression = Default(mapFrom.Type); + return TryCatch(mapFrom, Catch(typeof(NullReferenceException), defaultExpression), Catch(typeof(ArgumentNullException), defaultExpression)); } - private Expression GetCustomSource(MemberMap memberMap) => memberMap.IncludedMember?.Variable ?? Source; - private static Expression ServiceLocator(Type type) => Call(ContextParameter, ContextCreate, Constant(type)); - private Expression BuildResolveCall(MemberMap memberMap, Expression source, Expression destValueExpr) + public MemberInfo GetSourceMember(MemberMap _) => Lambda.GetMember(); + public LambdaExpression ProjectToExpression => Lambda; + } + public abstract class ValueResolverConfig + { + private protected Expression _instance; + public Type ConcreteType { get; } + public Type InterfaceType { get; protected set; } + public LambdaExpression SourceMemberLambda { get; set; } + protected ValueResolverConfig(Type concreteType, Type interfaceType) + { + ConcreteType = concreteType; + InterfaceType = interfaceType; + } + protected ValueResolverConfig(object instance, Type interfaceType) + { + _instance = Constant(instance); + InterfaceType = interfaceType; + } + public string SourceMemberName { get; set; } + public Type ResolvedType => InterfaceType.GenericTypeArguments[^1]; + } + public class ValueConverter : ValueResolverConfig, IValueResolver + { + public ValueConverter(Type concreteType, Type interfaceType) : base(concreteType, interfaceType) => _instance = ServiceLocator(concreteType); + public ValueConverter(object instance, Type interfaceType) : base(instance, interfaceType) { } + public Expression GetExpression(MemberMap memberMap, Expression source, Expression _, Expression destinationMember) + { + var iResolverTypeArgs = InterfaceType.GenericTypeArguments; + var sourceMember = SourceMemberLambda?.ReplaceParameters(source) ?? + (SourceMemberName != null ? + PropertyOrField(source, SourceMemberName) : + memberMap.ChainSourceMembers(source, iResolverTypeArgs[1], destinationMember) ?? Throw(Constant(BuildExceptionMessage()), iResolverTypeArgs[0])); + return Call(ToType(_instance, InterfaceType), "Convert", ToType(sourceMember, iResolverTypeArgs[0]), ContextParameter); + AutoMapperConfigurationException BuildExceptionMessage() + => new($"Cannot find a source member to pass to the value converter of type {ConcreteType}. Configure a source member to map from."); + } + public MemberInfo GetSourceMember(MemberMap memberMap) => this switch + { + { SourceMemberLambda: { } lambda } => lambda.GetMember(), + { SourceMemberName: { } } => null, + _ => memberMap.SourceMembers.Length == 1 ? memberMap.SourceMembers[0] : null + }; + } + public class ClassValueResolver : ValueResolverConfig, IValueResolver + { + public ClassValueResolver(Type concreteType, Type interfaceType) : base(concreteType, interfaceType) { } + public ClassValueResolver(object instance, Type interfaceType) : base(instance, interfaceType) { } + public Expression GetExpression(MemberMap memberMap, Expression source, Expression destination, Expression destinationMember) { var typeMap = memberMap.TypeMap; - var valueResolverConfig = memberMap.ValueResolverConfig; - var resolverInstance = valueResolverConfig.Instance != null ? - Constant(valueResolverConfig.Instance) : - ServiceLocator(typeMap.MakeGenericType(valueResolverConfig.ConcreteType)); - var sourceMember = valueResolverConfig.SourceMember?.ReplaceParameters(source) ?? - (valueResolverConfig.SourceMemberName != null ? PropertyOrField(source, valueResolverConfig.SourceMemberName) : null); - var iResolverType = valueResolverConfig.InterfaceType; - if (iResolverType.ContainsGenericParameters) + var resolverInstance = _instance ?? ServiceLocator(typeMap.MakeGenericType(ConcreteType)); + var sourceMember = SourceMemberLambda?.ReplaceParameters(source) ?? (SourceMemberName != null ? PropertyOrField(source, SourceMemberName) : null); + if (InterfaceType.ContainsGenericParameters) { var typeArgs = - iResolverType.GenericTypeArguments.Zip(new[] { typeMap.SourceType, typeMap.DestinationType, sourceMember?.Type, destValueExpr.Type }.Where(t => t != null), + InterfaceType.GenericTypeArguments.Zip(new[] { typeMap.SourceType, typeMap.DestinationType, sourceMember?.Type, destinationMember.Type }.Where(t => t != null), (declaredType, runtimeType) => declaredType.ContainsGenericParameters ? runtimeType : declaredType).ToArray(); - iResolverType = iResolverType.GetGenericTypeDefinition().MakeGenericType(typeArgs); + InterfaceType = InterfaceType.GetGenericTypeDefinition().MakeGenericType(typeArgs); } - var parameters = new[] { source, _destination, sourceMember, destValueExpr }.Where(p => p != null) - .Zip(iResolverType.GenericTypeArguments, ToType) + var parameters = new[] { source, destination, sourceMember, destinationMember }.Where(p => p != null) + .Zip(InterfaceType.GenericTypeArguments, ToType) .Append(ContextParameter) .ToArray(); - return Call(ToType(resolverInstance, iResolverType), "Resolve", parameters); + return Call(ToType(resolverInstance, InterfaceType), "Resolve", parameters); + } + public MemberInfo GetSourceMember(MemberMap _) => SourceMemberLambda?.GetMember(); + } + public abstract class TypeConverter + { + public abstract Expression GetExpression(ParameterExpression[] parameters); + public virtual void CloseGenerics(ITypeMapConfiguration openMapConfig, TypePair closedTypes) { } + public virtual LambdaExpression ProjectToExpression => null; + } + public class LambdaTypeConverter : TypeConverter + { + public LambdaTypeConverter(LambdaExpression lambda) => Lambda = lambda; + public LambdaExpression Lambda { get; } + public override Expression GetExpression(ParameterExpression[] parameters) => Lambda.ConvertReplaceParameters(parameters); + } + public class ExpressionTypeConverter : LambdaTypeConverter + { + public ExpressionTypeConverter(LambdaExpression lambda) : base(lambda){} + public override LambdaExpression ProjectToExpression => Lambda; + } + public class ClassTypeConverter : TypeConverter + { + public ClassTypeConverter(Type converterType, Type converterInterface) + { + ConverterType = converterType; + ConverterInterface = converterInterface; } - private Expression BuildConvertCall(MemberMap memberMap, Expression source, Expression destValueExpr) + public Type ConverterType { get; private set; } + public Type ConverterInterface { get; } + public override Expression GetExpression(ParameterExpression[] parameters) => + Call(ToType(ServiceLocator(ConverterType), ConverterInterface), "Convert", parameters); + public override void CloseGenerics(ITypeMapConfiguration openMapConfig, TypePair closedTypes) { - var valueConverterConfig = memberMap.ValueConverterConfig; - var iResolverType = valueConverterConfig.InterfaceType; - var iResolverTypeArgs = iResolverType.GenericTypeArguments; - var resolverInstance = valueConverterConfig.Instance != null ? - Constant(valueConverterConfig.Instance) : - ServiceLocator(valueConverterConfig.ConcreteType); - var sourceMember = valueConverterConfig.SourceMember?.ReplaceParameters(source) ?? - (valueConverterConfig.SourceMemberName != null ? - PropertyOrField(source, valueConverterConfig.SourceMemberName) : - memberMap.SourceMembers.Length > 0 ? - memberMap.ChainSourceMembers(source, iResolverTypeArgs[1], destValueExpr) : - Throw(Constant(BuildExceptionMessage()), iResolverTypeArgs[0])); - return Call(ToType(resolverInstance, iResolverType), "Convert", ToType(sourceMember, iResolverTypeArgs[0]), ContextParameter); - AutoMapperConfigurationException BuildExceptionMessage() - => new AutoMapperConfigurationException($"Cannot find a source member to pass to the value converter of type {valueConverterConfig.ConcreteType.FullName}. Configure a source member to map from."); + var typeParams = (openMapConfig.SourceType.IsGenericTypeDefinition ? closedTypes.SourceType.GenericTypeArguments : Type.EmptyTypes) + .Concat(openMapConfig.DestinationType.IsGenericTypeDefinition ? closedTypes.DestinationType.GenericTypeArguments : Type.EmptyTypes); + var neededParameters = ConverterType.GenericParametersCount(); + ConverterType = ConverterType.MakeGenericType(typeParams.Take(neededParameters).ToArray()); } } } \ No newline at end of file diff --git a/src/AutoMapper/Internal/MemberPath.cs b/src/AutoMapper/Internal/MemberPath.cs index c2365aca8e..7ba4d87e10 100644 --- a/src/AutoMapper/Internal/MemberPath.cs +++ b/src/AutoMapper/Internal/MemberPath.cs @@ -17,7 +17,7 @@ public MemberPath(Stack members) : this(members.ToMemberInfos()){} public MemberPath(MemberInfo[] members) => Members = members; - public MemberInfo Last => Members[Members.Length - 1]; + public MemberInfo Last => Members[^1]; public MemberInfo First => Members[0]; diff --git a/src/AutoMapper/Internal/PrimitiveHelper.cs b/src/AutoMapper/Internal/PrimitiveHelper.cs index 8e9303b0a6..e6f030444c 100644 --- a/src/AutoMapper/Internal/PrimitiveHelper.cs +++ b/src/AutoMapper/Internal/PrimitiveHelper.cs @@ -11,18 +11,22 @@ public static class PrimitiveHelper public static IReadOnlyCollection NullCheck(this IReadOnlyCollection source) => source ?? Array.Empty(); public static IEnumerable Concat(this IReadOnlyCollection collection, IReadOnlyCollection otherCollection) { - otherCollection ??= Array.Empty(); + if (otherCollection == null || otherCollection.Count == 0) + { + return collection; + } if (collection.Count == 0) { return otherCollection; } - return otherCollection.Count == 0 ? collection : Enumerable.Concat(collection, otherCollection); + return Enumerable.Concat(collection, otherCollection); } public static void CheckIsDerivedFrom(this TypePair types, TypePair baseTypes) { types.SourceType.CheckIsDerivedFrom(baseTypes.SourceType); types.DestinationType.CheckIsDerivedFrom(baseTypes.DestinationType); } + public static bool IsCollection(this TypePair context) => context.SourceType.IsCollection() && context.DestinationType.IsCollection(); public static bool IsEnumToEnum(this TypePair context) => context.SourceType.IsEnum && context.DestinationType.IsEnum; public static bool IsUnderlyingTypeToEnum(this TypePair context) => context.DestinationType.IsEnum && context.SourceType.IsAssignableFrom(Enum.GetUnderlyingType(context.DestinationType)); diff --git a/src/AutoMapper/Internal/TypePair.cs b/src/AutoMapper/Internal/TypePair.cs index e8768d5676..75ad184cc9 100644 --- a/src/AutoMapper/Internal/TypePair.cs +++ b/src/AutoMapper/Internal/TypePair.cs @@ -53,6 +53,7 @@ public TypePair CloseGenericTypes(TypePair closedTypes) var closedDestinationType = DestinationType.IsGenericTypeDefinition ? DestinationType.MakeGenericType(destinationArguments) : DestinationType; return new TypePair(closedSourceType, closedDestinationType); } + public Type ITypeConverter() => ContainsGenericParameters ? null : typeof(ITypeConverter<,>).MakeGenericType(SourceType, DestinationType); public TypePair GetTypeDefinitionIfGeneric() => new TypePair(GetTypeDefinitionIfGeneric(SourceType), GetTypeDefinitionIfGeneric(DestinationType)); private static Type GetTypeDefinitionIfGeneric(Type type) => type.IsGenericType ? type.GetGenericTypeDefinition() : type; public static bool operator ==(TypePair left, TypePair right) => left.Equals(right); diff --git a/src/AutoMapper/Mapper.cs b/src/AutoMapper/Mapper.cs index 4012559bed..928fa747a5 100644 --- a/src/AutoMapper/Mapper.cs +++ b/src/AutoMapper/Mapper.cs @@ -6,6 +6,7 @@ namespace AutoMapper { using QueryableExtensions; using IObjectMappingOperationOptions = IMappingOperationOptions; + using Factory = Func; using Internal; public interface IMapperBase { @@ -140,18 +141,22 @@ internal interface IInternalRuntimeMapper : IRuntimeMapper { TDestination Map(TSource source, TDestination destination, ResolutionContext context, Type sourceType = null, Type destinationType = null, MemberMap memberMap = null); ResolutionContext DefaultContext { get; } + Factory ServiceCtor { get; } } public class Mapper : IMapper, IInternalRuntimeMapper { private readonly IGlobalConfiguration _configurationProvider; + private readonly Factory _serviceCtor; public Mapper(IConfigurationProvider configurationProvider) : this(configurationProvider, configurationProvider.Internal().ServiceCtor) { } - public Mapper(IConfigurationProvider configurationProvider, Func serviceCtor) + public Mapper(IConfigurationProvider configurationProvider, Factory serviceCtor) { _configurationProvider = (IGlobalConfiguration)configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); - DefaultContext = new ResolutionContext(new MappingOperationOptions(serviceCtor ?? throw new NullReferenceException(nameof(serviceCtor))), this); + _serviceCtor = serviceCtor ?? throw new NullReferenceException(nameof(serviceCtor)); + DefaultContext = new(this); } internal ResolutionContext DefaultContext { get; } ResolutionContext IInternalRuntimeMapper.DefaultContext => DefaultContext; + Factory IInternalRuntimeMapper.ServiceCtor => _serviceCtor; public IConfigurationProvider ConfigurationProvider => _configurationProvider; public TDestination Map(object source) => Map(source, default(TDestination)); public TDestination Map(object source, Action> opts) => Map(source, default, opts); @@ -181,19 +186,19 @@ TDestination IInternalRuntimeMapper.Map(TSource source, T private TDestination MapWithOptions(TSource source, TDestination destination, Action> opts, Type sourceType = null, Type destinationType = null) { - var typedOptions = new MappingOperationOptions(DefaultContext.Options.ServiceCtor); + MappingOperationOptions typedOptions = new(_serviceCtor); opts(typedOptions); typedOptions.BeforeMapAction?.Invoke(source, destination); - destination = MapCore(source, destination, new ResolutionContext(typedOptions, this), sourceType, destinationType); + destination = MapCore(source, destination, new(this, typedOptions), sourceType, destinationType); typedOptions.AfterMapAction?.Invoke(source, destination); return destination; } private TDestination MapCore( TSource source, TDestination destination, ResolutionContext context, Type sourceType = null, Type destinationType = null, MemberMap memberMap = null) { - var runtimeTypes = new TypePair(source?.GetType() ?? sourceType ?? typeof(TSource), destination?.GetType() ?? destinationType ?? typeof(TDestination)); - var requestedTypes = new TypePair(typeof(TSource), typeof(TDestination)); - var mapRequest = new MapRequest(requestedTypes, runtimeTypes, memberMap); + TypePair requestedTypes = new(typeof(TSource), typeof(TDestination)); + TypePair runtimeTypes = new(source?.GetType() ?? sourceType ?? typeof(TSource), destination?.GetType() ?? destinationType ?? typeof(TDestination)); + MapRequest mapRequest = new(requestedTypes, runtimeTypes, memberMap); return _configurationProvider.GetExecutionPlan(mapRequest)(source, destination, context); } } diff --git a/src/AutoMapper/Mappers/CollectionMapper.cs b/src/AutoMapper/Mappers/CollectionMapper.cs index 4a8afb6eda..7785af7f72 100644 --- a/src/AutoMapper/Mappers/CollectionMapper.cs +++ b/src/AutoMapper/Mappers/CollectionMapper.cs @@ -15,7 +15,7 @@ namespace AutoMapper.Internal.Mappers public class CollectionMapper : IObjectMapperInfo { public TypePair GetAssociatedTypes(TypePair context) => new(GetElementType(context.SourceType), GetElementType(context.DestinationType)); - public bool IsMatch(TypePair context) => context.SourceType.IsCollection() && context.DestinationType.IsCollection(); + public bool IsMatch(TypePair context) => context.IsCollection(); public Expression MapExpression(IGlobalConfiguration configurationProvider, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) { var destinationType = destExpression.Type; diff --git a/src/AutoMapper/MemberMap.cs b/src/AutoMapper/MemberMap.cs index 3ba61e7f40..8f72466629 100644 --- a/src/AutoMapper/MemberMap.cs +++ b/src/AutoMapper/MemberMap.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace AutoMapper @@ -9,23 +8,28 @@ namespace AutoMapper using static Expression; using Execution; using Internal; + using System.Diagnostics; + /// /// The base class for member maps (property, constructor and path maps). /// [EditorBrowsable(EditorBrowsableState.Never)] - public class MemberMap + public class MemberMap : IValueResolver { + private static readonly LambdaExpression EmptyLambda = Lambda(ExpressionBuilder.Null); protected MemberMap(TypeMap typeMap = null) => TypeMap = typeMap; public static readonly MemberMap Instance = new(); public TypeMap TypeMap { get; protected set; } - public LambdaExpression CustomMapExpression { get; set; } - public virtual Type SourceType { get => default; protected set { } } - public virtual MemberInfo[] SourceMembers => Array.Empty(); - public IncludedMember IncludedMember { get; protected set; } + public LambdaExpression CustomMapExpression => Resolver?.ProjectToExpression; + public bool IsResolveConfigured => Resolver != null && Resolver != this; + public void SetResolver(LambdaExpression lambda) => Resolver = new ExpressionResolver(lambda); + public virtual Type SourceType => default; + public virtual MemberInfo[] SourceMembers { get => Array.Empty(); set { } } + public virtual IncludedMember IncludedMember => null; public virtual string DestinationName => default; public virtual Type DestinationType { get => default; protected set { } } - public virtual TypePair Types() => new TypePair(SourceType, DestinationType); - public virtual bool CanResolveValue { get => default; set { } } + public virtual TypePair Types() => new(SourceType, DestinationType); + public bool CanResolveValue => !Ignored && Resolver != null; public bool IsMapped => Ignored || CanResolveValue; public virtual bool Ignored { get => default; set { } } public virtual bool Inline { get; set; } = true; @@ -35,30 +39,29 @@ public class MemberMap public virtual object NullSubstitute { get => default; set { } } public virtual LambdaExpression PreCondition { get => default; set { } } public virtual LambdaExpression Condition { get => default; set { } } - public virtual LambdaExpression CustomMapFunction { get => default; set { } } - public virtual ValueResolverConfiguration ValueResolverConfig { get => default; set { } } - public virtual ValueResolverConfiguration ValueConverterConfig { get => default; set { } } + public IValueResolver Resolver { get; set; } public virtual IReadOnlyCollection ValueTransformers => Array.Empty(); - public MemberInfo SourceMember => CustomMapExpression.GetMember() ?? SourceMembers.LastOrDefault(); + public MemberInfo SourceMember => Resolver?.GetSourceMember(this); + public string GetSourceMemberName() => Resolver?.SourceMemberName ?? SourceMember?.Name; public bool MustUseDestination => UseDestinationValue is true || !CanBeSet; public void MapFrom(LambdaExpression sourceMember) { - CustomMapExpression = sourceMember; + SetResolver(sourceMember); Ignored = false; } public void MapFrom(string sourceMembersPath) { var mapExpression = TypeMap.SourceType.IsGenericTypeDefinition ? - // just a placeholder so the member is mapped - Lambda(ExpressionBuilder.Null) : + EmptyLambda :// just a placeholder so the member is mapped ExpressionBuilder.MemberAccessLambda(TypeMap.SourceType, sourceMembersPath); MapFrom(mapExpression); } public override string ToString() => DestinationName; + public Expression ChainSourceMembers(Expression source) => SourceMembers.Chain(source); public Expression ChainSourceMembers(Expression source, Type destinationType, Expression defaultValue) => - SourceMembers.Chain(source).NullCheck(destinationType, defaultValue); - public bool AllowsNullDestinationValues() => Profile?.AllowsNullDestinationValuesFor(this) ?? true; - public bool AllowsNullCollections() => (Profile?.AllowsNullCollectionsFor(this)).GetValueOrDefault(); + ChainSourceMembers(source)?.NullCheck(destinationType, defaultValue); + public bool AllowsNullDestinationValues => Profile?.AllowsNullDestinationValuesFor(this) ?? true; + public bool AllowsNullCollections => (Profile?.AllowsNullCollectionsFor(this)).GetValueOrDefault(); public ProfileMap Profile => TypeMap?.Profile; private int MaxDepth => (TypeMap?.MaxDepth).GetValueOrDefault(); public bool MapperEquals(MemberMap other) @@ -68,30 +71,20 @@ public bool MapperEquals(MemberMap other) return false; } return other.MustUseDestination == MustUseDestination && other.MaxDepth == MaxDepth && - other.AllowsNullDestinationValues() == AllowsNullDestinationValues() && other.AllowsNullCollections() == AllowsNullCollections(); + other.AllowsNullDestinationValues == AllowsNullDestinationValues && other.AllowsNullCollections == AllowsNullCollections; } - public int MapperGetHashCode() => HashCode.Combine(MustUseDestination, MaxDepth, AllowsNullDestinationValues(), AllowsNullCollections()); - } - public class ValueResolverConfiguration - { - public object Instance { get; } - public Type ConcreteType { get; } - public Type InterfaceType { get; } - public LambdaExpression SourceMember { get; set; } - public string SourceMemberName { get; set; } - - public ValueResolverConfiguration(Type concreteType, Type interfaceType) - { - ConcreteType = concreteType; - InterfaceType = interfaceType; - } - - public ValueResolverConfiguration(object instance, Type interfaceType) + public int MapperGetHashCode() => HashCode.Combine(MustUseDestination, MaxDepth, AllowsNullDestinationValues, AllowsNullCollections); + protected Type GetSourceType() => Resolver?.ResolvedType ?? DestinationType; + public void MapByConvention(MemberInfo[] sourceMembers) { - Instance = instance; - InterfaceType = interfaceType; + Debug.Assert(sourceMembers.Length > 0); + SourceMembers = sourceMembers; + Resolver = this; } - public Type ResolvedType => InterfaceType.GenericTypeArguments.Last(); + Expression IValueResolver.GetExpression(MemberMap memberMap, Expression source, Expression destination, Expression destinationMember) => + ChainSourceMembers(source, memberMap.DestinationType, destinationMember); + MemberInfo IValueResolver.GetSourceMember(MemberMap memberMap) => SourceMembers[0]; + Type IValueResolver.ResolvedType => SourceMembers[^1].GetMemberType(); } public readonly struct ValueTransformerConfiguration { diff --git a/src/AutoMapper/PathMap.cs b/src/AutoMapper/PathMap.cs index ac079f2d00..3e49a27868 100644 --- a/src/AutoMapper/PathMap.cs +++ b/src/AutoMapper/PathMap.cs @@ -14,7 +14,7 @@ public class PathMap : MemberMap public PathMap(PathMap pathMap, TypeMap typeMap, IncludedMember includedMember) : this(pathMap.DestinationExpression, pathMap.MemberPath, typeMap) { IncludedMember = includedMember.Chain(pathMap.IncludedMember); - CustomMapExpression = pathMap.CustomMapExpression; + Resolver = pathMap.Resolver; Condition = pathMap.Condition; Ignored = pathMap.Ignored; } @@ -23,14 +23,14 @@ public PathMap(LambdaExpression destinationExpression, MemberPath memberPath, Ty MemberPath = memberPath; DestinationExpression = destinationExpression; } - public override Type SourceType => CustomMapExpression.ReturnType; + public override Type SourceType => Resolver.ResolvedType; public LambdaExpression DestinationExpression { get; } public MemberPath MemberPath { get; } public override Type DestinationType => MemberPath.Last.GetMemberType(); public override string DestinationName => MemberPath.ToString(); - public override bool CanResolveValue => !Ignored; public override bool CanBeSet => ReflectionHelper.CanBeSet(MemberPath.Last); public override bool Ignored { get; set; } + public override IncludedMember IncludedMember { get; } public override LambdaExpression Condition { get; set; } } } \ No newline at end of file diff --git a/src/AutoMapper/ProfileMap.cs b/src/AutoMapper/ProfileMap.cs index 0c292fbc2e..a96ddf3928 100644 --- a/src/AutoMapper/ProfileMap.cs +++ b/src/AutoMapper/ProfileMap.cs @@ -206,19 +206,8 @@ public TypeMap CreateClosedGenericTypeMap(ITypeMapConfiguration openMapConfig, T closedMap = new TypeMap(closedTypes.SourceType, closedTypes.DestinationType, this, openMapConfig); } openMapConfig.Configure(closedMap); - Configure(closedMap, configurationProvider); - if (closedMap.TypeConverterType != null) - { - var typeParams = (openMapConfig.SourceType.IsGenericTypeDefinition ? closedTypes.SourceType.GenericTypeArguments : Type.EmptyTypes) - .Concat(openMapConfig.DestinationType.IsGenericTypeDefinition ? closedTypes.DestinationType.GenericTypeArguments : Type.EmptyTypes); - var neededParameters = closedMap.TypeConverterType.GenericParametersCount(); - closedMap.TypeConverterType = closedMap.TypeConverterType.MakeGenericType(typeParams.Take(neededParameters).ToArray()); - } - if (closedMap.DestinationTypeOverride is { IsGenericTypeDefinition: true }) - { - var neededParameters = closedMap.DestinationTypeOverride.GenericParametersCount(); - closedMap.DestinationTypeOverride = closedMap.DestinationTypeOverride.MakeGenericType(closedTypes.DestinationType.GenericTypeArguments.Take(neededParameters).ToArray()); - } + Configure(closedMap, configurationProvider); + closedMap.CloseGenerics(openMapConfig, closedTypes); return closedMap; } public ITypeMapConfiguration GetGenericMap(TypePair genericPair) => _openTypeMapConfigs.GetValueOrDefault(genericPair); diff --git a/src/AutoMapper/PropertyMap.cs b/src/AutoMapper/PropertyMap.cs index 4f6149bdce..fd7fc7fa9f 100644 --- a/src/AutoMapper/PropertyMap.cs +++ b/src/AutoMapper/PropertyMap.cs @@ -2,20 +2,16 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using AutoMapper.Internal; - namespace AutoMapper { [DebuggerDisplay("{DestinationMember.Name}")] [EditorBrowsable(EditorBrowsableState.Never)] public class PropertyMap : MemberMap { - private MemberInfo[] _sourceMembers = Array.Empty(); - private List _valueTransformerConfigs; - private bool? _canResolveValue; + private MemberMapDetails _details; private Type _sourceType; public PropertyMap(MemberInfo destinationMember, Type destinationMemberType, TypeMap typeMap) : base(typeMap) { @@ -25,35 +21,15 @@ public PropertyMap(MemberInfo destinationMember, Type destinationMemberType, Typ public PropertyMap(PropertyMap inheritedMappedProperty, TypeMap typeMap) : this(inheritedMappedProperty.DestinationMember, inheritedMappedProperty.DestinationType, typeMap) => ApplyInheritedPropertyMap(inheritedMappedProperty); public PropertyMap(PropertyMap includedMemberMap, TypeMap typeMap, IncludedMember includedMember) - : this(includedMemberMap, typeMap) => IncludedMember = includedMember.Chain(includedMemberMap.IncludedMember); + : this(includedMemberMap, typeMap) => Details.IncludedMember = includedMember.Chain(includedMemberMap.IncludedMember); + private MemberMapDetails Details => _details ??= new(); public MemberInfo DestinationMember { get; } public override string DestinationName => DestinationMember.Name; public override Type DestinationType { get; protected set; } - public override MemberInfo[] SourceMembers => _sourceMembers; + public override MemberInfo[] SourceMembers { get; set; } = Array.Empty(); public override bool CanBeSet => ReflectionHelper.CanBeSet(DestinationMember); public override bool Ignored { get; set; } - public override bool? AllowNull { get; set; } - public int? MappingOrder { get; set; } - public override LambdaExpression CustomMapFunction { get; set; } - public override LambdaExpression Condition { get; set; } - public override LambdaExpression PreCondition { get; set; } - public override bool? UseDestinationValue { get; set; } - public bool? ExplicitExpansion { get; set; } - public override object NullSubstitute { get; set; } - public override ValueResolverConfiguration ValueResolverConfig { get; set; } - public override ValueResolverConfiguration ValueConverterConfig { get; set; } - public override IReadOnlyCollection ValueTransformers => _valueTransformerConfigs.NullCheck(); - public override Type SourceType - { - get => _sourceType ??= - ValueConverterConfig?.ResolvedType ?? - ValueResolverConfig?.ResolvedType ?? - CustomMapFunction?.ReturnType ?? - CustomMapExpression?.ReturnType ?? - (_sourceMembers.Length > 0 ? _sourceMembers[_sourceMembers.Length - 1].GetMemberType() : typeof(object)); - protected set => _sourceType = value; - } - public void MapByConvention(IEnumerable sourceMembers) => _sourceMembers = sourceMembers.ToArray(); + public override Type SourceType => _sourceType ??= GetSourceType(); public void ApplyInheritedPropertyMap(PropertyMap inheritedMappedProperty) { if (Ignored) @@ -64,44 +40,66 @@ public void ApplyInheritedPropertyMap(PropertyMap inheritedMappedProperty) { if (inheritedMappedProperty.Ignored) { - _canResolveValue = false; Ignored = true; return; } - _canResolveValue = true; if (inheritedMappedProperty.IsResolveConfigured) { _sourceType = inheritedMappedProperty._sourceType; - CustomMapExpression = inheritedMappedProperty.CustomMapExpression; - CustomMapFunction = inheritedMappedProperty.CustomMapFunction; - ValueResolverConfig = inheritedMappedProperty.ValueResolverConfig; - ValueConverterConfig = inheritedMappedProperty.ValueConverterConfig; + Resolver = inheritedMappedProperty.Resolver; } - else if (_sourceMembers.Length == 0) + else if (Resolver == null) { _sourceType = inheritedMappedProperty._sourceType; - _sourceMembers = inheritedMappedProperty._sourceMembers; + MapByConvention(inheritedMappedProperty.SourceMembers); } } - AllowNull ??= inheritedMappedProperty.AllowNull; - Condition ??= inheritedMappedProperty.Condition; - PreCondition ??= inheritedMappedProperty.PreCondition; - NullSubstitute ??= inheritedMappedProperty.NullSubstitute; - MappingOrder ??= inheritedMappedProperty.MappingOrder; - UseDestinationValue ??= inheritedMappedProperty.UseDestinationValue; - ExplicitExpansion ??= inheritedMappedProperty.ExplicitExpansion; - if (inheritedMappedProperty._valueTransformerConfigs != null) + if (inheritedMappedProperty._details != null) { - _valueTransformerConfigs ??= new(); - _valueTransformerConfigs.InsertRange(0, inheritedMappedProperty._valueTransformerConfigs); + Details.ApplyInheritedPropertyMap(inheritedMappedProperty._details); } } - public override bool CanResolveValue => _canResolveValue ??= !Ignored && (_sourceMembers.Length > 0 || IsResolveConfigured); - public bool IsResolveConfigured => (ValueResolverConfig ?? CustomMapFunction ?? CustomMapExpression ?? (object)ValueConverterConfig) != null; - public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) + public override IncludedMember IncludedMember => _details?.IncludedMember; + public override bool? AllowNull { get => _details?.AllowNull; set => Details.AllowNull = value; } + public int? MappingOrder { get => _details?.MappingOrder; set => Details.MappingOrder = value; } + public bool? ExplicitExpansion { get => _details?.ExplicitExpansion; set => Details.ExplicitExpansion = value; } + public override bool? UseDestinationValue { get => _details?.UseDestinationValue; set => Details.UseDestinationValue = value; } + public override object NullSubstitute { get => _details?.NullSubstitute; set => Details.NullSubstitute = value; } + public override LambdaExpression PreCondition { get => _details?.PreCondition; set => Details.PreCondition = value; } + public override LambdaExpression Condition { get => _details?.Condition; set => Details.Condition = value; } + public void AddValueTransformation(ValueTransformerConfiguration config) => Details.AddValueTransformation(config); + public override IReadOnlyCollection ValueTransformers => (_details?.ValueTransformers).NullCheck(); + class MemberMapDetails { - _valueTransformerConfigs ??= new(); - _valueTransformerConfigs.Add(valueTransformerConfiguration); + public List ValueTransformers { get; private set; } + public bool? AllowNull; + public int? MappingOrder; + public bool? ExplicitExpansion; + public bool? UseDestinationValue; + public object NullSubstitute; + public LambdaExpression PreCondition; + public LambdaExpression Condition; + public IncludedMember IncludedMember; + public void ApplyInheritedPropertyMap(MemberMapDetails inheritedMappedProperty) + { + AllowNull ??= inheritedMappedProperty.AllowNull; + Condition ??= inheritedMappedProperty.Condition; + PreCondition ??= inheritedMappedProperty.PreCondition; + NullSubstitute ??= inheritedMappedProperty.NullSubstitute; + MappingOrder ??= inheritedMappedProperty.MappingOrder; + UseDestinationValue ??= inheritedMappedProperty.UseDestinationValue; + ExplicitExpansion ??= inheritedMappedProperty.ExplicitExpansion; + if (inheritedMappedProperty.ValueTransformers != null) + { + ValueTransformers ??= new(); + ValueTransformers.InsertRange(0, inheritedMappedProperty.ValueTransformers); + } + } + public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) + { + ValueTransformers ??= new(); + ValueTransformers.Add(valueTransformerConfiguration); + } } } } \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/Extensions.cs b/src/AutoMapper/QueryableExtensions/Extensions.cs index 21c9d3ea17..d0f1f9b31b 100644 --- a/src/AutoMapper/QueryableExtensions/Extensions.cs +++ b/src/AutoMapper/QueryableExtensions/Extensions.cs @@ -16,17 +16,6 @@ namespace AutoMapper.QueryableExtensions public static class Extensions { /// - /// Maps a queryable expression of a source type to a queryable expression of a destination type - /// - /// Source type - /// Destination type - /// Source queryable - /// Destination queryable - /// - /// Mapped destination queryable - public static IQueryable Map(this IQueryable sourceQuery, IQueryable destQuery, IConfigurationProvider config) => - QueryMapperVisitor.Map(sourceQuery, destQuery, config.Internal()); - /// /// Extension method to project from a queryable using the provided mapping engine /// /// Projections are only calculated once and cached @@ -59,7 +48,7 @@ public static IQueryable ProjectTo(this IQueryable s /// Optional parameter object for parameterized mapping expressions /// Explicit members to expand /// Queryable result, use queryable extension methods to project and execute result - public static IQueryable ProjectTo(this IQueryable source, IConfigurationProvider configuration, IDictionary parameters, params string[] membersToExpand) => + public static IQueryable ProjectTo(this IQueryable source, IConfigurationProvider configuration, ParameterBag parameters, params string[] membersToExpand) => new ProjectionExpression(source, configuration).To(parameters, membersToExpand); /// /// Extension method to project from a queryable using the provided mapping engine @@ -80,7 +69,7 @@ public static IQueryable ProjectTo(this IQueryable source, Type destinationType, /// Optional parameter object for parameterized mapping expressions /// Explicit members to expand /// Queryable result, use queryable extension methods to project and execute result - public static IQueryable ProjectTo(this IQueryable source, Type destinationType, IConfigurationProvider configuration, IDictionary parameters, params string[] membersToExpand) => + public static IQueryable ProjectTo(this IQueryable source, Type destinationType, IConfigurationProvider configuration, ParameterBag parameters, params string[] membersToExpand) => new ProjectionExpression(source, configuration).To(destinationType, parameters, membersToExpand); readonly struct ProjectionExpression { @@ -114,13 +103,12 @@ public static MemberInfo[] GetMemberPath(Expression expression) { var memberVisitor = new MemberVisitor(); memberVisitor.Visit(expression); - return memberVisitor.MemberPath.ToArray(); + return memberVisitor._members.ToArray(); } protected override Expression VisitMember(MemberExpression node) { _members.AddRange(node.GetMemberExpressions().Select(e => e.Member)); return node; } - public IReadOnlyCollection MemberPath => _members; } } \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/NullsafeQueryRewriter.cs b/src/AutoMapper/QueryableExtensions/NullsafeQueryRewriter.cs index 8ed076aa37..54ca7f125e 100644 --- a/src/AutoMapper/QueryableExtensions/NullsafeQueryRewriter.cs +++ b/src/AutoMapper/QueryableExtensions/NullsafeQueryRewriter.cs @@ -40,7 +40,7 @@ namespace AutoMapper.QueryableExtensions /// internal class NullsafeQueryRewriter : ExpressionVisitor { - static readonly LockingConcurrentDictionary Cache = new LockingConcurrentDictionary(Fallback); + static readonly LockingConcurrentDictionary Cache = new(Fallback); public static Expression NullCheck(Expression expression) => new NullsafeQueryRewriter().Visit(expression); diff --git a/src/AutoMapper/QueryableExtensions/ProjectionBuilder.cs b/src/AutoMapper/QueryableExtensions/ProjectionBuilder.cs index fcb1dbe3eb..d7033602c8 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionBuilder.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionBuilder.cs @@ -19,13 +19,12 @@ public interface IProjectionBuilder { QueryExpressions GetProjection(Type sourceType, Type destinationType, object parameters, MemberPath[] membersToExpand); QueryExpressions CreateProjection(in ProjectionRequest request, LetPropertyMaps letPropertyMaps); - Expression CreateInnerProjection(in ProjectionRequest request, Expression instanceParameter, LetPropertyMaps letPropertyMaps); } [EditorBrowsable(EditorBrowsableState.Never)] public interface IProjectionMapper { - bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource); - Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps); + bool IsMatch(TypePair context); + Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps); } [EditorBrowsable(EditorBrowsableState.Never)] public class ProjectionBuilder : IProjectionBuilder @@ -33,8 +32,6 @@ public class ProjectionBuilder : IProjectionBuilder internal static List DefaultProjectionMappers() => new() { - new CustomProjectionMapper(), - new MappedTypeProjectionMapper(), new AssignableProjectionMapper(), new EnumerableProjectionMapper(), new NullableSourceProjectionMapper(), @@ -65,23 +62,18 @@ private QueryExpressions CreateProjection(ProjectionRequest request) => public QueryExpressions CreateProjection(in ProjectionRequest request, LetPropertyMaps letPropertyMaps) { var instanceParameter = Parameter(request.SourceType, "dto"+ request.SourceType.Name); - var projection = CreateProjectionCore(request, instanceParameter, letPropertyMaps, out var typeMap); + var typeMap = _configurationProvider.ResolveTypeMap(request.SourceType, request.DestinationType) ?? throw TypeMap.MissingMapException(request.SourceType, request.DestinationType); + var projection = CreateProjectionCore(request, instanceParameter, typeMap, letPropertyMaps); return letPropertyMaps.Count > 0 ? letPropertyMaps.GetSubQueryExpression(this, projection, typeMap, request, instanceParameter) : new(projection, instanceParameter); } - public Expression CreateInnerProjection(in ProjectionRequest request, Expression instanceParameter, LetPropertyMaps letPropertyMaps) => - CreateProjectionCore(request, instanceParameter, letPropertyMaps, out var _); - private Expression CreateProjectionCore(in ProjectionRequest request, Expression instanceParameter, LetPropertyMaps letPropertyMaps, out TypeMap typeMap) - { - typeMap = _configurationProvider.ResolveTypeMap(request.SourceType, request.DestinationType) ?? throw QueryMapperHelper.MissingMapException(request.SourceType, request.DestinationType); - return CreateProjectionCore(request, instanceParameter, typeMap, letPropertyMaps); - } private Expression CreateProjectionCore(ProjectionRequest request, Expression instanceParameter, TypeMap typeMap, LetPropertyMaps letPropertyMaps) { - if (typeMap.CustomMapExpression != null) + var customProjection = typeMap.CustomMapExpression?.ReplaceParameters(instanceParameter); + if (customProjection != null) { - return typeMap.CustomMapExpression.ReplaceParameters(instanceParameter); + return customProjection; } var propertiesProjections = new List(); int depth; @@ -117,7 +109,7 @@ void ProjectProperties() } } } - Expression TryProjectMember(MemberMap memberMap, bool? explicitExpansion = null) + Expression TryProjectMember(MemberMap memberMap, bool? explicitExpansion = null, Expression defaultSource = null) { var memberProjection = new MemberProjection(memberMap); letPropertyMaps.Push(memberProjection); @@ -135,8 +127,22 @@ Expression ProjectMemberCore() { return null; } - var projectionMapper = GetProjectionMapper(); - var mappedExpression = projectionMapper.Project(_configurationProvider, memberMap, memberTypeMap, memberRequest, resolvedSource, letPropertyMaps); + Expression mappedExpression; + if (memberTypeMap != null) + { + mappedExpression = CreateProjectionCore(memberRequest, resolvedSource, memberTypeMap, letPropertyMaps); + if (mappedExpression != null && memberTypeMap.CustomMapExpression == null && memberMap.AllowsNullDestinationValues && + resolvedSource is not ParameterExpression && !resolvedSource.Type.IsCollection()) + { + // Handles null source property so it will not create an object with possible non-nullable properties which would result in an exception. + mappedExpression = resolvedSource.IfNullElse(Constant(null, mappedExpression.Type), mappedExpression); + } + } + else + { + var projectionMapper = GetProjectionMapper(); + mappedExpression = projectionMapper.Project(_configurationProvider, memberRequest, resolvedSource, letPropertyMaps); + } return mappedExpression == null ? null : memberMap.ApplyTransformers(mappedExpression); Expression ResolveSource() { @@ -144,8 +150,8 @@ Expression ResolveSource() var resolvedSource = memberMap switch { { CustomMapExpression: LambdaExpression mapFrom } => MapFromExpression(mapFrom), - { SourceMembers: { Length: >0 } sourceMembers } => sourceMembers.Chain(CheckCustomSource()), - _ => throw CannotMap(memberMap, request.SourceType) + { SourceMembers.Length: > 0 } => memberMap.ChainSourceMembers(CheckCustomSource()), + _ => defaultSource ?? throw CannotMap(memberMap, request.SourceType) }; if (NullSubstitute()) { @@ -179,9 +185,10 @@ Expression CheckCustomSource() } IProjectionMapper GetProjectionMapper() { + var context = memberMap.Types(); foreach (var mapper in _projectionMappers) { - if (mapper.IsMatch(memberMap, memberTypeMap, resolvedSource)) + if (mapper.IsMatch(context)) { return mapper; } @@ -194,8 +201,8 @@ IProjectionMapper GetProjectionMapper() { { CustomCtorExpression: LambdaExpression ctorExpression } => (NewExpression)ctorExpression.ReplaceParameters(instanceParameter), { ConstructorMap: { CanResolve: true } constructorMap } => - New(constructorMap.Ctor, constructorMap.CtorParams.Select(map => TryProjectMember(map) ?? Default(map.DestinationType))), - _ => New(typeMap.DestinationTypeToUse) + New(constructorMap.Ctor, constructorMap.CtorParams.Select(map => TryProjectMember(map, null, map.DefaultValue()) ?? Default(map.DestinationType))), + _ => New(typeMap.DestinationType) }; } private static AutoMapperMappingException CannotMap(MemberMap memberMap, Type sourceType) => new( @@ -250,7 +257,7 @@ void ReplaceSubQueries() { var letProperty = letType.GetProperty(letMapInfo.Property.Name); var letPropertyMap = letTypeMap.FindOrCreatePropertyMapFor(letProperty, letMapInfo.Property.Type); - letPropertyMap.CustomMapExpression = Lambda(letMapInfo.LetExpression.ReplaceParameters(letMapInfo.MapFromSource), secondParameter); + letPropertyMap.SetResolver(Lambda(letMapInfo.LetExpression.ReplaceParameters(letMapInfo.MapFromSource), secondParameter)); projection = projection.Replace(letMapInfo.Marker, Property(secondParameter, letProperty)); } projection = new ReplaceMemberAccessesVisitor(instanceParameter, secondParameter).Visit(projection); diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs index e9e59e11c3..072b2bfa65 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs @@ -8,9 +8,8 @@ namespace AutoMapper.QueryableExtensions.Impl [EditorBrowsable(EditorBrowsableState.Never)] public class AssignableProjectionMapper : IProjectionMapper { - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) - => memberMap.DestinationType.IsAssignableFrom(resolvedSource.Type); - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) - => ExpressionBuilder.ToType(resolvedSource, memberMap.DestinationType); + public bool IsMatch(TypePair context) => context.DestinationType.IsAssignableFrom(context.SourceType); + public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) + => ExpressionBuilder.ToType(resolvedSource, request.DestinationType); } } \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/CustomProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/CustomProjectionMapper.cs deleted file mode 100644 index adf3fa13b4..0000000000 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/CustomProjectionMapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper.Execution; -using AutoMapper.Internal; -using System.ComponentModel; -using System.Linq.Expressions; -namespace AutoMapper.QueryableExtensions.Impl -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public class CustomProjectionMapper : IProjectionMapper - { - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) => memberTypeMap?.CustomMapExpression != null; - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) - => memberTypeMap.CustomMapExpression.ReplaceParameters(resolvedSource); - } -} \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs index 1a185c3aad..b61d779227 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs @@ -8,12 +8,8 @@ namespace AutoMapper.QueryableExtensions.Impl [EditorBrowsable(EditorBrowsableState.Never)] public class EnumProjectionMapper : IProjectionMapper { - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) - => Convert(resolvedSource, memberMap.DestinationType); - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) - { - var types = memberMap.Types(); - return types.IsEnumToEnum() || types.IsUnderlyingTypeToEnum() || types.IsEnumToUnderlyingType(); - } + public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) + => Convert(resolvedSource, request.DestinationType); + public bool IsMatch(TypePair context) => context.IsEnumToEnum() || context.IsUnderlyingTypeToEnum() || context.IsEnumToUnderlyingType(); } } \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs index 848ee8ba5c..cdd173a7de 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs @@ -16,13 +16,12 @@ public class EnumerableProjectionMapper : IProjectionMapper private static readonly MethodInfo SelectMethod = typeof(Enumerable).StaticGenericMethod("Select", parametersCount: 2); private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetStaticMethod("ToArray"); private static readonly MethodInfo ToListMethod = typeof(Enumerable).GetStaticMethod("ToList"); - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) => - memberMap.DestinationType.IsCollection() && memberMap.SourceType.IsCollection(); - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) + public bool IsMatch(TypePair context) => context.IsCollection(); + public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) { - var destinationType = memberMap.DestinationType; + var destinationType = request.DestinationType; var destinationListType = GetElementType(destinationType); - var sourceListType = GetElementType(memberMap.SourceType); + var sourceListType = GetElementType(request.SourceType); var sourceExpression = resolvedSource; if (sourceListType != destinationListType) { diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/MappedTypeProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/MappedTypeProjectionMapper.cs deleted file mode 100644 index 2ba574b3e8..0000000000 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/MappedTypeProjectionMapper.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AutoMapper.Execution; -using AutoMapper.Internal; -using System.ComponentModel; -using System.Linq.Expressions; - -namespace AutoMapper.QueryableExtensions.Impl -{ - using static Expression; - [EditorBrowsable(EditorBrowsableState.Never)] - public class MappedTypeProjectionMapper : IProjectionMapper - { - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) => memberTypeMap != null; - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) - { - var transformedExpression = configuration.ProjectionBuilder.CreateInnerProjection(request, resolvedSource, letPropertyMaps); - if(transformedExpression == null) - { - return null; - } - // Handles null source property so it will not create an object with possible non-nullable properties which would result in an exception. - if (memberMap.AllowsNullDestinationValues() && resolvedSource is not ParameterExpression && !resolvedSource.Type.IsCollection()) - { - transformedExpression = resolvedSource.IfNullElse(Constant(null, transformedExpression.Type), transformedExpression); - } - return transformedExpression; - } - } -} \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs index f352dca182..5405aab9df 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs @@ -5,9 +5,9 @@ namespace AutoMapper.QueryableExtensions.Impl using static Expression; internal class NullableSourceProjectionMapper : IProjectionMapper { - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => - Coalesce(resolvedSource, New(memberMap.DestinationType)); - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) => - memberMap.DestinationType.IsValueType && !memberMap.DestinationType.IsNullableType() && resolvedSource.Type.IsNullableType(); + public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => + Coalesce(resolvedSource, New(request.DestinationType)); + public bool IsMatch(TypePair context) => + context.DestinationType.IsValueType && !context.DestinationType.IsNullableType() && context.SourceType.IsNullableType(); } } \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs index c08eb43335..546330fae9 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs @@ -7,8 +7,8 @@ namespace AutoMapper.QueryableExtensions.Impl [EditorBrowsable(EditorBrowsableState.Never)] public class StringProjectionMapper : IProjectionMapper { - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) => memberMap.DestinationType == typeof(string); - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) + public bool IsMatch(TypePair context) => context.DestinationType == typeof(string); + public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => Expression.Call(resolvedSource, ExpressionBuilder.ObjectToString); } } \ No newline at end of file diff --git a/src/AutoMapper/QueryableExtensions/QueryMapperVisitor.cs b/src/AutoMapper/QueryableExtensions/QueryMapperVisitor.cs deleted file mode 100644 index a3961b3f45..0000000000 --- a/src/AutoMapper/QueryableExtensions/QueryMapperVisitor.cs +++ /dev/null @@ -1,255 +0,0 @@ -using AutoMapper.Internal; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace AutoMapper.QueryableExtensions.Impl -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public class QueryMapperVisitor : ExpressionVisitor - { - private readonly IQueryable _destQuery; - private readonly ParameterExpression _instanceParameter; - private readonly Type _sourceType; - private readonly Type _destinationType; - private readonly Stack _tree = new Stack(); - private readonly Stack _newTree = new Stack(); - private readonly MemberAccessQueryMapperVisitor _memberVisitor; - - - internal QueryMapperVisitor(Type sourceType, Type destinationType, IQueryable destQuery, IGlobalConfiguration config) - { - _sourceType = sourceType; - _destinationType = destinationType; - _destQuery = destQuery; - _instanceParameter = Expression.Parameter(destinationType, "dto"); - _memberVisitor = new MemberAccessQueryMapperVisitor(this, config); - } - - public static IQueryable Map(IQueryable sourceQuery, IQueryable destQuery, IGlobalConfiguration config) - { - var visitor = new QueryMapperVisitor(typeof(TSource), typeof(TDestination), destQuery, config); - var expr = visitor.Visit(sourceQuery.Expression); - - var newDestQuery = destQuery.Provider.CreateQuery(expr); - return newDestQuery; - } - - public override Expression Visit(Expression node) - { - _tree.Push(node); - // OData Client DataServiceQuery initial expression node type - if (node != null && (int)node.NodeType == 10000) - { - return node; - } - var newNode = base.Visit(node); - _newTree.Push(newNode); - return newNode; - } - - protected override Expression VisitParameter(ParameterExpression node) => _instanceParameter; - - protected override Expression VisitConstant(ConstantExpression node) - { - // It is data source of queryable object instance - if (node.Value is IQueryable query && query.ElementType == _sourceType) - return _destQuery.Expression; - return node; - } - - protected override Expression VisitBinary(BinaryExpression node) - { - var left = Visit(node.Left); - var right = Visit(node.Right); - - // Convert Right expression value to left expr type - // It is needed when PropertyMap is changing type of property - if (left.Type != right.Type && right.NodeType == ExpressionType.Constant) - { - var value = Convert.ChangeType(((ConstantExpression)right).Value, left.Type, CultureInfo.CurrentCulture); - - right = Expression.Constant(value, left.Type); - } - - return Expression.MakeBinary(node.NodeType, left, right); - } - - protected override Expression VisitLambda(Expression node) - { - var newBody = Visit(node.Body); - var newParams = node.Parameters.Select(p => (ParameterExpression)Visit(p)); - - var delegateType = ChangeLambdaArgTypeFormSourceToDest(node.Type, newBody.Type); - - var newLambda = Expression.Lambda(delegateType, newBody, newParams); - return newLambda; - } - - protected override Expression VisitMethodCall(MethodCallExpression node) - { - if (node.Method.Name == "OrderBy" || node.Method.Name == "OrderByDescending" || - node.Method.Name == "ThenBy" || node.Method.Name == "ThenByDescending") - { - return VisitOrderBy(node); - } - - var args = node.Arguments.Select(Visit).ToList(); - var newObject = Visit(node.Object); - var method = ChangeMethodArgTypeFormSourceToDest(node.Method); - - var newMethodCall = Expression.Call(newObject, method, args); - return newMethodCall; - } - - private Expression VisitOrderBy(MethodCallExpression node) - { - var query = node.Arguments[0]; - var orderByExpr = node.Arguments[1]; - - var newQuery = Visit(query); - var newOrderByExpr = Visit(orderByExpr); - var newObject = Visit(node.Object); - - - - var genericMethod = node.Method.GetGenericMethodDefinition(); - var methodArgs = node.Method.GetGenericArguments(); - methodArgs[0] = methodArgs[0].ReplaceItemType(_sourceType, _destinationType); - - // for typical orderby expression, a unaryexpression is used that contains a - // func which in turn defines the type of the field that has to be used for ordering/sorting - if (newOrderByExpr is UnaryExpression unary && unary.Operand.Type.IsGenericType) - { - methodArgs[1] = methodArgs[1].ReplaceItemType(typeof(string), unary.Operand.Type.GenericTypeArguments.Last()); - } - else - { - methodArgs[1] = methodArgs[1].ReplaceItemType(typeof(string), typeof(int)); - } - var orderByMethod = genericMethod.MakeGenericMethod(methodArgs); - - return Expression.Call(newObject, orderByMethod, newQuery, newOrderByExpr); - } - - protected override Expression VisitMember(MemberExpression node) => _memberVisitor.Visit(node); - - private MethodInfo ChangeMethodArgTypeFormSourceToDest(MethodInfo mi) - { - if (!mi.IsGenericMethod) - return mi; - var genericMethod = mi.GetGenericMethodDefinition(); - var methodArgs = mi.GetGenericArguments(); - methodArgs = methodArgs.Select(t => t.ReplaceItemType(_sourceType, _destinationType)).ToArray(); - return genericMethod.MakeGenericMethod(methodArgs); - - } - - private Type ChangeLambdaArgTypeFormSourceToDest(Type lambdaType, Type returnType) - { - if (lambdaType.IsGenericType) - { - var genArgs = lambdaType.GetTypeInfo().GenericTypeArguments; - var newGenArgs = genArgs.Select(t => t.ReplaceItemType(_sourceType, _destinationType)).ToArray(); - var genericTypeDef = lambdaType.GetGenericTypeDefinition(); - if (genericTypeDef.FullName.StartsWith("System.Func")) - { - newGenArgs[newGenArgs.Length - 1] = returnType; - } - return genericTypeDef.MakeGenericType(newGenArgs); - } - return lambdaType; - } - } - public class MemberAccessQueryMapperVisitor : ExpressionVisitor - { - private readonly ExpressionVisitor _rootVisitor; - private readonly IGlobalConfiguration _config; - - public MemberAccessQueryMapperVisitor(ExpressionVisitor rootVisitor, IGlobalConfiguration config) - { - _rootVisitor = rootVisitor; - _config = config; - } - - protected override Expression VisitMember(MemberExpression node) - { - var parentExpr = _rootVisitor.Visit(node.Expression); - if (parentExpr != null) - { - var propertyMap = _config.GetPropertyMap(node.Member, parentExpr.Type); - - var newMember = Expression.MakeMemberAccess(parentExpr, propertyMap.DestinationMember); - - return newMember; - } - return node; - } - } - [EditorBrowsable(EditorBrowsableState.Never)] - public static class QueryMapperHelper - { - /// - /// if targetType is oldType, method will return newType - /// if targetType is not oldType, method will return targetType - /// if targetType is generic type with oldType arguments, method will replace all oldType arguments on newType - /// - /// - /// - /// - /// - public static Type ReplaceItemType(this Type targetType, Type oldType, Type newType) - { - if (targetType == oldType) - return newType; - - if (targetType.IsGenericType) - { - var genSubArgs = targetType.GetTypeInfo().GenericTypeArguments; - var newGenSubArgs = new Type[genSubArgs.Length]; - for (var i = 0; i < genSubArgs.Length; i++) - newGenSubArgs[i] = ReplaceItemType(genSubArgs[i], oldType, newType); - return targetType.GetGenericTypeDefinition().MakeGenericType(newGenSubArgs); - } - - return targetType; - } - - public static PropertyMap GetPropertyMap(this IGlobalConfiguration config, MemberInfo sourceMemberInfo, Type destinationMemberType) - { - var typeMap = config.CheckIfMapExists(sourceMemberInfo.DeclaringType, destinationMemberType); - - var propertyMap = typeMap.PropertyMaps - .FirstOrDefault(pm => pm.CanResolveValue && - pm.SourceMember != null && pm.SourceMember.Name == sourceMemberInfo.Name); - - if (propertyMap == null) - throw PropertyConfigurationException(typeMap, sourceMemberInfo.Name); - - return propertyMap; - } - - public static TypeMap CheckIfMapExists(this IGlobalConfiguration config, Type sourceType, Type destinationType) - { - var typeMap = config.ResolveTypeMap(sourceType, destinationType); - if (typeMap == null) - { - throw MissingMapException(sourceType, destinationType); - } - return typeMap; - } - - public static Exception PropertyConfigurationException(TypeMap typeMap, params string[] unmappedPropertyNames) - => new AutoMapperConfigurationException(new[] { new AutoMapperConfigurationException.TypeMapConfigErrors(typeMap, unmappedPropertyNames, true) }); - - public static Exception MissingMapException(TypePair types) - => MissingMapException(types.SourceType, types.DestinationType); - - public static Exception MissingMapException(Type sourceType, Type destinationType) - => new InvalidOperationException($"Missing map from {sourceType} to {destinationType}. Create using CreateMap<{sourceType.Name}, {destinationType.Name}>."); - } -} \ No newline at end of file diff --git a/src/AutoMapper/ResolutionContext.cs b/src/AutoMapper/ResolutionContext.cs index f4700fbb00..1ccae305be 100644 --- a/src/AutoMapper/ResolutionContext.cs +++ b/src/AutoMapper/ResolutionContext.cs @@ -10,33 +10,32 @@ public class ResolutionContext : IInternalRuntimeMapper { private Dictionary _instanceCache; private Dictionary _typeDepth; - private readonly IInternalRuntimeMapper _inner; - internal ResolutionContext(IMappingOperationOptions options, IInternalRuntimeMapper mapper) + private readonly IInternalRuntimeMapper _mapper; + private readonly IMappingOperationOptions _options; + internal ResolutionContext(IInternalRuntimeMapper mapper, IMappingOperationOptions options = null) { - Options = options; - _inner = mapper; + _mapper = mapper; + _options = options; } - internal ResolutionContext(IInternalRuntimeMapper mapper) : this(mapper.DefaultContext.Options, mapper) { } /// - /// Mapping operation options - /// - public IMappingOperationOptions Options { get; } - /// - /// Context items from + /// The items passed in the options of the Map call. /// public IDictionary Items { get { - CheckDefault(); - return Options.Items; + if (_options == null) + { + ThrowInvalidMap(); + } + return _options.Items; } } /// /// Current mapper /// public IRuntimeMapper Mapper => this; - ResolutionContext IInternalRuntimeMapper.DefaultContext => _inner.DefaultContext; + ResolutionContext IInternalRuntimeMapper.DefaultContext => _mapper.DefaultContext; /// /// Instance cache for resolving circular references /// @@ -45,7 +44,7 @@ public Dictionary InstanceCache get { CheckDefault(); - return _instanceCache ??= new Dictionary(); + return _instanceCache ??= new(); } } /// @@ -56,45 +55,26 @@ private Dictionary TypeDepth get { CheckDefault(); - return _typeDepth ??= new Dictionary(); + return _typeDepth ??= new(); } } TDestination IMapperBase.Map(object source) => ((IMapperBase)this).Map(source, default(TDestination)); - TDestination IMapperBase.Map(TSource source) - => _inner.Map(source, default(TDestination), this); - TDestination IMapperBase.Map(TSource source, TDestination destination) - => _inner.Map(source, destination, this); - object IMapperBase.Map(object source, Type sourceType, Type destinationType) - => _inner.Map(source, (object)null, this, sourceType, destinationType); - object IMapperBase.Map(object source, object destination, Type sourceType, Type destinationType) - => _inner.Map(source, destination, this, sourceType, destinationType); + TDestination IMapperBase.Map(TSource source) => _mapper.Map(source, default(TDestination), this); + TDestination IMapperBase.Map(TSource source, TDestination destination) => _mapper.Map(source, destination, this); + object IMapperBase.Map(object source, Type sourceType, Type destinationType) => _mapper.Map(source, (object)null, this, sourceType, destinationType); + object IMapperBase.Map(object source, object destination, Type sourceType, Type destinationType) => _mapper.Map(source, destination, this, sourceType, destinationType); TDestination IInternalRuntimeMapper.Map(TSource source, TDestination destination, ResolutionContext context, - Type sourceType, Type destinationType, MemberMap memberMap) - => _inner.Map(source, destination, context, sourceType, destinationType, memberMap); - internal object CreateInstance(Type type) - { - var service = Options.ServiceCtor(type); - if (service == null) - { - throw new AutoMapperMappingException("Cannot create an instance of type " + type); - } - return service; - } - internal object GetDestination(object source, Type destinationType) - { - if (source == null) - { - return null; - } - return InstanceCache.GetValueOrDefault(new ContextCacheKey(source, destinationType)); - } + Type sourceType, Type destinationType, MemberMap memberMap) => _mapper.Map(source, destination, context, sourceType, destinationType, memberMap); + internal object CreateInstance(Type type) => ServiceCtor()(type) ?? throw new AutoMapperMappingException("Cannot create an instance of type " + type); + private Func ServiceCtor() => _options?.ServiceCtor ?? _mapper.ServiceCtor; + internal object GetDestination(object source, Type destinationType) => source == null ? null : InstanceCache.GetValueOrDefault(new(source, destinationType)); internal void CacheDestination(object source, Type destinationType, object destination) { if (source == null) { return; } - InstanceCache[new ContextCacheKey(source, destinationType)] = destination; + InstanceCache[new(source, destinationType)] = destination; } internal void IncrementTypeDepth(TypeMap typeMap) => TypeDepth[typeMap.Types]++; internal void DecrementTypeDepth(TypeMap typeMap) => TypeDepth[typeMap.Types]--; @@ -107,25 +87,27 @@ internal bool OverTypeDepth(TypeMap typeMap) } return depth > typeMap.MaxDepth; } - internal bool IsDefault => this == _inner.DefaultContext; + internal bool IsDefault => this == _mapper.DefaultContext; + Func IInternalRuntimeMapper.ServiceCtor => ServiceCtor(); internal static void CheckContext(ref ResolutionContext resolutionContext) { if (resolutionContext.IsDefault) { - resolutionContext = new ResolutionContext(resolutionContext._inner); + resolutionContext = new(resolutionContext._mapper); } } - internal TDestination MapInternal(TSource source, TDestination destination, MemberMap memberMap) - => _inner.Map(source, destination, this, memberMap: memberMap); - internal object Map(object source, object destination, Type sourceType, Type destinationType, MemberMap memberMap) - => _inner.Map(source, destination, this, sourceType, destinationType, memberMap); + internal TDestination MapInternal(TSource source, TDestination destination, MemberMap memberMap) => + _mapper.Map(source, destination, this, memberMap: memberMap); + internal object Map(object source, object destination, Type sourceType, Type destinationType, MemberMap memberMap) => + _mapper.Map(source, destination, this, sourceType, destinationType, memberMap); private void CheckDefault() { if (IsDefault) { - throw new InvalidOperationException("You must use a Map overload that takes Action!"); + ThrowInvalidMap(); } } + private static void ThrowInvalidMap() => throw new InvalidOperationException("Context.Items are only available when using a Map overload that takes Action!"); } public readonly struct ContextCacheKey : IEquatable { diff --git a/src/AutoMapper/TypeMap.cs b/src/AutoMapper/TypeMap.cs index 4eedfb1a8e..5fecf159e2 100644 --- a/src/AutoMapper/TypeMap.cs +++ b/src/AutoMapper/TypeMap.cs @@ -8,6 +8,8 @@ namespace AutoMapper { using Execution; + using static Expression; + using static Execution.ExpressionBuilder; using Configuration; using Features; using QueryableExtensions.Impl; @@ -19,20 +21,10 @@ namespace AutoMapper [EditorBrowsable(EditorBrowsableState.Never)] public class TypeMap { + private static readonly MethodInfo CreateProxyMethod = typeof(ObjectFactory).GetStaticMethod(nameof(ObjectFactory.CreateInterfaceProxy)); + private TypeMapDetails _details; private Dictionary _propertyMaps; - private Features _features; - private HashSet _afterMapActions; - private HashSet _beforeMapActions; - private HashSet _includedDerivedTypes; - private HashSet _includedBaseTypes; - private Dictionary _pathMaps; - private Dictionary _sourceMemberConfigs; - private PropertyMap[] _orderedPropertyMaps; private bool _sealed; - private HashSet _inheritedTypeMaps; - private HashSet _includedMembersTypeMaps; - private List _valueTransformerConfigs; - private Type _destinationTypeOverride; public TypeMap(Type sourceType, Type destinationType, ProfileMap profile, ITypeMapConfiguration typeMapConfiguration = null) { Types = new(sourceType, destinationType); @@ -48,35 +40,28 @@ public TypeMap(Type sourceType, Type destinationType, ProfileMap profile, ITypeM { sourceMembers.Clear(); var propertyType = destinationProperty.GetMemberType(); - if (profile.MapDestinationPropertyToSource(SourceTypeDetails, destinationType, propertyType, destinationProperty.Name, sourceMembers, + if (profile.MapDestinationPropertyToSource(SourceTypeDetails, destinationType, propertyType, destinationProperty.Name, sourceMembers, typeMapConfiguration?.IsReverseMap is true)) { AddPropertyMap(destinationProperty, propertyType, sourceMembers); } } } + public Features Features => Details.Features; + private TypeMapDetails Details => _details ??= new(); public void CheckProjection() { if (Projection) { - throw new AutoMapperConfigurationException("CreateProjection works with ProjectTo, not with Map.", QueryMapperHelper.MissingMapException(Types)); + throw new AutoMapperConfigurationException("CreateProjection works with ProjectTo, not with Map.", MissingMapException(Types)); } } - public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, MemberPath path, TypeMap typeMap) - { - _pathMaps ??= new(); - var pathMap = _pathMaps.GetValueOrDefault(path); - if (pathMap == null) - { - pathMap = new(destinationExpression, path, typeMap); - AddPathMap(pathMap); - } - return pathMap; - } - private void AddPathMap(PathMap pathMap) => _pathMaps.Add(pathMap.MemberPath, pathMap); - public Features Features => _features ??= new(); + public static Exception MissingMapException(TypePair types) => MissingMapException(types.SourceType, types.DestinationType); + public static Exception MissingMapException(Type sourceType, Type destinationType) + => new InvalidOperationException($"Missing map from {sourceType} to {destinationType}. Create using CreateMap<{sourceType.Name}, {destinationType.Name}>."); + public bool Projection { get; set; } public LambdaExpression MapExpression { get; private set; } - internal bool CanConstructorMap() => Profile.ConstructorMappingEnabled && !DestinationType.IsAbstract && !ConstructDestinationUsingServiceLocator && + internal bool CanConstructorMap() => Profile.ConstructorMappingEnabled && !DestinationType.IsAbstract && !CustomConstruction && !HasTypeConverter && DestinationConstructors.Length > 0; public TypePair Types; public ConstructorMap ConstructorMap { get; set; } @@ -85,43 +70,46 @@ internal bool CanConstructorMap() => Profile.ConstructorMappingEnabled && !Desti public Type SourceType => Types.SourceType; public Type DestinationType => Types.DestinationType; public ProfileMap Profile { get; } - public LambdaExpression CustomMapFunction { get; set; } - public LambdaExpression CustomMapExpression { get; set; } - public LambdaExpression CustomCtorFunction { get; set; } - public LambdaExpression CustomCtorExpression { get; set; } + public LambdaExpression CustomMapExpression => TypeConverter?.ProjectToExpression; + public LambdaExpression CustomCtorFunction { get => _details?.CustomCtorFunction; set => Details.CustomCtorFunction = value; } + public LambdaExpression CustomCtorExpression => CustomCtorFunction?.Parameters.Count == 1 ? CustomCtorFunction : null; public Type DestinationTypeOverride - { - get => _destinationTypeOverride; + { + get => _details?.DestinationTypeOverride; set { - _destinationTypeOverride = value; + Details.DestinationTypeOverride = value; _sealed = true; } } - public Type DestinationTypeToUse => DestinationTypeOverride ?? DestinationType; - public bool ConstructDestinationUsingServiceLocator { get; set; } - public bool IncludeAllDerivedTypes { get; set; } - public MemberList ConfiguredMemberList { get; set; } - public IReadOnlyCollection IncludedDerivedTypes => _includedDerivedTypes.NullCheck(); - public IReadOnlyCollection IncludedBaseTypes => _includedBaseTypes.NullCheck(); - public IReadOnlyCollection BeforeMapActions => _beforeMapActions.NullCheck(); - public IReadOnlyCollection AfterMapActions => _afterMapActions.NullCheck(); - public IReadOnlyCollection ValueTransformers => _valueTransformerConfigs.NullCheck(); - public bool PreserveReferences { get; set; } - public int MaxDepth { get; set; } - public Type TypeConverterType { get; set; } - public bool DisableConstructorValidation { get; set; } - public IReadOnlyCollection PropertyMaps => _orderedPropertyMaps ?? (_propertyMaps?.Values).NullCheck(); - public IReadOnlyCollection PathMaps => (_pathMaps?.Values).NullCheck(); - public IEnumerable MemberMaps + public bool IncludeAllDerivedTypes { get => (_details?.IncludeAllDerivedTypes).GetValueOrDefault(); set => Details.IncludeAllDerivedTypes = value; } + public MemberList ConfiguredMemberList { - get + get => (_details?.ConfiguredMemberList).GetValueOrDefault(); + set { - IEnumerable maps = PropertyMaps; - if (_pathMaps != null) + if (value == default) { - maps = maps.Concat(_pathMaps.Values); + return; } + Details.ConfiguredMemberList = value; + } + } + public IReadOnlyCollection IncludedDerivedTypes => (_details?.IncludedDerivedTypes).NullCheck(); + public IReadOnlyCollection IncludedBaseTypes => (_details?.IncludedBaseTypes).NullCheck(); + public IReadOnlyCollection BeforeMapActions => (_details?.BeforeMapActions).NullCheck(); + public IReadOnlyCollection AfterMapActions => (_details?.AfterMapActions).NullCheck(); + public IReadOnlyCollection ValueTransformers => (_details?.ValueTransformerConfigs).NullCheck(); + public bool PreserveReferences { get => (_details?.PreserveReferences).GetValueOrDefault(); set => Details.PreserveReferences = value; } + public int MaxDepth { get => (_details?.MaxDepth).GetValueOrDefault(); set => Details.MaxDepth = value; } + public bool DisableConstructorValidation { get => (_details?.DisableConstructorValidation).GetValueOrDefault(); set => Details.DisableConstructorValidation = value; } + public IReadOnlyCollection PropertyMaps => (_propertyMaps?.Values).NullCheck(); + public IReadOnlyCollection PathMaps => (_details?.PathMaps?.Values).NullCheck(); + public IEnumerable MemberMaps + { + get + { + var maps = PropertyMaps.Concat((IReadOnlyCollection)PathMaps); if (ConstructorMapping) { maps = maps.Concat(ConstructorMap.CtorParams); @@ -129,44 +117,35 @@ public IEnumerable MemberMaps return maps; } } - public bool? IsValid { get; set; } - public bool AsProxy { get; set; } public bool PassesCtorValidation => DisableConstructorValidation || CustomConstruction - || ConstructDestinationUsingServiceLocator || ConstructorMapping - || DestinationTypeToUse.IsAbstract - || DestinationTypeToUse.IsGenericTypeDefinition - || DestinationTypeToUse.IsValueType + || DestinationType.IsAbstract + || DestinationType.IsGenericTypeDefinition + || DestinationType.IsValueType || TypeDetails.GetConstructors(DestinationType, Profile).Any(c => c.AllParametersOptional()); public MemberInfo[] DestinationSetters => DestinationTypeDetails.WriteAccessors; public ConstructorParameters[] DestinationConstructors => DestinationTypeDetails.Constructors; public bool ConstructorMapping => ConstructorMap is { CanResolve: true }; - public bool CustomConstruction => (CustomCtorExpression ?? CustomCtorFunction) != null; - public bool HasTypeConverter => (CustomMapFunction ?? CustomMapExpression ?? (object)TypeConverterType) != null; - public bool ShouldCheckForValid => - !HasTypeConverter - && DestinationTypeOverride == null - && ConfiguredMemberList != MemberList.None - && !(IsValid ?? false); - public LambdaExpression[] IncludedMembers { get; internal set; } = Array.Empty(); - public string[] IncludedMembersNames { get; internal set; } = Array.Empty(); - public IReadOnlyCollection IncludedMembersTypeMaps => _includedMembersTypeMaps.NullCheck(); + public bool CustomConstruction => CustomCtorFunction != null; + public bool HasTypeConverter => TypeConverter != null; + public TypeConverter TypeConverter { get; set; } + public bool ShouldCheckForValid => !HasTypeConverter && DestinationTypeOverride == null && ConfiguredMemberList != MemberList.None; + public LambdaExpression[] IncludedMembers { get => _details?.IncludedMembers ?? Array.Empty(); set => Details.IncludedMembers = value; } + public string[] IncludedMembersNames { get => _details?.IncludedMembersNames ?? Array.Empty(); set => Details.IncludedMembersNames = value; } + public IReadOnlyCollection IncludedMembersTypeMaps => (_details?.IncludedMembersTypeMaps).NullCheck(); public Type MakeGenericType(Type type) => type.IsGenericTypeDefinition ? type.MakeGenericType(SourceType.GenericTypeArguments.Concat(DestinationType.GenericTypeArguments).Take(type.GenericParametersCount()).ToArray()) : type; public bool HasIncludedMembers => IncludedMembers.Length > 0 || IncludedMembersNames.Length > 0; - public IEnumerable GetAllIncludedMembers() => IncludedMembers.Concat(GetUntypedIncludedMembers()); - private IEnumerable GetUntypedIncludedMembers() => - SourceType.IsGenericTypeDefinition ? - Array.Empty() : - IncludedMembersNames.Select(name => ExpressionBuilder.MemberAccessLambda(SourceType, name)); + public IEnumerable GetAllIncludedMembers() => IncludedMembersNames.Length == 0 || SourceType.ContainsGenericParameters ? + IncludedMembers : IncludedMembers.Concat(IncludedMembersNames.Select(name => MemberAccessLambda(SourceType, name))); public bool ConstructorParameterMatches(string destinationPropertyName) => ConstructorMapping && ConstructorMap[destinationPropertyName] != null; public void AddPropertyMap(MemberInfo destProperty, Type destinationPropertyType, IEnumerable sourceMembers) { var propertyMap = new PropertyMap(destProperty, destinationPropertyType, this); - propertyMap.MapByConvention(sourceMembers); + propertyMap.MapByConvention(sourceMembers.ToArray()); AddPropertyMap(propertyMap); } private void AddPropertyMap(PropertyMap propertyMap) @@ -176,41 +155,30 @@ private void AddPropertyMap(PropertyMap propertyMap) } public string[] GetUnmappedPropertyNames() { - var autoMappedProperties = GetPropertyNames(PropertyMaps); IEnumerable properties; if (ConfiguredMemberList == MemberList.Destination) { properties = Profile.CreateTypeDetails(DestinationType).WriteAccessors .Select(p => p.Name) .Where(p => !ConstructorParameterMatches(p)) - .Except(autoMappedProperties) + .Except(MappedMembers().Select(m => m.DestinationName)) .Except(PathMaps.Select(p => p.MemberPath.First.Name)); } else { - var redirectedSourceMembers = MemberMaps - .Where(pm => pm.IsMapped && pm.SourceMember != null && pm.SourceMember.Name != pm.DestinationName) - .Select(pm => pm.SourceMember.Name); - - var ignoredSourceMembers = _sourceMemberConfigs?.Values + var ignoredSourceMembers = _details?.SourceMemberConfigs?.Values .Where(smc => smc.IsIgnored()) .Select(pm => pm.SourceMember.Name); - properties = Profile.CreateTypeDetails(SourceType).ReadAccessors .Select(p => p.Name) - .Except(autoMappedProperties) - .Except(redirectedSourceMembers) + .Except(MappedMembers().Select(m => m.GetSourceMemberName())) + .Except(IncludedMembersNames) + .Except(IncludedMembers.Select(m => m.GetMember()?.Name)) .Except(ignoredSourceMembers ?? Array.Empty()); } return properties.Where(memberName => !Profile.GlobalIgnores.Any(memberName.StartsWith)).ToArray(); - string GetPropertyName(PropertyMap pm) => ConfiguredMemberList == MemberList.Destination - ? pm.DestinationName - : pm.SourceMembers.Length > 1 - ? pm.SourceMembers[0].Name - : pm.SourceMember?.Name ?? pm.DestinationName; - string[] GetPropertyNames(IEnumerable propertyMaps) => propertyMaps.Where(pm => pm.IsMapped).Select(GetPropertyName).ToArray(); + IEnumerable MappedMembers() => MemberMaps.Where(pm => pm.IsMapped); } - public PropertyMap FindOrCreatePropertyMapFor(MemberInfo destinationProperty, Type destinationPropertyType) { var propertyMap = GetPropertyMap(destinationProperty.Name); @@ -222,12 +190,6 @@ public PropertyMap FindOrCreatePropertyMapFor(MemberInfo destinationProperty, Ty return propertyMap; } public TypePair AsPair() => new(SourceType, DestinationTypeOverride); - public void IncludeDerivedTypes(TypePair derivedTypes) - { - CheckDifferent(derivedTypes); - _includedDerivedTypes ??= new(); - _includedDerivedTypes.Add(derivedTypes); - } private void CheckDifferent(TypePair types) { if (types == Types) @@ -235,12 +197,6 @@ private void CheckDifferent(TypePair types) throw new InvalidOperationException($"You cannot include a type map into itself.{Environment.NewLine}Source type: {types.SourceType.FullName}{Environment.NewLine}Destination type: {types.DestinationType.FullName}"); } } - public void IncludeBaseTypes(TypePair baseTypes) - { - CheckDifferent(baseTypes); - _includedBaseTypes ??= new(); - _includedBaseTypes.Add(baseTypes); - } internal void IgnorePaths(MemberInfo destinationMember) { foreach (var pathMap in PathMaps.Where(pm => pm.MemberPath.First == destinationMember)) @@ -248,23 +204,7 @@ internal void IgnorePaths(MemberInfo destinationMember) pathMap.Ignored = true; } } - public bool HasDerivedTypesToInclude => _includedDerivedTypes?.Count > 0 || DestinationTypeOverride != null; - public bool Projection { get; set; } - public void AddBeforeMapAction(LambdaExpression beforeMap) - { - _beforeMapActions ??= new(); - _beforeMapActions.Add(beforeMap); - } - public void AddAfterMapAction(LambdaExpression afterMap) - { - _afterMapActions ??= new(); - _afterMapActions.Add(afterMap); - } - public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) - { - _valueTransformerConfigs ??= new(); - _valueTransformerConfigs.Add(valueTransformerConfiguration); - } + public bool HasDerivedTypesToInclude => IncludedDerivedTypes.Count > 0; public void Seal(IGlobalConfiguration configurationProvider, HashSet typeMapsPath) { if (_sealed) @@ -272,45 +212,24 @@ public void Seal(IGlobalConfiguration configurationProvider, HashSet ty return; } _sealed = true; - if (_inheritedTypeMaps != null) - { - foreach (var inheritedTypeMap in _inheritedTypeMaps) - { - if (inheritedTypeMap._includedMembersTypeMaps != null) - { - _includedMembersTypeMaps ??= new(); - _includedMembersTypeMaps.UnionWith(inheritedTypeMap._includedMembersTypeMaps); - } - } - } - if (_includedMembersTypeMaps != null) - { - foreach (var includedMemberTypeMap in _includedMembersTypeMaps) - { - includedMemberTypeMap.TypeMap.Seal(configurationProvider, typeMapsPath); - ApplyIncludedMemberTypeMap(includedMemberTypeMap); - } - } - if (_inheritedTypeMaps != null) - { - foreach (var inheritedTypeMap in _inheritedTypeMaps) - { - ApplyInheritedTypeMap(inheritedTypeMap); - } - } + _details?.Seal(configurationProvider, this, typeMapsPath); if (!Projection) { - if (HasMappingOrder()) - { - _orderedPropertyMaps = PropertyMaps.OrderBy(map => map.MappingOrder).ToArray(); - _propertyMaps.Clear(); - } MapExpression = CreateMapperLambda(configurationProvider, typeMapsPath); } - _features?.Seal(configurationProvider); SourceTypeDetails = null; DestinationTypeDetails = null; - return; + } + public IEnumerable OrderedPropertyMaps() + { + if (HasMappingOrder()) + { + return PropertyMaps.OrderBy(map => map.MappingOrder); + } + else + { + return PropertyMaps; + } bool HasMappingOrder() { if (_propertyMaps == null) @@ -327,152 +246,271 @@ bool HasMappingOrder() return false; } } - internal LambdaExpression CreateMapperLambda(IGlobalConfiguration configurationProvider, HashSet typeMapsPath) => - Types.IsGenericTypeDefinition ? null : new TypeMapPlanBuilder(configurationProvider, this).CreateMapperLambda(typeMapsPath); - private PropertyMap GetPropertyMap(string name) => _propertyMaps?.GetValueOrDefault(name); - private PropertyMap GetPropertyMap(PropertyMap propertyMap) => GetPropertyMap(propertyMap.DestinationName); - public bool AddMemberMap(IncludedMember includedMember) + public void IncludeDerivedTypes(TypePair derivedTypes) { - _includedMembersTypeMaps ??= new(); - return _includedMembersTypeMaps.Add(includedMember); + CheckDifferent(derivedTypes); + Details.IncludeDerivedTypes(derivedTypes); } - public SourceMemberConfig FindOrCreateSourceMemberConfigFor(MemberInfo sourceMember) + public void IncludeBaseTypes(TypePair baseTypes) { - _sourceMemberConfigs ??= new(); - var config = _sourceMemberConfigs.GetValueOrDefault(sourceMember); - - if (config != null) return config; - - config = new(sourceMember); - _sourceMemberConfigs.Add(config.SourceMember, config); - return config; + CheckDifferent(baseTypes); + Details.IncludeBaseTypes(baseTypes); } - public bool AddInheritedMap(TypeMap inheritedTypeMap) + public void AddBeforeMapAction(LambdaExpression beforeMap) => Details.AddBeforeMapAction(beforeMap); + public void AddAfterMapAction(LambdaExpression afterMap) => Details.AddAfterMapAction(afterMap); + public void AddValueTransformation(ValueTransformerConfiguration config) => Details.AddValueTransformation(config); + public void ConstructUsingServiceLocator() => CustomCtorFunction = Lambda(ServiceLocator(DestinationType)); + internal LambdaExpression CreateMapperLambda(IGlobalConfiguration configurationProvider, HashSet typeMapsPath) => + Types.IsGenericTypeDefinition ? null : new TypeMapPlanBuilder(configurationProvider, this).CreateMapperLambda(typeMapsPath); + private PropertyMap GetPropertyMap(string name) => _propertyMaps?.GetValueOrDefault(name); + private PropertyMap GetPropertyMap(PropertyMap propertyMap) => GetPropertyMap(propertyMap.DestinationName); + public void AsProxy() => CustomCtorFunction = Lambda(Call(CreateProxyMethod, Constant(DestinationType))); + internal void CopyInheritedMapsTo(TypeMap typeMap) { - _inheritedTypeMaps ??= new(); - return _inheritedTypeMaps.Add(inheritedTypeMap); + if (_details?.InheritedTypeMaps == null) + { + return; + } + _details.CopyInheritedMapsTo(typeMap); } - private void ApplyIncludedMemberTypeMap(IncludedMember includedMember) + public void CloseGenerics(ITypeMapConfiguration openMapConfig, TypePair closedTypes) { - var typeMap = includedMember.TypeMap; - var includedMemberMaps = typeMap.PropertyMaps. - Where(m => m.CanResolveValue && GetPropertyMap(m) == null) - .Select(p => new PropertyMap(p, this, includedMember)) - .ToArray(); - var notOverridenPathMaps = NotOverridenPathMaps(typeMap); - var appliedConstructorMap = ConstructorMap?.ApplyIncludedMember(includedMember); - if (includedMemberMaps.Length == 0 && notOverridenPathMaps.Length == 0 && appliedConstructorMap is not true) + TypeConverter?.CloseGenerics(openMapConfig, closedTypes); + if (DestinationTypeOverride is { IsGenericTypeDefinition: true }) { - return; + var neededParameters = DestinationTypeOverride.GenericParametersCount(); + DestinationTypeOverride = DestinationTypeOverride.MakeGenericType(closedTypes.DestinationType.GenericTypeArguments.Take(neededParameters).ToArray()); } - foreach (var includedMemberMap in includedMemberMaps) + } + public bool AddMemberMap(IncludedMember includedMember) => Details.AddMemberMap(includedMember); + public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, MemberPath path, TypeMap typeMap) => + Details.FindOrCreatePathMapFor(destinationExpression, path, typeMap); + public bool AddInheritedMap(TypeMap inheritedTypeMap) => Details.AddInheritedMap(inheritedTypeMap); + public SourceMemberConfig FindOrCreateSourceMemberConfigFor(MemberInfo sourceMember) => Details.FindOrCreateSourceMemberConfigFor(sourceMember); + class TypeMapDetails + { + Features _features; + public bool PreserveReferences; + public LambdaExpression[] IncludedMembers; + public string[] IncludedMembersNames; + public bool DisableConstructorValidation; + public int MaxDepth; + public bool IncludeAllDerivedTypes; + public LambdaExpression CustomCtorFunction; + public MemberList ConfiguredMemberList; + public Type DestinationTypeOverride; + public HashSet AfterMapActions { get; private set; } + public HashSet BeforeMapActions { get; private set; } + public HashSet IncludedDerivedTypes { get; private set; } + public HashSet IncludedBaseTypes { get; private set; } + public Dictionary PathMaps { get; private set; } + public Dictionary SourceMemberConfigs { get; private set; } + public HashSet InheritedTypeMaps { get; private set; } + public HashSet IncludedMembersTypeMaps { get; private set; } + public List ValueTransformerConfigs { get; private set; } + public Features Features => _features ??= new(); + public void Seal(IGlobalConfiguration configurationProvider, TypeMap thisMap, HashSet typeMapsPath) { - AddPropertyMap(includedMemberMap); - foreach (var transformer in typeMap.ValueTransformers) + if (InheritedTypeMaps != null) + { + foreach (var inheritedTypeMap in InheritedTypeMaps) + { + var includedMaps = inheritedTypeMap?._details?.IncludedMembersTypeMaps; + if (includedMaps != null) + { + IncludedMembersTypeMaps ??= new(); + IncludedMembersTypeMaps.UnionWith(includedMaps); + } + } + } + if (IncludedMembersTypeMaps != null) + { + foreach (var includedMemberTypeMap in IncludedMembersTypeMaps) + { + includedMemberTypeMap.TypeMap.Seal(configurationProvider, typeMapsPath); + ApplyIncludedMemberTypeMap(includedMemberTypeMap, thisMap); + } + } + if (InheritedTypeMaps != null) { - includedMemberMap.AddValueTransformation(transformer); + foreach (var inheritedTypeMap in InheritedTypeMaps) + { + ApplyInheritedTypeMap(inheritedTypeMap, thisMap); + } } + _features?.Seal(configurationProvider); } - ApplyIncludedMemberActions(includedMember, typeMap); - foreach (var notOverridenPathMap in notOverridenPathMaps) + public void IncludeDerivedTypes(TypePair derivedTypes) { - AddPathMap(new(notOverridenPathMap, this, includedMember) { CustomMapExpression = notOverridenPathMap.CustomMapExpression }); + IncludedDerivedTypes ??= new(); + IncludedDerivedTypes.Add(derivedTypes); } - return; - void ApplyIncludedMemberActions(IncludedMember includedMember, TypeMap typeMap) + public void AddBeforeMapAction(LambdaExpression beforeMap) { - if (typeMap._beforeMapActions != null) - { - _beforeMapActions ??= new(); - _beforeMapActions.UnionWith(typeMap._beforeMapActions.Select(includedMember.Chain)); - } - if (typeMap._afterMapActions != null) + BeforeMapActions ??= new(); + BeforeMapActions.Add(beforeMap); + } + public void AddAfterMapAction(LambdaExpression afterMap) + { + AfterMapActions ??= new(); + AfterMapActions.Add(afterMap); + } + public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) + { + ValueTransformerConfigs ??= new(); + ValueTransformerConfigs.Add(valueTransformerConfiguration); + } + public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, MemberPath path, TypeMap typeMap) + { + PathMaps ??= new(); + var pathMap = PathMaps.GetValueOrDefault(path); + if (pathMap == null) { - _afterMapActions ??= new(); - _afterMapActions.UnionWith(typeMap._afterMapActions.Select(includedMember.Chain)); + pathMap = new(destinationExpression, path, typeMap); + AddPathMap(pathMap); } + return pathMap; } - } - private void ApplyInheritedTypeMap(TypeMap inheritedTypeMap) - { - if (inheritedTypeMap._propertyMaps != null) + private void AddPathMap(PathMap pathMap) => PathMaps.Add(pathMap.MemberPath, pathMap); + public void IncludeBaseTypes(TypePair baseTypes) { - ApplyInheritedPropertyMaps(inheritedTypeMap); + IncludedBaseTypes ??= new(); + IncludedBaseTypes.Add(baseTypes); } - ApplyInheritedMapActions(inheritedTypeMap); - if (inheritedTypeMap._sourceMemberConfigs != null) + internal void CopyInheritedMapsTo(TypeMap typeMap) { - ApplyInheritedSourceMembers(inheritedTypeMap); + typeMap.Details.InheritedTypeMaps ??= new(); + typeMap._details.InheritedTypeMaps.UnionWith(InheritedTypeMaps); } - var notOverridenPathMaps = NotOverridenPathMaps(inheritedTypeMap); - foreach (var notOverridenPathMap in notOverridenPathMaps) + public bool AddMemberMap(IncludedMember includedMember) { - AddPathMap(notOverridenPathMap); + IncludedMembersTypeMaps ??= new(); + return IncludedMembersTypeMaps.Add(includedMember); + } + public SourceMemberConfig FindOrCreateSourceMemberConfigFor(MemberInfo sourceMember) + { + SourceMemberConfigs ??= new(); + var config = SourceMemberConfigs.GetValueOrDefault(sourceMember); + + if (config != null) return config; + + config = new(sourceMember); + SourceMemberConfigs.Add(config.SourceMember, config); + return config; } - if (inheritedTypeMap._valueTransformerConfigs != null) + public bool AddInheritedMap(TypeMap inheritedTypeMap) { - _valueTransformerConfigs ??= new(); - _valueTransformerConfigs.InsertRange(0, inheritedTypeMap._valueTransformerConfigs); + InheritedTypeMaps ??= new(); + return InheritedTypeMaps.Add(inheritedTypeMap); } - return; - void ApplyInheritedPropertyMaps(TypeMap inheritedTypeMap) + private void ApplyIncludedMemberTypeMap(IncludedMember includedMember, TypeMap thisMap) { - foreach (var inheritedMappedProperty in inheritedTypeMap._propertyMaps.Values) + var typeMap = includedMember.TypeMap; + var includedMemberMaps = typeMap.PropertyMaps. + Where(m => m.CanResolveValue && thisMap.GetPropertyMap(m) == null) + .Select(p => new PropertyMap(p, thisMap, includedMember)) + .ToArray(); + var notOverridenPathMaps = NotOverridenPathMaps(typeMap); + var appliedConstructorMap = thisMap.ConstructorMap?.ApplyIncludedMember(includedMember); + if (includedMemberMaps.Length == 0 && notOverridenPathMaps.Length == 0 && appliedConstructorMap is not true) { - if (!inheritedMappedProperty.IsMapped) + return; + } + foreach (var includedMemberMap in includedMemberMaps) + { + thisMap.AddPropertyMap(includedMemberMap); + foreach (var transformer in typeMap.ValueTransformers) { - continue; + includedMemberMap.AddValueTransformation(transformer); } - var conventionPropertyMap = GetPropertyMap(inheritedMappedProperty); - if (conventionPropertyMap != null) + } + var details = typeMap._details; + if (details != null) + { + ApplyInheritedMapActions(details.BeforeMapActions?.Select(includedMember.Chain), details.AfterMapActions?.Select(includedMember.Chain)); + } + foreach (var notOverridenPathMap in notOverridenPathMaps) + { + AddPathMap(new(notOverridenPathMap, thisMap, includedMember)); + } + } + private void ApplyInheritedTypeMap(TypeMap inheritedTypeMap, TypeMap thisMap) + { + if (inheritedTypeMap._propertyMaps != null) + { + ApplyInheritedPropertyMaps(inheritedTypeMap, thisMap); + } + var inheritedDetails = inheritedTypeMap._details; + if (inheritedDetails == null) + { + return; + } + ApplyInheritedMapActions(inheritedDetails.BeforeMapActions, inheritedDetails.AfterMapActions); + if (inheritedDetails.SourceMemberConfigs != null) + { + ApplyInheritedSourceMembers(inheritedTypeMap._details); + } + var notOverridenPathMaps = NotOverridenPathMaps(inheritedTypeMap); + foreach (var notOverridenPathMap in notOverridenPathMaps) + { + AddPathMap(notOverridenPathMap); + } + if (inheritedDetails.ValueTransformerConfigs != null) + { + ValueTransformerConfigs ??= new(); + ValueTransformerConfigs.InsertRange(0, inheritedDetails.ValueTransformerConfigs); + } + return; + void ApplyInheritedPropertyMaps(TypeMap inheritedTypeMap, TypeMap thisMap) + { + foreach (var inheritedMappedProperty in inheritedTypeMap._propertyMaps.Values) { - conventionPropertyMap.ApplyInheritedPropertyMap(inheritedMappedProperty); + if (!inheritedMappedProperty.IsMapped) + { + continue; + } + var conventionPropertyMap = thisMap.GetPropertyMap(inheritedMappedProperty); + if (conventionPropertyMap != null) + { + conventionPropertyMap.ApplyInheritedPropertyMap(inheritedMappedProperty); + } + else + { + thisMap.AddPropertyMap(new(inheritedMappedProperty, thisMap)); + } } - else + } + void ApplyInheritedSourceMembers(TypeMapDetails inheritedTypeMap) + { + SourceMemberConfigs ??= new(); + foreach (var inheritedSourceConfig in inheritedTypeMap.SourceMemberConfigs.Values) { - AddPropertyMap(new(inheritedMappedProperty, this)); + SourceMemberConfigs.TryAdd(inheritedSourceConfig.SourceMember, inheritedSourceConfig); } } } - void ApplyInheritedMapActions(TypeMap inheritedTypeMap) + void ApplyInheritedMapActions(IEnumerable beforeMap, IEnumerable afterMap) { - if (inheritedTypeMap._beforeMapActions != null) + if (beforeMap != null) { - _beforeMapActions ??= new(); - _beforeMapActions.UnionWith(inheritedTypeMap._beforeMapActions); + BeforeMapActions ??= new(); + BeforeMapActions.UnionWith(beforeMap); } - if (inheritedTypeMap._afterMapActions != null) + if (afterMap != null) { - _afterMapActions ??= new(); - _afterMapActions.UnionWith(inheritedTypeMap._afterMapActions); + AfterMapActions ??= new(); + AfterMapActions.UnionWith(afterMap); } } - void ApplyInheritedSourceMembers(TypeMap inheritedTypeMap) + private PathMap[] NotOverridenPathMaps(TypeMap inheritedTypeMap) { - _sourceMemberConfigs ??= new(); - foreach (var inheritedSourceConfig in inheritedTypeMap._sourceMemberConfigs.Values) + if (inheritedTypeMap.PathMaps.Count == 0) { - _sourceMemberConfigs.TryAdd(inheritedSourceConfig.SourceMember, inheritedSourceConfig); + return Array.Empty(); } + PathMaps ??= new(); + return inheritedTypeMap.PathMaps.Where(baseConfig => !PathMaps.ContainsKey(baseConfig.MemberPath)).ToArray(); } } - private PathMap[] NotOverridenPathMaps(TypeMap inheritedTypeMap) - { - if (inheritedTypeMap.PathMaps.Count == 0) - { - return Array.Empty(); - } - _pathMaps ??= new(); - return inheritedTypeMap.PathMaps.Where(baseConfig => !_pathMaps.ContainsKey(baseConfig.MemberPath)).ToArray(); - } - internal void CopyInheritedMapsTo(TypeMap typeMap) - { - if (_inheritedTypeMaps == null) - { - return; - } - typeMap._inheritedTypeMaps ??= new(); - typeMap._inheritedTypeMaps.UnionWith(_inheritedTypeMaps); - } } } \ No newline at end of file diff --git a/src/IntegrationTests/ConstructorDefaultValue.cs b/src/IntegrationTests/ConstructorDefaultValue.cs new file mode 100644 index 0000000000..303d93e88d --- /dev/null +++ b/src/IntegrationTests/ConstructorDefaultValue.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using Shouldly; +using System.Linq; +using Xunit; +namespace AutoMapper.IntegrationTests; +public class ConstructorDefaultValue : IntegrationTest +{ + public class Customer + { + public int Id { get; set; } + } + public class CustomerViewModel + { + public CustomerViewModel(int value = 5) => Value = value; + public int Value { get; set; } + } + public class Context : LocalDbContext + { + public DbSet Customers { get; set; } + } + public class DatabaseInitializer : DropCreateDatabaseAlways + { + protected override void Seed(Context context) + { + context.Customers.Add(new Customer()); + base.Seed(context); + } + } + protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.CreateProjection()); + [Fact] + public void Can_map_with_projection() + { + using var context = new Context(); + ProjectTo(context.Customers).Single().Value.ShouldBe(5); + } +} \ No newline at end of file diff --git a/src/UnitTests/AutoMapperSpecBase.cs b/src/UnitTests/AutoMapperSpecBase.cs index b465d2e7c8..5e83c73734 100644 --- a/src/UnitTests/AutoMapperSpecBase.cs +++ b/src/UnitTests/AutoMapperSpecBase.cs @@ -43,6 +43,7 @@ public static void AddIgnoreMapAttribute(this IMapperConfigurationExpression con public abstract class AutoMapperSpecBase : NonValidatingSpecBase { protected override void OnConfig(MapperConfiguration mapperConfiguration) => mapperConfiguration.AssertConfigurationIsValid(); + protected override void AssertConfigurationIsValid() => Configuration.Internal(); } public abstract class NonValidatingSpecBase : SpecBase @@ -60,7 +61,7 @@ IMapper CreateMapper() protected virtual void OnConfig(MapperConfiguration mapperConfiguration) { } protected TDestination Map(object source) => Mapper.Map(source); protected TypeMap FindTypeMapFor() => Configuration.FindTypeMapFor(); - protected void AssertConfigurationIsValid() => Configuration.AssertConfigurationIsValid(); + protected virtual void AssertConfigurationIsValid() => Configuration.AssertConfigurationIsValid(); protected void AssertConfigurationIsValid() => Configuration.AssertConfigurationIsValid(Configuration.FindTypeMapFor()); protected void AssertConfigurationIsValid(Type sourceType, Type destinationType) => Configuration.AssertConfigurationIsValid(Configuration.FindTypeMapFor(sourceType, destinationType)); public void AssertConfigurationIsValid(string profileName) => Configuration.AssertConfigurationIsValid(profileName); diff --git a/src/UnitTests/BasicFlattening.cs b/src/UnitTests/BasicFlattening.cs index bdb7acd002..b972e6f77c 100644 --- a/src/UnitTests/BasicFlattening.cs +++ b/src/UnitTests/BasicFlattening.cs @@ -2,7 +2,8 @@ using System.Collections.Generic; using Xunit; using Shouldly; - +using System.Linq; + namespace AutoMapper.UnitTests { public class BasicFlattening : AutoMapperSpecBase @@ -232,6 +233,6 @@ public class CustomerDTO protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.CreateMap(MemberList.Source).ForMember(d=>d.Id, o=>o.MapFrom(s=>s.AnotherId))); [Fact] public void Should_validate() => - new Action(AssertConfigurationIsValid).ShouldThrowException(ex => ex.Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Address.Id))); + new Action(AssertConfigurationIsValid).ShouldThrow().Errors.Single().UnmappedPropertyNames.Single().ShouldBe(nameof(Address.Id)); } } \ No newline at end of file diff --git a/src/UnitTests/Bug/AssertConfigurationIsValidNullables.cs b/src/UnitTests/Bug/AssertConfigurationIsValidNullables.cs index 70ec4e1eb0..514a731588 100644 --- a/src/UnitTests/Bug/AssertConfigurationIsValidNullables.cs +++ b/src/UnitTests/Bug/AssertConfigurationIsValidNullables.cs @@ -19,5 +19,7 @@ class Destination { cfg.CreateMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Bug/CaseSensitivityBug.cs b/src/UnitTests/Bug/CaseSensitivityBug.cs index d58cf51286..7476b43293 100644 --- a/src/UnitTests/Bug/CaseSensitivityBug.cs +++ b/src/UnitTests/Bug/CaseSensitivityBug.cs @@ -17,5 +17,7 @@ public class Bar { public int id { get; set; } } + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Bug/DuplicateExtensionMethods.cs b/src/UnitTests/Bug/DuplicateExtensionMethods.cs index 4ed882b352..79804c44e6 100644 --- a/src/UnitTests/Bug/DuplicateExtensionMethods.cs +++ b/src/UnitTests/Bug/DuplicateExtensionMethods.cs @@ -38,5 +38,7 @@ class Destination { cfg.CreateMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Bug/InitializeNRE.cs b/src/UnitTests/Bug/InitializeNRE.cs index 820c6789f9..0c4fec30f9 100644 --- a/src/UnitTests/Bug/InitializeNRE.cs +++ b/src/UnitTests/Bug/InitializeNRE.cs @@ -33,6 +33,8 @@ public class Destination cfg.ConstructServicesUsing(t => new Res()); cfg.CreateMap().ForMember(d => d.Value, o => o.MapFrom()); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } @@ -60,5 +62,7 @@ public class TestViewModel { cfg.CreateMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Bug/JsonNet.cs b/src/UnitTests/Bug/JsonNet.cs index 1a3608b0db..2c0e2707d0 100644 --- a/src/UnitTests/Bug/JsonNet.cs +++ b/src/UnitTests/Bug/JsonNet.cs @@ -190,5 +190,7 @@ class Destination { cfg.CreateMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Bug/MapFromClosureBug.cs b/src/UnitTests/Bug/MapFromClosureBug.cs index faa89e6fdf..9d6e7a95c8 100644 --- a/src/UnitTests/Bug/MapFromClosureBug.cs +++ b/src/UnitTests/Bug/MapFromClosureBug.cs @@ -2,6 +2,7 @@ { using System; using Shouldly; + using Xunit; public class MapFromClosureBug : SpecBaseBase { @@ -45,7 +46,7 @@ public class BookingDto { public int? Total { get; set; } } - + [Fact] public void Should_map_successfully() { var mapperConfiguration = new MapperConfiguration(cfg => @@ -66,6 +67,5 @@ public void Should_map_successfully() // Assert dto.ShouldNotBeNull(); } - } } \ No newline at end of file diff --git a/src/UnitTests/Bug/NullableDateTime.cs b/src/UnitTests/Bug/NullableDateTime.cs index 9ba6881910..cc50123ddc 100644 --- a/src/UnitTests/Bug/NullableDateTime.cs +++ b/src/UnitTests/Bug/NullableDateTime.cs @@ -32,6 +32,8 @@ public class Destination opt.MapFrom(src => src.Bars.Min(b => b.Bar)); }); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class FromDateToNullableDateTime : AutoMapperSpecBase diff --git a/src/UnitTests/CollectionMapping.cs b/src/UnitTests/CollectionMapping.cs index 73e2367f45..a8144e4ede 100644 --- a/src/UnitTests/CollectionMapping.cs +++ b/src/UnitTests/CollectionMapping.cs @@ -702,6 +702,8 @@ public class SpecificDestinationItem : DestinationItemBase cfg.CreateMap(); cfg.CreateMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_passing_a_not_empty_collection : AutoMapperSpecBase diff --git a/src/UnitTests/ConditionalMapping.cs b/src/UnitTests/ConditionalMapping.cs index 26b5057a54..992b30c431 100644 --- a/src/UnitTests/ConditionalMapping.cs +++ b/src/UnitTests/ConditionalMapping.cs @@ -58,6 +58,8 @@ class DestinationClass : Interface public int PublicProperty { get; set; } } + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_configuring_a_member_to_skip_based_on_the_property_value : AutoMapperSpecBase diff --git a/src/UnitTests/ConfigurationValidation.cs b/src/UnitTests/ConfigurationValidation.cs index 0e5396baab..22df1fcf97 100644 --- a/src/UnitTests/ConfigurationValidation.cs +++ b/src/UnitTests/ConfigurationValidation.cs @@ -51,6 +51,8 @@ public class B public class C { } protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.CreateMap().ConvertUsing(x => new B { Foo = new C() })); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_using_a_type_converter_class : AutoMapperSpecBase @@ -71,6 +73,8 @@ class Converter : ITypeConverter { public B Convert(A source, B dest, ResolutionContext context) => new B { Foo = new C() }; } + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_skipping_validation : NonValidatingSpecBase @@ -283,6 +287,49 @@ public void Should_fail_a_configuration_check() { typeof(AutoMapperConfigurationException).ShouldBeThrownBy(AssertConfigurationIsValid); } + } + + public class ResolversWithSourceValidation : AutoMapperSpecBase + { + class Source + { + public int Resolved { get; set; } + public int TypedResolved { get; set; } + public int Converted { get; set; } + public int TypedConverted { get; set; } + } + class Destination + { + public int ResolvedDest { get; set; } + public int TypedResolvedDest { get; set; } + public int ConvertedDest { get; set; } + public int TypedConvertedDest { get; set; } + } + class MemberResolver : IMemberValueResolver + { + public int Resolve(Source source, Destination destination, int sourceMember, int destinationMember, ResolutionContext context) => 5; + } + class ValueConverter : IValueConverter + { + public int Convert(int sourceMember, ResolutionContext context) => 5; + } + protected override MapperConfiguration CreateConfiguration() => new(cfg => + { + cfg.CreateMap(MemberList.Source) + .ForMember(d => d.ResolvedDest, o => o.MapFrom("Resolved")) + .ForMember(d=>d.TypedResolvedDest, o => o.MapFrom(s => s.TypedResolved)) + .ForMember(d => d.ConvertedDest, o => o.ConvertUsing("Converted")) + .ForMember(d => d.TypedConvertedDest, o => o.ConvertUsing(s => s.TypedConverted)); + }); + [Fact] + public void Should_work() + { + var result = Mapper.Map(new Source()); + result.ResolvedDest.ShouldBe(5); + result.TypedResolvedDest.ShouldBe(5); + result.ConvertedDest.ShouldBe(5); + result.TypedConvertedDest.ShouldBe(5); + } } public class NonMemberExpressionWithSourceValidation : NonValidatingSpecBase @@ -301,6 +348,23 @@ class Destination public void Should_be_ignored() => new Action(AssertConfigurationIsValid) .ShouldThrow().Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Source.Value)); } + + public class MatchingNonMemberExpressionWithSourceValidation : NonValidatingSpecBase + { + class Source + { + public string Value { get; set; } + } + class Destination + { + public string Value { get; set; } + } + protected override MapperConfiguration CreateConfiguration() => new(c => c.CreateMap(MemberList.Source) + .ForMember(d => d.Value, o => o.MapFrom(s => s.Value ?? ""))); + [Fact] + public void Should_be_ignored() => new Action(AssertConfigurationIsValid) + .ShouldThrow().Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Source.Value)); + } public class When_testing_a_dto_with_fully_mapped_and_custom_matchers : AutoMapperSpecBase { @@ -321,6 +385,8 @@ public class ModelDto cfg.CreateMap() .ForMember(dto => dto.Bar, opt => opt.MapFrom(m => m.Barr)); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_testing_a_dto_with_matching_member_names_but_mismatched_types : NonValidatingSpecBase @@ -732,6 +798,8 @@ public class Query public class Command { public List> Details { get; private set; } - } + } + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Constructors.cs b/src/UnitTests/Constructors.cs index 1bd85ffee9..866272da5f 100644 --- a/src/UnitTests/Constructors.cs +++ b/src/UnitTests/Constructors.cs @@ -22,6 +22,8 @@ public Destination(int otherValue, int value = 2) { } } protected override MapperConfiguration CreateConfiguration() => new(c => c.CreateMap().ForCtorParam("otherValue", o=>o.MapFrom(s=>0))); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class Nullable_enum_default_value : AutoMapperSpecBase { diff --git a/src/UnitTests/ContextItems.cs b/src/UnitTests/ContextItems.cs index db0d590468..c8e54db0a8 100644 --- a/src/UnitTests/ContextItems.cs +++ b/src/UnitTests/ContextItems.cs @@ -25,7 +25,7 @@ public class ContextResolver : IMemberValueResolver { public int Resolve(Source src, Dest d, int source, int dest, ResolutionContext context) { - return source + (int)context.Options.Items["Item"]; + return source + (int)context.Items["Item"]; } } @@ -69,7 +69,7 @@ public void Should_report_error() { var inner = ex.InnerException; inner.ShouldBeOfType(); - inner.Message.ShouldBe("You must use a Map overload that takes Action!"); + inner.Message.ShouldBe("Context.Items are only available when using a Map overload that takes Action!"); }); } } @@ -119,7 +119,7 @@ public void Should_use_value_passed_in() var config = new MapperConfiguration(cfg => { cfg.CreateMap() - .ForMember(d => d.Value1, opt => opt.MapFrom((source, d, dMember, context) => (int)context.Options.Items["Item"] + source.Value1)); + .ForMember(d => d.Value1, opt => opt.MapFrom((source, d, dMember, context) => (int)context.Items["Item"] + source.Value1)); }); var dest = config.CreateMapper().Map(new Source { Value1 = 5 }, opt => { opt.Items["Item"] = 10; }); diff --git a/src/UnitTests/CustomValidations.cs b/src/UnitTests/CustomValidations.cs index cfeceec7de..90c6c59034 100644 --- a/src/UnitTests/CustomValidations.cs +++ b/src/UnitTests/CustomValidations.cs @@ -96,7 +96,7 @@ public class When_using_custom_validation_for_convertusing_with_mappingfunction private static void SetValidated(ValidationContext context) { if (context.TypeMap.SourceType == typeof(Source) && - context.TypeMap.DestinationTypeToUse == typeof(Destination)) + context.TypeMap.DestinationType == typeof(Destination)) { _validated = true; } @@ -127,7 +127,7 @@ public class When_using_custom_validation_for_convertusing_with_typeconvertertyp private static void SetValidated(ValidationContext context) { if (context.TypeMap.SourceType == typeof(Source) && - context.TypeMap.DestinationTypeToUse == typeof(Destination)) + context.TypeMap.DestinationType == typeof(Destination)) { _validated = true; } @@ -166,7 +166,7 @@ public class When_using_custom_validation_for_convertusing_with_typeconverter_in private static void SetValidated(ValidationContext context) { if (context.TypeMap.SourceType == typeof(Source) && - context.TypeMap.DestinationTypeToUse == typeof(Destination)) + context.TypeMap.DestinationType == typeof(Destination)) { _validated = true; } @@ -206,7 +206,7 @@ public class When_using_custom_validation_for_convertusing_with_mappingexpressio private static void SetValidated(ValidationContext context) { if (context.TypeMap.SourceType == typeof(Source) && - context.TypeMap.DestinationTypeToUse == typeof(Destination)) + context.TypeMap.DestinationType == typeof(Destination)) { _validated = true; } diff --git a/src/UnitTests/Dictionaries.cs b/src/UnitTests/Dictionaries.cs index a43279ab05..7a69f57acd 100644 --- a/src/UnitTests/Dictionaries.cs +++ b/src/UnitTests/Dictionaries.cs @@ -482,6 +482,8 @@ public string GetString(string name, string @default) } protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.CreateMap()); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } diff --git a/src/UnitTests/IMappingExpression/ForCtorParam.cs b/src/UnitTests/IMappingExpression/ForCtorParam.cs index 4bbad0b471..fd3f303392 100644 --- a/src/UnitTests/IMappingExpression/ForCtorParam.cs +++ b/src/UnitTests/IMappingExpression/ForCtorParam.cs @@ -129,7 +129,7 @@ public void Should_throw_on_nonexistent_parameter() }); configuration.ShouldThrowException(exception => { - exception.Message.ShouldContain("does not have a constructor with a parameter named 'think'.", Case.Sensitive); + exception.Message.ShouldContain("does not have a matching constructor with a parameter named 'think'.", Case.Sensitive); exception.Message.ShouldContain(typeof(Dest).FullName, Case.Sensitive); }); } @@ -162,7 +162,7 @@ public void Should_throw_when_parameter_is_misspelt() configuration.ShouldThrowException(exception => { - exception.Message.ShouldContain("does not have a constructor with a parameter named 'think'.", Case.Sensitive); + exception.Message.ShouldContain("does not have a matching constructor with a parameter named 'think'.", Case.Sensitive); exception.Message.ShouldContain(typeof(Dest).FullName, Case.Sensitive); }); } diff --git a/src/UnitTests/IMappingExpression/IncludeMembers.cs b/src/UnitTests/IMappingExpression/IncludeMembers.cs index 6d4039cbfa..b870a141c4 100644 --- a/src/UnitTests/IMappingExpression/IncludeMembers.cs +++ b/src/UnitTests/IMappingExpression/IncludeMembers.cs @@ -1310,6 +1310,16 @@ class Destination cfg.CreateMap(MemberList.None); cfg.CreateMap(MemberList.None); }); + [Fact] + public void Should_flatten() + { + var source = new Source { Name = "name", InnerSource = new InnerSource { Description = "description" }, OtherInnerSource = new OtherInnerSource { Title = "title" } }; + var destination = Mapper.Map(source); + destination.Name.ShouldBe("name"); + destination.Description.ShouldBe("description"); + destination.Title.ShouldBe("title"); + } + } public class IncludeMembersWithGenericsSourceValidation : AutoMapperSpecBase { @@ -1342,6 +1352,15 @@ class Destination cfg.CreateMap(MemberList.None); cfg.CreateMap(MemberList.None); }); + [Fact] + public void Should_flatten() + { + var source = new Source { Name = "name", InnerSource = new InnerSource { Description = "description" }, OtherInnerSource = new OtherInnerSource { Title = "title" } }; + var destination = Mapper.Map(source); + destination.Name.ShouldBe("name"); + destination.Description.ShouldBe("description"); + destination.Title.ShouldBe("title"); + } } public class IncludeMembersWithInclude : AutoMapperSpecBase { @@ -1557,6 +1576,8 @@ class Destination cfg.CreateMap().IncludeMembers(s => s.InnerSource); cfg.CreateMap(MemberList.None); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class CascadedIncludeMembers : AutoMapperSpecBase { diff --git a/src/UnitTests/IMappingExpression/NonGenericProjectEnumTest.cs b/src/UnitTests/IMappingExpression/NonGenericProjectEnumTest.cs index b5e89139f5..e788b75956 100644 --- a/src/UnitTests/IMappingExpression/NonGenericProjectEnumTest.cs +++ b/src/UnitTests/IMappingExpression/NonGenericProjectEnumTest.cs @@ -120,5 +120,7 @@ public class Dest { cfg.CreateMap(typeof (Source), typeof (Dest)).ConvertUsing(src => new Dest {Value = 10}); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/IMappingExpression/NonGenericReverseMapping.cs b/src/UnitTests/IMappingExpression/NonGenericReverseMapping.cs index 90b47f1602..725b907d06 100644 --- a/src/UnitTests/IMappingExpression/NonGenericReverseMapping.cs +++ b/src/UnitTests/IMappingExpression/NonGenericReverseMapping.cs @@ -62,6 +62,8 @@ public class Dest .ForMember("Ignored", opt => opt.Ignore()) .ReverseMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_reverse_mapping_and_ignoring : SpecBase diff --git a/src/UnitTests/Internal/QueryMapperHelperTests.cs b/src/UnitTests/Internal/QueryMapperHelperTests.cs deleted file mode 100644 index 579caa7311..0000000000 --- a/src/UnitTests/Internal/QueryMapperHelperTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Xunit; -using Shouldly; -using AutoMapper.QueryableExtensions.Impl; - -namespace AutoMapper.UnitTests -{ - public class QueryMapperHelperTests - { - [Fact] - public void Should_include_full_type_name_when_missing_map() - { - QueryMapperHelper.MissingMapException(typeof(QueryMapperHelperTests), typeof(QueryMapperHelperTests)) - .Message.ShouldStartWith("Missing map from "+typeof(QueryMapperHelperTests).FullName); - } - } -} diff --git a/src/UnitTests/Mappers/CustomMapperTests.cs b/src/UnitTests/Mappers/CustomMapperTests.cs index 2035221c10..b03c5b1a65 100644 --- a/src/UnitTests/Mappers/CustomMapperTests.cs +++ b/src/UnitTests/Mappers/CustomMapperTests.cs @@ -95,6 +95,8 @@ public class DestinationType { public bool Value { get; set; } } + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_adding_a_simple_custom_mapper : AutoMapperSpecBase diff --git a/src/UnitTests/MappingInheritance/IncludeBaseShouldNotCreateMaps.cs b/src/UnitTests/MappingInheritance/IncludeBaseShouldNotCreateMaps.cs index 8500e37bef..6747fe80cd 100644 --- a/src/UnitTests/MappingInheritance/IncludeBaseShouldNotCreateMaps.cs +++ b/src/UnitTests/MappingInheritance/IncludeBaseShouldNotCreateMaps.cs @@ -1,4 +1,5 @@ using System; +using Xunit; namespace AutoMapper.UnitTests.MappingInheritance { @@ -29,5 +30,7 @@ public TestProfile() } protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.AddProfile()); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/MappingInheritance/IncludeBaseWithNonGenericUsage.cs b/src/UnitTests/MappingInheritance/IncludeBaseWithNonGenericUsage.cs index e2b3921509..3d3550d868 100644 --- a/src/UnitTests/MappingInheritance/IncludeBaseWithNonGenericUsage.cs +++ b/src/UnitTests/MappingInheritance/IncludeBaseWithNonGenericUsage.cs @@ -32,8 +32,9 @@ abstract class DestinationBase cfg.CreateMap(typeof(Source), typeof(Destination)) .IncludeBase(typeof(SourceBase), typeof(DestinationBase)); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } - public class IncludeBaseWithGenericUsage : AutoMapperSpecBase { class Source : SourceBase @@ -62,5 +63,7 @@ abstract class DestinationBase cfg.CreateMap() .IncludeBase, DestinationBase>(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/MappingInheritance/ShouldSupportOnlyDestinationTypeBeingDerived.cs b/src/UnitTests/MappingInheritance/ShouldSupportOnlyDestinationTypeBeingDerived.cs index dd8ef694d6..8986d1471f 100644 --- a/src/UnitTests/MappingInheritance/ShouldSupportOnlyDestinationTypeBeingDerived.cs +++ b/src/UnitTests/MappingInheritance/ShouldSupportOnlyDestinationTypeBeingDerived.cs @@ -42,6 +42,8 @@ class Override : Destination c.CreateMap(typeof(Source<>), typeof(Override<>)); c.CreateMap(typeof(Source<>), typeof(Destination<>)).As(typeof(Override<>)); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class AsShouldWorkOnlyWithDerivedTypes diff --git a/src/UnitTests/MappingInheritance/SourceValidationWithInheritance.cs b/src/UnitTests/MappingInheritance/SourceValidationWithInheritance.cs index dbc8073648..8c574968ba 100644 --- a/src/UnitTests/MappingInheritance/SourceValidationWithInheritance.cs +++ b/src/UnitTests/MappingInheritance/SourceValidationWithInheritance.cs @@ -1,4 +1,5 @@ using System; +using Xunit; namespace AutoMapper.UnitTests { @@ -59,6 +60,8 @@ public class FormElementDTO2 : FormControlBaseDTO2 cfg.CreateMap(MemberList.Source) .ForMember(dto => dto.ElementType, opt => opt.MapFrom(src => 0)); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class SourceValidationWithIgnore: AutoMapperSpecBase @@ -118,6 +121,7 @@ public class FormElementDTO2 : FormControlBaseDTO2 cfg.CreateMap(MemberList.Source) .ForMember(dto => dto.ElementType, opt => opt.MapFrom(src => 0)); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } - } \ No newline at end of file diff --git a/src/UnitTests/OpenGenerics.cs b/src/UnitTests/OpenGenerics.cs index 1e32e2c465..84049636e1 100644 --- a/src/UnitTests/OpenGenerics.cs +++ b/src/UnitTests/OpenGenerics.cs @@ -416,6 +416,8 @@ public MyProfile() } protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.AddProfile()); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class OpenGenerics diff --git a/src/UnitTests/Projection/MoreExplanatoryExceptionTests.cs b/src/UnitTests/Projection/MoreExplanatoryExceptionTests.cs index 30c8b66de2..988283e47b 100644 --- a/src/UnitTests/Projection/MoreExplanatoryExceptionTests.cs +++ b/src/UnitTests/Projection/MoreExplanatoryExceptionTests.cs @@ -27,10 +27,11 @@ public void ConstructorWithUnknownParameterTypeThrowsExplicitException() class EntitySource { + public DateTime NotSupported; } class EntityDestination { - public EntityDestination(object notSupported = null) { } + public EntityDestination(int notSupported = 0) { } } } } diff --git a/src/UnitTests/Projection/ProjectEnumTest.cs b/src/UnitTests/Projection/ProjectEnumTest.cs index 35d295ac58..fd46819dcf 100644 --- a/src/UnitTests/Projection/ProjectEnumTest.cs +++ b/src/UnitTests/Projection/ProjectEnumTest.cs @@ -72,5 +72,7 @@ public class Dest cfg.CreateProjection() .ConvertUsing(src => new Dest {Value = 10}); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } } \ No newline at end of file diff --git a/src/UnitTests/Projection/ProjectionMappers.cs b/src/UnitTests/Projection/ProjectionMappers.cs index 38950be69c..9d3b068073 100644 --- a/src/UnitTests/Projection/ProjectionMappers.cs +++ b/src/UnitTests/Projection/ProjectionMappers.cs @@ -38,10 +38,10 @@ public void Should_work_with_projections() } private class EnumToUnderlyingTypeProjectionMapper : IProjectionMapper { - public Expression Project(IGlobalConfiguration configuration, MemberMap memberMap, TypeMap memberTypeMap, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => - Convert(resolvedSource, memberMap.DestinationType); - public bool IsMatch(MemberMap memberMap, TypeMap memberTypeMap, Expression resolvedSource) => - memberMap.SourceType.IsEnum && Enum.GetUnderlyingType(memberMap.SourceType) == memberMap.DestinationType; + public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => + Convert(resolvedSource, request.DestinationType); + public bool IsMatch(TypePair context) => + context.SourceType.IsEnum && Enum.GetUnderlyingType(context.SourceType) == context.DestinationType; } } } \ No newline at end of file diff --git a/src/UnitTests/ReverseMapping.cs b/src/UnitTests/ReverseMapping.cs index ad9038a6d6..ceb69066d3 100644 --- a/src/UnitTests/ReverseMapping.cs +++ b/src/UnitTests/ReverseMapping.cs @@ -20,6 +20,8 @@ class Destination } protected override MapperConfiguration CreateConfiguration() => new(c=> c.CreateMap().ForMember(src => src.Id, opt => opt.MapFrom(_ => Guid.Empty)).ReverseMap()); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class InvalidReverseMap : NonValidatingSpecBase { @@ -519,6 +521,8 @@ public class Destination cfg.CreateMap(MemberList.Source) .ForMember(dest => dest.Value3, opt => opt.MapFrom(src => src.Value2)); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_validating_only_against_source_members_and_unmatching_source_members_are_manually_mapped_with_resolvers : AutoMapperSpecBase @@ -540,6 +544,8 @@ public class Destination .ForMember(dest => dest.Value3, opt => opt.MapFrom(src => src.Value2)) .ForSourceMember(src => src.Value2, opt => opt.DoNotValidate()); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_reverse_mapping_and_ignoring_via_method : AutoMapperSpecBase @@ -561,6 +567,8 @@ public class Dest .ForMember(d => d.Ignored, opt => opt.Ignore()) .ReverseMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class When_reverse_mapping_and_ignoring : SpecBase diff --git a/src/UnitTests/ShouldUseConstructor.cs b/src/UnitTests/ShouldUseConstructor.cs index aefd037ea2..7df906aeed 100644 --- a/src/UnitTests/ShouldUseConstructor.cs +++ b/src/UnitTests/ShouldUseConstructor.cs @@ -146,6 +146,8 @@ class Source protected override MapperConfiguration CreateConfiguration() => new MapperConfiguration(cfg => { cfg.CreateMap(); }); + [Fact] + public void Validate() => AssertConfigurationIsValid(); } public class ShouldIgnoreExplicitStaticConstructor : NonValidatingSpecBase