diff --git a/src/AutoMapper/AutoMapper.csproj b/src/AutoMapper/AutoMapper.csproj index 257444d05c..300d9e1a80 100644 --- a/src/AutoMapper/AutoMapper.csproj +++ b/src/AutoMapper/AutoMapper.csproj @@ -3,7 +3,7 @@ A convention-based object-object mapper A convention-based object-object mapper. AutoMapper uses a fluent configuration API to define an object-object mapping strategy. AutoMapper uses a convention-based matching algorithm to match up source to destination values. Currently, AutoMapper is designed for model projection scenarios to flatten complex object models to DTOs and other simple objects, whose design is better suited for serialization, communication, messaging, or simply an anti-corruption layer between the domain and application layer. - 6.2.0 + 6.2.1 Jimmy Bogard netstandard1.3;netstandard1.1;net45;net40 $(NoWarn);1591 diff --git a/src/AutoMapper/Configuration/ITypeMapConfiguration.cs b/src/AutoMapper/Configuration/ITypeMapConfiguration.cs index 2bf30ee341..bb03c14a95 100644 --- a/src/AutoMapper/Configuration/ITypeMapConfiguration.cs +++ b/src/AutoMapper/Configuration/ITypeMapConfiguration.cs @@ -5,7 +5,6 @@ namespace AutoMapper.Configuration public interface ITypeMapConfiguration { void Configure(TypeMap typeMap); - MemberList MemberList { get; } Type SourceType { get; } Type DestinationType { get; } bool IsOpenGeneric { get; } diff --git a/src/AutoMapper/Configuration/MappingExpression.cs b/src/AutoMapper/Configuration/MappingExpression.cs index 8d23c822d5..ed5696f758 100644 --- a/src/AutoMapper/Configuration/MappingExpression.cs +++ b/src/AutoMapper/Configuration/MappingExpression.cs @@ -152,12 +152,11 @@ public MappingExpression(MemberList memberList, Type sourceType, Type destinatio public MappingExpression(MemberList memberList, TypePair types) { - MemberList = memberList; Types = types; IsOpenGeneric = types.SourceType.IsGenericTypeDefinition() || types.DestinationType.IsGenericTypeDefinition(); + TypeMapActions.Add(tm => tm.ConfiguredMemberList = memberList); } - public MemberList MemberList { get; } public TypePair Types { get; } public Type SourceType => Types.SourceType; public Type DestinationType => Types.DestinationType; diff --git a/src/AutoMapper/IConfigurationProvider.cs b/src/AutoMapper/IConfigurationProvider.cs index b747488eb4..959172c30d 100644 --- a/src/AutoMapper/IConfigurationProvider.cs +++ b/src/AutoMapper/IConfigurationProvider.cs @@ -47,6 +47,23 @@ public interface IConfigurationProvider /// Type map configuration TypeMap ResolveTypeMap(Type sourceType, Type destinationType); + /// + /// Resolve the for the configured source and destination type, checking parent types + /// + /// Configured source type + /// Configured destination type + /// Inline type map configuration if exists + /// Type map configuration + TypeMap ResolveTypeMap(Type sourceType, Type destinationType, ITypeMapConfiguration inlineConfiguration); + + /// + /// Resolve the for the configured type pair, checking parent types + /// + /// Type pair + /// Inline type map configuration if exists + /// Type map configuration + TypeMap ResolveTypeMap(TypePair typePair, ITypeMapConfiguration inlineConfiguration); + /// /// Resolve the for the configured type pair, checking parent types /// diff --git a/src/AutoMapper/LockingConcurrentDictionary.cs b/src/AutoMapper/LockingConcurrentDictionary.cs index 11195ccf18..321e8197df 100644 --- a/src/AutoMapper/LockingConcurrentDictionary.cs +++ b/src/AutoMapper/LockingConcurrentDictionary.cs @@ -16,6 +16,7 @@ public LockingConcurrentDictionary(Func valueFactory) } public TValue GetOrAdd(TKey key) => _dictionary.GetOrAdd(key, _valueFactory).Value; + public TValue GetOrAdd(TKey key, Func> valueFactory) => _dictionary.GetOrAdd(key, valueFactory).Value; public TValue this[TKey key] { diff --git a/src/AutoMapper/MapperConfiguration.cs b/src/AutoMapper/MapperConfiguration.cs index 5b98c562a4..9b46ad58aa 100644 --- a/src/AutoMapper/MapperConfiguration.cs +++ b/src/AutoMapper/MapperConfiguration.cs @@ -18,6 +18,7 @@ namespace AutoMapper public class MapperConfiguration : IConfigurationProvider { + private static readonly Type[] ExcludedTypes = { typeof(object), typeof(ValueType), typeof(Enum) }; private static readonly ConstructorInfo ExceptionConstructor = typeof(AutoMapperMappingException).GetDeclaredConstructors().Single(c => c.GetParameters().Length == 3); private readonly IEnumerable _mappers; @@ -110,19 +111,12 @@ public LambdaExpression BuildExecutionPlan(Type sourceType, Type destinationType public LambdaExpression BuildExecutionPlan(MapRequest mapRequest) { - var typeMap = ResolveTypeMap(mapRequest.RuntimeTypes) ?? ResolveTypeMap(mapRequest.RequestedTypes); + var typeMap = ResolveTypeMap(mapRequest.RuntimeTypes, mapRequest.InlineConfig) ?? ResolveTypeMap(mapRequest.RequestedTypes, mapRequest.InlineConfig); if (typeMap != null) { return GenerateTypeMapExpression(mapRequest, typeMap); } var mapperToUse = FindMapper(mapRequest.RuntimeTypes); - if (Configuration.CreateMissingTypeMaps && mapperToUse == null) - { - typeMap = Configuration.CreateInlineMap(_typeMapRegistry, mapRequest.InlineConfig ?? new DefaultTypeMapConfig(mapRequest.RuntimeTypes)); - _typeMapPlanCache[mapRequest.RuntimeTypes] = typeMap; - typeMap.Seal(this); - return GenerateTypeMapExpression(mapRequest, typeMap); - } return GenerateObjectMapperExpression(mapRequest, mapperToUse, this); } @@ -193,17 +187,28 @@ public TypeMap ResolveTypeMap(Type sourceType, Type destinationType) { var typePair = new TypePair(sourceType, destinationType); - return ResolveTypeMap(typePair); + return ResolveTypeMap(typePair, new DefaultTypeMapConfig(typePair)); + } + + public TypeMap ResolveTypeMap(Type sourceType, Type destinationType, ITypeMapConfiguration inlineConfiguration) + { + var typePair = new TypePair(sourceType, destinationType); + + return ResolveTypeMap(typePair, inlineConfiguration); } public TypeMap ResolveTypeMap(TypePair typePair) + => ResolveTypeMap(typePair, new DefaultTypeMapConfig(typePair)); + + public TypeMap ResolveTypeMap(TypePair typePair, ITypeMapConfiguration inlineConfiguration) { var typeMap = _typeMapPlanCache.GetOrAdd(typePair); // if it's a dynamically created type map, we need to seal it outside GetTypeMap to handle recursion if (typeMap != null && typeMap.MapExpression == null && _typeMapRegistry.GetTypeMap(typePair) == null) { lock (typeMap) - { + { + inlineConfiguration.Configure(typeMap); typeMap.Seal(this); } } @@ -211,13 +216,17 @@ public TypeMap ResolveTypeMap(TypePair typePair) } private TypeMap GetTypeMap(TypePair initialTypes) - { - var hasMapper = new Lazy(() => FindMapper(initialTypes) != null); + { + var doesNotHaveMapper = FindMapper(initialTypes) == null; + foreach (var types in initialTypes.GetRelatedTypePairs()) { if (types != initialTypes && _typeMapPlanCache.TryGetValue(types, out var typeMap)) { - return typeMap; + if (typeMap != null) + { + return typeMap; + } } typeMap = FindTypeMapFor(types); if (typeMap != null) @@ -229,7 +238,7 @@ private TypeMap GetTypeMap(TypePair initialTypes) { return typeMap; } - if (!hasMapper.Value) + if (doesNotHaveMapper) { typeMap = FindConventionTypeMapFor(types); if (typeMap != null) @@ -239,6 +248,19 @@ private TypeMap GetTypeMap(TypePair initialTypes) } } + if (doesNotHaveMapper + && Configuration.CreateMissingTypeMaps + && !(initialTypes.SourceType.IsAbstract() && initialTypes.SourceType.IsClass()) + && !(initialTypes.DestinationType.IsAbstract() && initialTypes.DestinationType.IsClass()) + && !ExcludedTypes.Contains(initialTypes.SourceType) + && !ExcludedTypes.Contains(initialTypes.DestinationType)) + { + lock (this) + { + return Configuration.CreateInlineMap(_typeMapRegistry, initialTypes); + } + } + return null; } @@ -405,7 +427,7 @@ private static Expression Wrap(MapRequest mapRequest, Delegat } } - private class DefaultTypeMapConfig : ITypeMapConfiguration + internal class DefaultTypeMapConfig : ITypeMapConfiguration { public DefaultTypeMapConfig(TypePair types) { @@ -414,7 +436,6 @@ public DefaultTypeMapConfig(TypePair types) public void Configure(TypeMap typeMap) { } - public MemberList MemberList => MemberList.Destination; public Type SourceType => Types.SourceType; public Type DestinationType => Types.DestinationType; public bool IsOpenGeneric => false; diff --git a/src/AutoMapper/ProfileMap.cs b/src/AutoMapper/ProfileMap.cs index 56f397494d..a7cb1dbc6b 100644 --- a/src/AutoMapper/ProfileMap.cs +++ b/src/AutoMapper/ProfileMap.cs @@ -16,7 +16,6 @@ struct IConvertible{} [DebuggerDisplay("{Name}")] public class ProfileMap { - private static readonly Type[] ExcludedTypes = { typeof(object), typeof(ValueType), typeof(Enum), typeof(IComparable), typeof(IFormattable), typeof(IConvertible) }; private readonly TypeMapFactory _typeMapFactory = new TypeMapFactory(); private readonly IEnumerable _typeMapConfigs; private readonly IEnumerable _openTypeMapConfigs; @@ -129,7 +128,7 @@ public void Configure(TypeMapRegistry typeMapRegistry) private void BuildTypeMap(TypeMapRegistry typeMapRegistry, ITypeMapConfiguration config) { - var typeMap = _typeMapFactory.CreateTypeMap(config.SourceType, config.DestinationType, this, config.MemberList); + var typeMap = _typeMapFactory.CreateTypeMap(config.SourceType, config.DestinationType, this); config.Configure(typeMap); @@ -176,7 +175,7 @@ public bool IsConventionMap(TypePair types) public TypeMap CreateConventionTypeMap(TypeMapRegistry typeMapRegistry, TypePair types) { - var typeMap = _typeMapFactory.CreateTypeMap(types.SourceType, types.DestinationType, this, MemberList.Destination); + var typeMap = _typeMapFactory.CreateTypeMap(types.SourceType, types.DestinationType, this); typeMap.IsConventionMap = true; @@ -189,14 +188,12 @@ public TypeMap CreateConventionTypeMap(TypeMapRegistry typeMapRegistry, TypePair return typeMap; } - public TypeMap CreateInlineMap(TypeMapRegistry typeMapRegistry, ITypeMapConfiguration inlineConfig) + public TypeMap CreateInlineMap(TypeMapRegistry typeMapRegistry, TypePair types) { - var typeMap = _typeMapFactory.CreateTypeMap(inlineConfig.SourceType, inlineConfig.DestinationType, this, inlineConfig.MemberList); + var typeMap = _typeMapFactory.CreateTypeMap(types.SourceType, types.DestinationType, this); typeMap.IsConventionMap = true; - inlineConfig.Configure(typeMap); - Configure(typeMapRegistry, typeMap); return typeMap; @@ -204,7 +201,7 @@ public TypeMap CreateInlineMap(TypeMapRegistry typeMapRegistry, ITypeMapConfigur public TypeMap CreateClosedGenericTypeMap(ITypeMapConfiguration openMapConfig, TypeMapRegistry typeMapRegistry, TypePair closedTypes) { - var closedMap = _typeMapFactory.CreateTypeMap(closedTypes.SourceType, closedTypes.DestinationType, this, openMapConfig.MemberList); + var closedMap = _typeMapFactory.CreateTypeMap(closedTypes.SourceType, closedTypes.DestinationType, this); openMapConfig.Configure(closedMap); diff --git a/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs b/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs index af066bdb34..ea0207f0cf 100644 --- a/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs +++ b/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs @@ -463,7 +463,7 @@ public override QueryExpressions GetSubQueryExpression(ExpressionBuilder builder TypeMap firstTypeMap; lock(_configurationProvider) { - firstTypeMap = typeMapFactory.CreateTypeMap(request.SourceType, letType, typeMap.Profile, MemberList.None); + firstTypeMap = typeMapFactory.CreateTypeMap(request.SourceType, letType, typeMap.Profile); } var secondParameter = Parameter(letType, "dto"); diff --git a/src/AutoMapper/TypeMap.cs b/src/AutoMapper/TypeMap.cs index 6120ada4b9..6bdeedc60a 100644 --- a/src/AutoMapper/TypeMap.cs +++ b/src/AutoMapper/TypeMap.cs @@ -31,13 +31,12 @@ public class TypeMap private readonly IList _inheritedTypeMaps = new List(); private readonly List _valueTransformerConfigs = new List(); - public TypeMap(TypeDetails sourceType, TypeDetails destinationType, MemberList memberList, ProfileMap profile) + public TypeMap(TypeDetails sourceType, TypeDetails destinationType, ProfileMap profile) { SourceTypeDetails = sourceType; DestinationTypeDetails = destinationType; Types = new TypePair(sourceType.Type, destinationType.Type); Profile = profile; - ConfiguredMemberList = memberList; } public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, MemberPath path, TypeMap typeMap) @@ -77,7 +76,7 @@ public PathMap FindPathMapByDestinationPath(string destinationFullPath) => public bool ConstructDestinationUsingServiceLocator { get; set; } - public MemberList ConfiguredMemberList { get; } + public MemberList ConfiguredMemberList { get; set; } public IEnumerable IncludedDerivedTypes => _includedDerivedTypes; public IEnumerable IncludedBaseTypes => _includedBaseTypes; diff --git a/src/AutoMapper/TypeMapFactory.cs b/src/AutoMapper/TypeMapFactory.cs index 9740031ac9..d9ee1e7461 100644 --- a/src/AutoMapper/TypeMapFactory.cs +++ b/src/AutoMapper/TypeMapFactory.cs @@ -8,12 +8,12 @@ namespace AutoMapper { public class TypeMapFactory { - public TypeMap CreateTypeMap(Type sourceType, Type destinationType, ProfileMap options, MemberList memberList) + public TypeMap CreateTypeMap(Type sourceType, Type destinationType, ProfileMap options) { var sourceTypeInfo = options.CreateTypeDetails(sourceType); var destTypeInfo = options.CreateTypeDetails(destinationType); - var typeMap = new TypeMap(sourceTypeInfo, destTypeInfo, memberList, options); + var typeMap = new TypeMap(sourceTypeInfo, destTypeInfo, options); foreach (var destProperty in destTypeInfo.PublicWriteAccessors) { diff --git a/src/AutoMapper/TypePair.cs b/src/AutoMapper/TypePair.cs index 6d2bd94d95..b402cee895 100644 --- a/src/AutoMapper/TypePair.cs +++ b/src/AutoMapper/TypePair.cs @@ -15,7 +15,7 @@ public struct MapRequest : IEquatable public ITypeMapConfiguration InlineConfig { get; } public MapRequest(TypePair requestedTypes, TypePair runtimeTypes) - : this(requestedTypes, runtimeTypes, null) + : this(requestedTypes, runtimeTypes, new MapperConfiguration.DefaultTypeMapConfig(requestedTypes)) { } diff --git a/src/UnitTests/Bug/BaseMapWithIncludesAndUnincludedMappings.cs b/src/UnitTests/Bug/BaseMapWithIncludesAndUnincludedMappings.cs index 9012909c01..e8b7626e99 100644 --- a/src/UnitTests/Bug/BaseMapWithIncludesAndUnincludedMappings.cs +++ b/src/UnitTests/Bug/BaseMapWithIncludesAndUnincludedMappings.cs @@ -51,12 +51,12 @@ public class Container2 { public BaseB Item { get; set; } } - public class BaseA + public abstract class BaseA { public string Name { get; set; } } - public class BaseB + public abstract class BaseB { public string Name { get; set; } } @@ -84,7 +84,8 @@ public void TestInitialiserProxyOfSub() cfg.CreateMap(); }); - var mapped = config.CreateMapper().Map(new Container() { Item = new ProxyOfSubA() { Name = "Martin", Description = "Hello" } }); + var mapped = config.CreateMapper() + .Map(new Container() { Item = new ProxyOfSubA() { Name = "Martin", Description = "Hello" } }); Assert.IsType(mapped.Item); } diff --git a/src/UnitTests/Bug/ExpressionMapping.cs b/src/UnitTests/Bug/ExpressionMapping.cs index 5ebd56f5bf..f8b1fa0e8c 100644 --- a/src/UnitTests/Bug/ExpressionMapping.cs +++ b/src/UnitTests/Bug/ExpressionMapping.cs @@ -127,6 +127,7 @@ public Parent Parent protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => { + cfg.CreateMissingTypeMaps = false; cfg.CreateMap().ReverseMap(); cfg.CreateMap().ReverseMap(); cfg.CreateMap() diff --git a/src/UnitTests/DynamicMapping.cs b/src/UnitTests/DynamicMapping.cs index c273a8daca..232f507e68 100644 --- a/src/UnitTests/DynamicMapping.cs +++ b/src/UnitTests/DynamicMapping.cs @@ -3,6 +3,7 @@ using Xunit; using Shouldly; using System.Collections.Generic; +using AutoMapper.QueryableExtensions; namespace AutoMapper.UnitTests.DynamicMapping { @@ -396,6 +397,32 @@ public void Should_map() } } + public class When_automatically_dynamically_projecting : NonValidatingSpecBase + { + public class Source + { + public int Value { get; set; } + } + + public class Dest + { + public int Value { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => {}); + + [Fact] + public void Should_map() + { + var source = new Source {Value = 5}; + var items = new[] {source}.AsQueryable(); + var dest = items.ProjectTo(ConfigProvider).ToArray(); + + dest.Length.ShouldBe(1); + dest[0].Value.ShouldBe(5); + } + } + public class When_mixing_auto_and_manual_map : NonValidatingSpecBase { public class Source diff --git a/src/UnitTests/Projection/ProjectTest.cs b/src/UnitTests/Projection/ProjectTest.cs index 97df1f699d..1acfe5d19f 100644 --- a/src/UnitTests/Projection/ProjectTest.cs +++ b/src/UnitTests/Projection/ProjectTest.cs @@ -44,6 +44,7 @@ public ProjectTest() { cfg.CreateMap(); cfg.CreateMap(); + cfg.CreateMissingTypeMaps = false; }); } diff --git a/src/UnitTests/Query/SourceInjectedQuery.cs b/src/UnitTests/Query/SourceInjectedQuery.cs index 4b638138fa..9659038736 100644 --- a/src/UnitTests/Query/SourceInjectedQuery.cs +++ b/src/UnitTests/Query/SourceInjectedQuery.cs @@ -628,6 +628,7 @@ private static IMapper SetupAutoMapper() { var config = new MapperConfiguration(cfg => { + cfg.CreateMissingTypeMaps = false; cfg.CreateMap() .ForMember(d => d.Id, opt => opt.MapFrom(s => s.UserId)) .ForMember(d => d.FullName, opt => opt.MapFrom(s => s.Name)) diff --git a/src/UnitTests/Tests/TypeMapFactorySpecs.cs b/src/UnitTests/Tests/TypeMapFactorySpecs.cs index 52676a620c..f8d66a2da9 100644 --- a/src/UnitTests/Tests/TypeMapFactorySpecs.cs +++ b/src/UnitTests/Tests/TypeMapFactorySpecs.cs @@ -65,7 +65,7 @@ public void Should_map_properties_with_same_name() //mappingOptions.DestinationMemberNamingConvention = new PascalCaseNamingConvention(); var profile = new ProfileMap(mappingOptions); - var typeMap = _factory.CreateTypeMap(typeof(Source), typeof(Destination), profile, MemberList.Destination); + var typeMap = _factory.CreateTypeMap(typeof(Source), typeof(Destination), profile); var propertyMaps = typeMap.GetPropertyMaps(); @@ -116,7 +116,7 @@ protected override void Establish_context() protected override void Because_of() { - _map = _factory.CreateTypeMap(typeof(Source), typeof(Destination), _mappingOptions, MemberList.Destination); + _map = _factory.CreateTypeMap(typeof(Source), typeof(Destination), _mappingOptions); } [Fact] @@ -169,7 +169,7 @@ protected override void Establish_context() protected override void Because_of() { - _map = _factory.CreateTypeMap(typeof(Source), typeof(Destination), _mappingOptions, MemberList.Destination); + _map = _factory.CreateTypeMap(typeof(Source), typeof(Destination), _mappingOptions); } [Fact] diff --git a/src/UnitTests/XpressionMapperTests.cs b/src/UnitTests/XpressionMapperTests.cs index 5f67526ef1..f9dc93cd2b 100644 --- a/src/UnitTests/XpressionMapperTests.cs +++ b/src/UnitTests/XpressionMapperTests.cs @@ -216,6 +216,7 @@ public void Map_expression_should_throws_exception_when_mapping_is_missed() var config = new MapperConfiguration(cfg => { cfg.CreateMap(); + cfg.CreateMissingTypeMaps = false; }); var customMapper = config.CreateMapper();