diff --git a/AgileMapper.UnitTests.Orms.EfCore2/AgileMapper.UnitTests.Orms.EfCore2.csproj b/AgileMapper.UnitTests.Orms.EfCore2/AgileMapper.UnitTests.Orms.EfCore2.csproj index 6852fcf27..363325913 100644 --- a/AgileMapper.UnitTests.Orms.EfCore2/AgileMapper.UnitTests.Orms.EfCore2.csproj +++ b/AgileMapper.UnitTests.Orms.EfCore2/AgileMapper.UnitTests.Orms.EfCore2.csproj @@ -60,9 +60,6 @@ ..\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll - - ..\packages\Microsoft.Extensions.Configuration.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll - ..\packages\Microsoft.Extensions.DependencyInjection.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs index 62936a911..140bebc19 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { + using System; using System.Collections.Generic; using AgileMapper.Extensions.Internal; using Common; @@ -214,8 +215,8 @@ public void ShouldUseATypedToTarget() .Map(ctx => ctx.Source.Leaf) .ToTarget() .AndWhenMapping - .From().To() - .Map((dto, l) => dto.Leaf.Description + "!") + .From().To() + .Map((dto, l) => dto.Description + "!") .To(l => l.Description); var leafDto = new Issue123.CompositeDto @@ -233,6 +234,66 @@ public void ShouldUseATypedToTarget() } } + [Fact] + public void ShouldUseACtorParameterWithATypedToTarget() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .To() + .If(d => d.Source.ConcreteValue == Issue129.Source.Wrapper.ConcreteValueType.Delay) + .Map(d => d.Source.DelayValue) + .ToTarget(); + + var delaySource = new Issue129.Source.Wrapper + { + ConcreteValue = Issue129.Source.Wrapper.ConcreteValueType.Delay, + DelayValue = new Issue129.Source.DelayObject + { + Name = "Situation Object", + Duration = TimeSpan.FromHours(2).ToString() + } + }; + + var delayResult = mapper.Map(delaySource).ToANew(); + + delayResult.ShouldNotBeNull(); + delayResult.ShouldBeOfType(); + + var delayObject = (Issue129.Target.DelayObject)delayResult; + delayObject.CurrentClass.ShouldNotBeNull(); + delayObject.CurrentClass.ShouldBeOfType(); + + var delayClass = (Issue129.Target.DelayClass)delayObject.CurrentClass; + delayClass.Name.ShouldBe("Delay"); + delayClass.Duration.ShouldBe(TimeSpan.FromHours(2)); + } + } + + [Fact] + public void ShouldHandleANullTypedToTargetSource() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .To() + .If(d => d.Source.ConcreteValue == Issue129.Source.Wrapper.ConcreteValueType.Delay) + .Map(d => d.Source.DelayValue) + .ToTarget(); + + var nullDelaySource = new Issue129.Source.Wrapper + { + ConcreteValue = Issue129.Source.Wrapper.ConcreteValueType.Delay + }; + + var delayResult = mapper.Map(nullDelaySource).ToANew(); + + delayResult.ShouldBeNull(); + } + } + // See https://github.com/agileobjects/AgileMapper/issues/129 [Fact] public void ShouldUseAConfiguredCtorParameterWithATypedToTarget() @@ -248,7 +309,7 @@ public void ShouldUseAConfiguredCtorParameterWithATypedToTarget() mapper.WhenMapping .From() .To() - .Map(new Issue129.Target.ActionClass()) + .Map(ctx => new Issue129.Target.ActionClass()) .ToCtor(); mapper.WhenMapping @@ -261,6 +322,31 @@ public void ShouldUseAConfiguredCtorParameterWithATypedToTarget() .If(d => d.Source.ConcreteValue == Issue129.Source.Wrapper.ConcreteValueType.Situation) .Map(d => d.Source.SituationValue) .ToTarget(); + + var situationSource = new Issue129.Source.Wrapper + { + ConcreteValue = Issue129.Source.Wrapper.ConcreteValueType.Situation, + SituationValue = new Issue129.Source.SituationObject + { + Name = "Situation Object", + CurrentClass = new Issue129.Source.SituationClass + { + Name = "Situation Class" + } + } + }; + + var situationResult = mapper.Map(situationSource).ToANew(); + + situationResult.ShouldNotBeNull(); + situationResult.ShouldBeOfType(); + + var situationObject = (Issue129.Target.SituationObject)situationResult; + situationObject.CurrentClass.ShouldNotBeNull(); + situationObject.CurrentClass.ShouldBeOfType(); + + var situationClass = (Issue129.Target.SituationClass)situationObject.CurrentClass; + situationClass.Name.ShouldBe("Situation Class"); } } @@ -334,7 +420,7 @@ public class Leaf : ILeaf } } - internal class Issue129 + internal static class Issue129 { public static class Source { @@ -348,22 +434,31 @@ public class SituationObject public string Name { get; set; } public SituationClass CurrentClass { get; set; } - } + public class ActionObject { public string Name { get; set; } } + public class DelayObject + { + public string Name { get; set; } + + public string Duration { get; set; } + } + public class Wrapper { - public enum ConcreteValueType { Situation, Action } + public enum ConcreteValueType { Situation, Action, Delay } public ConcreteValueType ConcreteValue { get; set; } public SituationObject SituationValue { get; set; } public ActionObject ActionValue { get; set; } + + public DelayObject DelayValue { get; set; } } } @@ -373,22 +468,49 @@ public interface ITrafficObj { ITrafficClass CurrentClass { get; } } public interface ITrafficClass { string Name { get; } } - public class SituationClass : ITrafficClass { public string Name { get; set; } } + public class SituationClass : ITrafficClass + { + public string Name { get; set; } + } + + public class ActionClass : ITrafficClass + { + public string Name { get; set; } + } + + public class DelayClass : ITrafficClass + { + public string Name { get; set; } - public class ActionClass : ITrafficClass { public string Name { get; set; } } + public TimeSpan Duration { get; set; } + } public class SituationObject : ITrafficObj { public SituationObject(SituationClass clazz) { CurrentClass = clazz; } - public ITrafficClass CurrentClass { get; private set; } + public ITrafficClass CurrentClass { get; } } public class ActionObject : ITrafficObj { public ActionObject(ActionClass clazz) { CurrentClass = clazz; } - public ITrafficClass CurrentClass { get; private set; } + public ITrafficClass CurrentClass { get; } + } + + public class DelayObject : ITrafficObj + { + public DelayObject(TimeSpan duration) + { + CurrentClass = new DelayClass + { + Name = "Delay", + Duration = duration + }; + } + + public ITrafficClass CurrentClass { get; } } } } diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringMappingCallbacks.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringMappingCallbacks.cs index 56dbe9f2d..9d1aad259 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringMappingCallbacks.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringMappingCallbacks.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using AgileMapper.Extensions.Internal; using Common; using TestClasses; #if !NET35 @@ -355,5 +356,116 @@ public void ShouldPopulateAChildTargetObjectInAPostMappingCallback() result.Value.Value.ShouldBe("Hello!"); } } + + [Fact] + public void ShouldExecuteAPreMappingCallbackInARootToTargetMapping() + { + using (var mapper = Mapper.CreateNew()) + { + var callbackCalled = false; + + mapper.WhenMapping + .From, int>>() + .To>() + .Map(ctx => ctx.Source.Value1) + .ToTarget(); + + mapper.WhenMapping + .From>() + .To>() + .Before.MappingBegins + .Call(md => callbackCalled = true); + + var source = new PublicTwoFields, int> + { + Value1 = new PublicField { Value = 123 }, + Value2 = 456 + }; + + var result = mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(123); + callbackCalled.ShouldBeTrue(); + } + } + + [Fact] + public void ShouldExecuteAPreMappingCallbackInAChildToTargetMapping() + { + using (var mapper = Mapper.CreateNew()) + { + var callbackCalled = false; + + mapper.WhenMapping + .From, int>>() + .To>() + .Map(ctx => ctx.Source.Value1) + .ToTarget(); + + mapper.WhenMapping + .From>() + .To>() + .After.MappingEnds + .Call(md => callbackCalled = true); + + var source = new PublicProperty, int>> + { + Value = new PublicTwoFields, int> + { + Value1 = new PublicField { Value = 456 }, + Value2 = 123 + } + }; + + var result = mapper.Map(source).ToANew>>(); + + result.Value.ShouldNotBeNull(); + result.Value.Value.ShouldBe(456); + callbackCalled.ShouldBeTrue(); + } + } + + [Fact] + public void ShouldExecuteAPreMappingCallbackInAnElementToTargetMapping() + { + using (var mapper = Mapper.CreateNew()) + { + var callbackCount = 0; + + mapper.WhenMapping + .From>>() + .To>() + .Map(ctx => ctx.Source.Value2) + .ToTarget(); + + mapper.WhenMapping + .From>() + .To>() + .After.MappingEnds + .Call(md => ++callbackCount); + + var source = new[] + { + new PublicTwoFields> + { + Value1 = 111, + Value2 = new PublicField { Value = 222 }, + }, + new PublicTwoFields> + { + Value1 = 333, + Value2 = new PublicField { Value = 444 }, + } + }; + + var result = mapper.Map(source).ToANew[]>(); + + result.ShouldNotBeNull(); + result.Length.ShouldBe(2); + result.First().Value.ShouldBe(222); + result.Second().Value.ShouldBe(444); + callbackCount.ShouldBe(2); + } + } } } diff --git a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs index c72771948..b68318d80 100644 --- a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs +++ b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs @@ -1,10 +1,8 @@ namespace AgileObjects.AgileMapper.Api.Configuration { - using System; using AgileMapper.Configuration; using Extensions.Internal; using NetStandardPolyfills; - using ObjectPopulation; using Projection; using ReadableExpressions.Extensions; @@ -46,12 +44,8 @@ private void ThrowIfUnconstructable() { var mappingData = _configInfo.ToMappingData(); - if (IsConstructableUsing(mappingData)) - { - return; - } - - if (IsConstructableFromToTargetDataSources(mappingData, typeof(TDerivedTarget))) + if (mappingData.IsTargetConstructable() || + mappingData.IsConstructableFromToTargetDataSource()) { return; } @@ -63,9 +57,8 @@ private void ThrowIfUnconstructable() var configuredImplementationPairings = MapperContext .UserConfigurations - .DerivedTypes.GetImplementationTypePairsFor( - _configInfo.ToMapperData(), - MapperContext); + .DerivedTypes + .GetImplementationTypePairsFor(_configInfo.ToMapperData(), MapperContext); if (configuredImplementationPairings.None()) { @@ -73,45 +66,6 @@ private void ThrowIfUnconstructable() } } - private bool IsConstructableUsing(IObjectMappingData mappingData) - => MapperContext.ConstructionFactory.GetNewObjectCreation(mappingData) != null; - - private bool IsConstructableFromToTargetDataSources(IObjectMappingData mappingData, Type derivedTargetType) - { - var toTargetDataSources = MapperContext - .UserConfigurations - .GetDataSourcesForToTarget(mappingData.MapperData); - - if (toTargetDataSources.None()) - { - return false; - } - - var constructionCheckMethod = typeof(DerivedPairTargetTypeSpecifier) - .GetNonPublicInstanceMethod(nameof(IsConstructableFromDataSource)); - - foreach (var dataSource in toTargetDataSources) - { - var isConstructable = (bool)constructionCheckMethod - .MakeGenericMethod(dataSource.SourceMember.Type, derivedTargetType) - .Invoke(this, Enumerable.EmptyArray); - - if (isConstructable) - { - return true; - } - } - - return false; - } - - private bool IsConstructableFromDataSource() - { - var mappingData = _configInfo.ToMappingData(); - - return IsConstructableUsing(mappingData); - } - private static void ThrowUnableToCreate() { throw new MappingConfigurationException( diff --git a/AgileMapper/Configuration/DerivedTypePair.cs b/AgileMapper/Configuration/DerivedTypePair.cs index 43ffd881b..acde8201f 100644 --- a/AgileMapper/Configuration/DerivedTypePair.cs +++ b/AgileMapper/Configuration/DerivedTypePair.cs @@ -2,6 +2,11 @@ { using System; using System.Globalization; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index c7ad43150..8af37e956 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -3,16 +3,16 @@ using System; using System.Collections.Generic; using System.Globalization; - using Extensions.Internal; - using Members; - using ObjectPopulation; - using ReadableExpressions; #if NET35 using LinqExp = System.Linq.Expressions; using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; + using ObjectPopulation; + using ReadableExpressions; internal class MappingConfigInfo : ITypePair { diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index 4852dd2d6..3ca074145 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -342,7 +342,7 @@ public void Add(ConfiguredDataSourceFactory dataSourceFactory) if (dataSourceFactory.TargetMember.IsRoot) { - HasConfiguredRootDataSources = true; + HasConfiguredToTargetDataSources = true; return; } @@ -355,14 +355,14 @@ public void Add(ConfiguredDataSourceFactory dataSourceFactory) public ConfiguredDataSourceFactory GetDataSourceFactoryFor(MappingConfigInfo configInfo) => _dataSourceFactories.First(dsf => dsf.ConfigInfo == configInfo); - public bool HasConfiguredRootDataSources { get; private set; } + public bool HasConfiguredToTargetDataSources { get; private set; } public IList GetDataSources(IMemberMapperData mapperData) => GetDataSources(QueryDataSourceFactories(mapperData), mapperData); public IList GetDataSourcesForToTarget(IMemberMapperData mapperData) { - if (!HasConfiguredRootDataSources) + if (!HasConfiguredToTargetDataSources) { return Enumerable.EmptyArray; } diff --git a/AgileMapper/Constants.cs b/AgileMapper/Constants.cs index ff0bc1fde..64462339d 100644 --- a/AgileMapper/Constants.cs +++ b/AgileMapper/Constants.cs @@ -18,7 +18,7 @@ internal static class Constants public static readonly string RootMemberName = "Root"; public static readonly string EnumerableElementName = "[i]"; - public static readonly Type[] NoTypeArguments = Enumerable.EmptyArray; + public static readonly Type[] EmptyTypeArray = Enumerable.EmptyArray; public static readonly Type AllTypes = typeof(Constants); public static readonly Expression EmptyExpression = Expression.Empty(); diff --git a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs index 0ea0d4a7c..18aec53a3 100644 --- a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs @@ -36,7 +36,7 @@ public IEnumerable FindFor(DataSourceFindContext context) } if (matchingSourceMemberDataSource.SourceMember.IsSimple && - context.MapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources) + context.MapperData.MapperContext.UserConfigurations.HasConfiguredToTargetDataSources) { var updatedMapperData = new ChildMemberMapperData( matchingSourceMemberDataSource.SourceMember, diff --git a/AgileMapper/DerivedTypesCache.cs b/AgileMapper/DerivedTypesCache.cs index 6f723616f..169deba8b 100644 --- a/AgileMapper/DerivedTypesCache.cs +++ b/AgileMapper/DerivedTypesCache.cs @@ -36,7 +36,7 @@ public IList GetTypesDerivedFrom(Type type) { if (type.IsSealed() || type.IsFromBcl()) { - return Enumerable.EmptyArray; + return Constants.EmptyTypeArray; } return _derivedTypesByType.GetOrAdd(type, GetDerivedTypesForType); @@ -63,7 +63,7 @@ private IList GetDerivedTypesForType(Type type) if (derivedTypes.None()) { - return Enumerable.EmptyArray; + return Constants.EmptyTypeArray; } var derivedTypesList = derivedTypes diff --git a/AgileMapper/MappingDataExtensions.cs b/AgileMapper/MappingDataExtensions.cs new file mode 100644 index 000000000..eea14457d --- /dev/null +++ b/AgileMapper/MappingDataExtensions.cs @@ -0,0 +1,58 @@ +namespace AgileObjects.AgileMapper +{ +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using DataSources; + using Extensions.Internal; + using ObjectPopulation; + + internal static class MappingDataExtensions + { + public static bool IsStandalone(this IObjectMappingData mappingData) + => mappingData.IsRoot || mappingData.MappingTypes.RuntimeTypesNeeded; + + public static bool IsTargetConstructable(this IObjectMappingData mappingData) + => mappingData.GetTargetObjectCreation() != null; + + public static bool IsConstructableFromToTargetDataSource(this IObjectMappingData mappingData) + => mappingData.GetToTargetDataSourceOrNullForTargetType() != null; + + public static IConfiguredDataSource GetToTargetDataSourceOrNullForTargetType(this IObjectMappingData mappingData) + { + var toTargetDataSources = mappingData + .MapperData + .MapperContext + .UserConfigurations + .GetDataSourcesForToTarget(mappingData.MapperData); + + if (toTargetDataSources.None()) + { + return null; + } + + foreach (var dataSource in toTargetDataSources) + { + mappingData = mappingData.WithSource(dataSource.SourceMember); + + if (mappingData.IsTargetConstructable()) + { + return dataSource; + } + } + + return null; + } + + public static Expression GetTargetObjectCreation(this IObjectMappingData mappingData) + { + return mappingData + .MapperData + .MapperContext + .ConstructionFactory + .GetNewObjectCreation(mappingData); + } + } +} diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 0b13804fe..67ea4d890 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -252,7 +252,7 @@ private static Expression AssignMember(Expression instance, Member targetMember, => targetMember.GetAccess(instance).AssignTo(value); private static Expression CallSetMethod(Expression instance, Member targetMember, Expression value) - => Expression.Call(instance, targetMember.Name, NoTypeArguments, value); + => Expression.Call(instance, targetMember.Name, EmptyTypeArray, value); #endregion diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index a333575d8..6751fdb26 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -21,9 +21,6 @@ namespace AgileObjects.AgileMapper.Members internal static class MemberMapperDataExtensions { - public static bool IsStandalone(this IObjectMappingData mappingData) - => mappingData.IsRoot || mappingData.MappingTypes.RuntimeTypesNeeded; - public static bool TargetTypeIsEntity(this IMemberMapperData mapperData) => IsEntity(mapperData, mapperData.TargetType, out _); @@ -433,7 +430,6 @@ public static Expression GetMappingCallbackOrNull( .GetCallbackOrNull(callbackPosition, basicData, mapperData); } - public static ICollection GetDerivedSourceTypes(this IMemberMapperData mapperData) => GlobalContext.Instance.DerivedTypes.GetTypesDerivedFrom(mapperData.SourceType); @@ -572,7 +568,7 @@ public static Expression GetAsCall(this IMemberMapperData mapperData, Type sourc => GetAsCall(mapperData.MappingDataObject, sourceType, targetType); public static Expression GetAsCall(this Expression subject, params Type[] contextTypes) - => GetAsCall(subject, null, contextTypes); + => GetAsCall(subject, true.ToConstantExpression(), contextTypes); public static Expression GetAsCall(this Expression subject, Expression isForDerivedTypeArgument, params Type[] contextTypes) { @@ -584,12 +580,9 @@ public static Expression GetAsCall(this Expression subject, Expression isForDeri if (subject.Type == typeof(IMappingData)) { - return GetAsCall(subject, typeof(IMappingData).GetPublicInstanceMethod("As"), contextTypes); - } - - if (isForDerivedTypeArgument == null) - { - isForDerivedTypeArgument = true.ToConstantExpression(); + return Expression.Call( + subject, + typeof(IMappingData).GetPublicInstanceMethod("As").MakeGenericMethod(contextTypes)); } MethodInfo conversionMethod; @@ -607,18 +600,10 @@ public static Expression GetAsCall(this Expression subject, Expression isForDeri conversionMethod = typeof(IObjectMappingDataUntyped).GetPublicInstanceMethod("As"); } - return GetAsCall(subject, conversionMethod, contextTypes, isForDerivedTypeArgument); - } - - private static Expression GetAsCall( - Expression subject, - MethodInfo asMethod, - Type[] typeArguments, - Expression isForDerivedTypeArgument = null) - { - return (isForDerivedTypeArgument != null) - ? Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments), isForDerivedTypeArgument) - : Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments)); + return Expression.Call( + subject, + conversionMethod.MakeGenericMethod(contextTypes), + isForDerivedTypeArgument); } public static Expression GetSourceAccess( diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index 7fedbe44c..0844e5cc8 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -3,6 +3,11 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using System; using System.Collections.Generic; using System.Linq; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using System.Reflection; using Caching; using Configuration; @@ -13,11 +18,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using MapperKeys; using Members; using NetStandardPolyfills; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif using static System.StringComparison; internal class ComplexTypeConstructionFactory diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs index 61fc364dd..aab418bf8 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs @@ -1,16 +1,16 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes { using System.Collections.Generic; - using Extensions.Internal; - using Members; - using NetStandardPolyfills; - using ReadableExpressions; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; + using NetStandardPolyfills; + using ReadableExpressions; + using ReadableExpressions.Extensions; internal class ComplexTypeMappingExpressionFactory : MappingExpressionFactoryBase { @@ -42,7 +42,7 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out return base.TargetCannotBeMapped(mappingData, out nullMappingBlock); } - if (mappingData.MapperData.MapperContext.ConstructionFactory.GetNewObjectCreation(mappingData) != null) + if (mappingData.IsTargetConstructable()) { return base.TargetCannotBeMapped(mappingData, out nullMappingBlock); } diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs index 5db9a1ad3..8b2ffa94a 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs @@ -104,11 +104,7 @@ protected virtual Expression GetNewObjectCreation( IObjectMappingData mappingData, IList memberPopulations) { - return mappingData - .MapperData - .MapperContext - .ConstructionFactory - .GetNewObjectCreation(mappingData); + return mappingData.GetTargetObjectCreation(); } #region Object Registration diff --git a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs index bc95d39e7..d3e5663a9 100644 --- a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs @@ -3,16 +3,16 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System; using System.Collections.Generic; using System.Linq; - using Configuration; - using Extensions; - using Extensions.Internal; - using Members; - using NetStandardPolyfills; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Configuration; + using Extensions; + using Extensions.Internal; + using Members; + using NetStandardPolyfills; internal static class DerivedComplexTypeMappingsFactory { @@ -27,7 +27,7 @@ public static Expression CreateFor(IObjectMappingData declaredTypeMappingData) var derivedSourceTypes = declaredTypeMapperData.RuleSet.Settings.CheckDerivedSourceTypes ? declaredTypeMapperData.GetDerivedSourceTypes() - : Enumerable.EmptyArray; + : Constants.EmptyTypeArray; var derivedTargetTypes = GetDerivedTargetTypesIfNecessary(declaredTypeMappingData); var derivedTypePairs = GetTypePairsFor(declaredTypeMapperData, declaredTypeMapperData); @@ -89,7 +89,7 @@ private static ICollection GetDerivedTargetTypesIfNecessary(IObjectMapping { if (mappingData.MapperData.TargetIsDefinitelyUnpopulated()) { - return Enumerable.EmptyArray; + return Constants.EmptyTypeArray; } return mappingData.MapperData.GetDerivedTargetTypes(); @@ -110,12 +110,26 @@ private static void AddDeclaredSourceTypeMappings( { var condition = GetTypePairCondition(derivedTypePair, declaredTypeMapperData); + var sourceValue = GetDerivedTypeSourceValue( + derivedTypePair, + declaredTypeMappingData, + out var sourceValueCondition); + var derivedTypeMapping = DerivedMappingFactory.GetDerivedTypeMapping( declaredTypeMappingData, - declaredTypeMapperData.SourceObject, + sourceValue, derivedTypePair.DerivedTargetType); + if (sourceValueCondition != null) + { + derivedTypeMapping = Expression.Condition( + sourceValueCondition, + derivedTypeMapping, + derivedTypeMapping.Type.ToDefaultExpression()); + } + var returnMappingResult = Expression.Return(declaredTypeMapperData.ReturnLabelTarget, derivedTypeMapping); + declaredTypeHasUnconditionalTypePair = (condition == null); if (declaredTypeHasUnconditionalTypePair) @@ -132,20 +146,59 @@ private static void AddDeclaredSourceTypeMappings( declaredTypeHasUnconditionalTypePair = false; } - private static Expression GetTypePairCondition(DerivedTypePair derivedTypePair, IMemberMapperData mapperData) + private static Expression GetTypePairCondition(DerivedTypePair derivedTypePair, IMemberMapperData declaredTypeMapperData) { - var condition = GetTargetValidCheckOrNull(derivedTypePair.DerivedTargetType, mapperData); + var condition = GetTargetValidCheckOrNull(derivedTypePair.DerivedTargetType, declaredTypeMapperData); if (!derivedTypePair.HasConfiguredCondition) { return condition; } - var pairCondition = derivedTypePair.GetConditionOrNull(mapperData); + var pairCondition = derivedTypePair.GetConditionOrNull(declaredTypeMapperData); return (condition != null) ? Expression.AndAlso(pairCondition, condition) : pairCondition; } + private static Expression GetDerivedTypeSourceValue( + DerivedTypePair derivedTypePair, + IObjectMappingData declaredTypeMappingData, + out Expression sourceValueCondition) + { + if (!derivedTypePair.IsImplementationPairing) + { + sourceValueCondition = null; + return declaredTypeMappingData.MapperData.SourceObject; + } + + var implementationMappingData = declaredTypeMappingData + .WithTypes(derivedTypePair.DerivedSourceType, derivedTypePair.DerivedTargetType); + + if (implementationMappingData.IsTargetConstructable()) + { + sourceValueCondition = null; + return declaredTypeMappingData.MapperData.SourceObject; + } + + // Derived Type is an implementation Type for an unconstructable target Type, + // and is itself unconstructable; only way we get here is if a ToTarget data + // source has been configured: + var toTargetDataSource = implementationMappingData + .GetToTargetDataSourceOrNullForTargetType(); + + sourceValueCondition = toTargetDataSource.IsConditional + ? toTargetDataSource.Condition.Replace( + implementationMappingData.MapperData.SourceObject, + declaredTypeMappingData.MapperData.SourceObject, + ExpressionEvaluation.Equivalator) + : null; + + return toTargetDataSource.Value.Replace( + implementationMappingData.MapperData.SourceObject, + declaredTypeMappingData.MapperData.SourceObject, + ExpressionEvaluation.Equivalator); + } + private static void AddDerivedSourceTypeMappings( ICollection derivedSourceTypes, IObjectMappingData declaredTypeMappingData, diff --git a/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs b/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs index a08975c5c..3b2cb6a87 100644 --- a/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs @@ -1,13 +1,13 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; internal static class DerivedMappingFactory { diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs index 5ec29b736..4110298b1 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs @@ -26,7 +26,7 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out return base.TargetCannotBeMapped(mappingData, out nullMappingBlock); } - if (HasConfiguredRootDataSources(mapperData, out var configuredRootDataSources) && + if (HasConfiguredToTargetDataSources(mapperData, out var configuredRootDataSources) && configuredRootDataSources.Any(ds => ds.SourceMember.IsEnumerable)) { return base.TargetCannotBeMapped(mappingData, out nullMappingBlock); diff --git a/AgileMapper/ObjectPopulation/IObjectMappingData.cs b/AgileMapper/ObjectPopulation/IObjectMappingData.cs index 596abe2fe..59ac92578 100644 --- a/AgileMapper/ObjectPopulation/IObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/IObjectMappingData.cs @@ -103,6 +103,19 @@ TTargetElement Map( TTargetElement targetElement, int enumerableIndex); + /// + /// Gets the as an + /// using the given + /// . + /// + /// The type of the new source object to use. + /// The new source object to use. + /// + /// The as a + /// . + /// + IObjectMappingData WithSource(TNewSource newSource); + /// /// Gets the typed as a /// when the target object definitely diff --git a/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs b/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs index 4f31c3cdd..2f01a2a5f 100644 --- a/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs +++ b/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs @@ -92,7 +92,7 @@ TDeclaredTarget MapRepeated( int enumerableIndex); /// - /// Gets the typed as a + /// Gets the typed as an /// . /// /// The type of source object being mapped in the current context. diff --git a/AgileMapper/ObjectPopulation/MappingCreationContext.cs b/AgileMapper/ObjectPopulation/MappingCreationContext.cs index 2172f2696..62fba858d 100644 --- a/AgileMapper/ObjectPopulation/MappingCreationContext.cs +++ b/AgileMapper/ObjectPopulation/MappingCreationContext.cs @@ -12,35 +12,36 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using DataSources; using Extensions; using Members; + using static CallbackPosition; internal class MappingCreationContext { private bool _mapperDataHasRootEnumerableVariables; private List _memberMappingExpressions; - public MappingCreationContext( - IObjectMappingData mappingData, - Expression mapToNullCondition = null, - List mappingExpressions = null) - : this(mappingData, null, null, mapToNullCondition, mappingExpressions) + public MappingCreationContext(IObjectMappingData mappingData) { - } + var mapperData = mappingData.MapperData; - public MappingCreationContext( - IObjectMappingData mappingData, - Expression preMappingCallback, - Expression postMappingCallback, - Expression mapToNullCondition, - List mappingExpressions = null) - { MappingData = mappingData; - PreMappingCallback = preMappingCallback; - PostMappingCallback = postMappingCallback; - MapToNullCondition = mapToNullCondition; + MapToNullCondition = GetMapToNullConditionOrNull(mapperData); InstantiateLocalVariable = true; - MappingExpressions = mappingExpressions ?? new List(); + MappingExpressions = new List(); + + if (mapperData.RuleSet.Settings.UseSingleRootMappingExpression) + { + return; + } + + var basicMapperData = mapperData.WithNoTargetMember(); + + PreMappingCallback = basicMapperData.GetMappingCallbackOrNull(Before, mapperData); + PostMappingCallback = basicMapperData.GetMappingCallbackOrNull(After, mapperData); } + private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData) + => mapperData.MapperContext.UserConfigurations.GetMapToNullConditionOrNull(mapperData); + public MapperContext MapperContext => MapperData.MapperContext; public MappingRuleSet RuleSet => MappingData.MappingContext.RuleSet; @@ -106,7 +107,7 @@ public MappingCreationContext WithDataSource(IDataSource newDataSource) { var newSourceMappingData = MappingData.WithSource(newDataSource.SourceMember); - var newContext = new MappingCreationContext(newSourceMappingData, mappingExpressions: MappingExpressions) + var newContext = new MappingCreationContext(newSourceMappingData) { InstantiateLocalVariable = false }; diff --git a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs index d7067d4f8..3d5d0bb14 100644 --- a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs @@ -1,16 +1,36 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Diagnostics; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; + using NetStandardPolyfills; internal static class MappingDataCreationFactory { + [DebuggerStepThrough] + public static Expression ForToTarget( + ObjectMapperData parentMapperData, + Expression toTargetDataSource) + { + var withSourceMethod = parentMapperData + .MappingDataObject + .Type + .GetPublicInstanceMethod("WithSource") + .MakeGenericMethod(toTargetDataSource.Type); + + var withSourceCall = Expression.Call( + parentMapperData.MappingDataObject, + withSourceMethod, + toTargetDataSource); + + return withSourceCall; + } + [DebuggerStepThrough] public static Expression ForDerivedType(ObjectMapperData childMapperData) { @@ -50,16 +70,13 @@ public static Expression ForChild( .ForChildMethod .MakeGenericMethod(childMapperData.SourceType, childMapperData.TargetType); - var targetMemberRegistrationName = childMapperData.TargetMember.RegistrationName.ToConstantExpression(); - var dataSourceIndexConstant = dataSourceIndex.ToConstantExpression(); - var createCall = Expression.Call( createMethod, mappingValues.SourceValue, mappingValues.TargetValue, mappingValues.EnumerableIndex, - targetMemberRegistrationName, - dataSourceIndexConstant, + childMapperData.TargetMember.RegistrationName.ToConstantExpression(), + dataSourceIndex.ToConstantExpression(), childMapperData.Parent.MappingDataObject); return createCall; diff --git a/AgileMapper/ObjectPopulation/MappingDataFactory.cs b/AgileMapper/ObjectPopulation/MappingDataFactory.cs index 961e260f4..ac2334c70 100644 --- a/AgileMapper/ObjectPopulation/MappingDataFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingDataFactory.cs @@ -13,10 +13,10 @@ internal static class MappingDataFactory private static MethodInfo _forElementMethod; public static MethodInfo ForChildMethod => - _forChildMethod ?? (_forChildMethod = typeof(MappingDataFactory).GetPublicStaticMethod("ForChild")); + _forChildMethod ?? (_forChildMethod = typeof(MappingDataFactory).GetPublicStaticMethod(nameof(ForChild))); public static MethodInfo ForElementMethod - => _forElementMethod ?? (_forElementMethod = typeof(MappingDataFactory).GetPublicStaticMethod("ForElement")); + => _forElementMethod ?? (_forElementMethod = typeof(MappingDataFactory).GetPublicStaticMethod(nameof(ForElement))); public static ObjectMappingData ForChild( TSource source, diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 6264f8bff..65c24aa42 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -3,6 +3,11 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System; using System.Collections.Generic; using System.Linq; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using DataSources; using Extensions; using Extensions.Internal; @@ -10,13 +15,10 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using NetStandardPolyfills; using ReadableExpressions.Extensions; #if NET35 - using Microsoft.Scripting.Ast; using static Microsoft.Scripting.Ast.ExpressionType; #else - using System.Linq.Expressions; using static System.Linq.Expressions.ExpressionType; #endif - using static CallbackPosition; internal abstract class MappingExpressionFactoryBase { @@ -44,13 +46,11 @@ public Expression Create(IObjectMappingData mappingData) : derivedTypeMappings; } - var context = GetCreationContext(mappingData); + var context = new MappingCreationContext(mappingData); context.MappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings); - context.MappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); - context.MappingExpressions.AddRange(GetNonNullObjectPopulation(context)); - context.MappingExpressions.AddRange(GetConfiguredRootDataSourcePopulations(context)); - context.MappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); + + AddPopulationsAndCallbacks(context); if (NothingIsBeingMapped(context)) { @@ -94,69 +94,59 @@ private bool MappingAlwaysBranchesToDerivedType(IObjectMappingData mappingData, protected virtual IEnumerable GetShortCircuitReturns(GotoExpression returnNull, IObjectMappingData mappingData) => Enumerable.Empty; - private static MappingCreationContext GetCreationContext(IObjectMappingData mappingData) + private void AddPopulationsAndCallbacks(MappingCreationContext context) { - var mapperData = mappingData.MapperData; - var mapToNullCondition = GetMapToNullConditionOrNull(mapperData); - - if (mapperData.RuleSet.Settings.UseSingleRootMappingExpression) - { - return new MappingCreationContext(mappingData, mapToNullCondition); - } - - var basicMapperData = mapperData.WithNoTargetMember(); - var preMappingCallback = basicMapperData.GetMappingCallbackOrNull(Before, mapperData); - var postMappingCallback = basicMapperData.GetMappingCallbackOrNull(After, mapperData); - - return new MappingCreationContext( - mappingData, - preMappingCallback, - postMappingCallback, - mapToNullCondition); + context.MappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); + context.MappingExpressions.AddRange(GetNonNullObjectPopulation(context)); + context.MappingExpressions.AddRange(GetConfiguredToTargetDataSourceMappings(context)); + context.MappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); } - private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData) - => mapperData.MapperContext.UserConfigurations.GetMapToNullConditionOrNull(mapperData); - private IEnumerable GetNonNullObjectPopulation(MappingCreationContext context) => GetObjectPopulation(context).WhereNotNull(); protected abstract IEnumerable GetObjectPopulation(MappingCreationContext context); - private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCreationContext context) + private IEnumerable GetConfiguredToTargetDataSourceMappings(MappingCreationContext context) { - if (!HasConfiguredRootDataSources(context.MapperData, out var configuredRootDataSources)) + if (!HasConfiguredToTargetDataSources(context.MapperData, out var configuredToTargetDataSources)) { yield break; } - for (var i = 0; i < configuredRootDataSources.Count; ++i) + for (var i = 0; i < configuredToTargetDataSources.Count;) { - var configuredRootDataSource = configuredRootDataSources[i]; - var newSourceContext = context.WithDataSource(configuredRootDataSource); + var configuredToTargetDataSource = configuredToTargetDataSources[i++]; + var newSourceContext = context.WithDataSource(configuredToTargetDataSource); - var memberPopulations = GetNonNullObjectPopulation(newSourceContext).ToArray(); + AddPopulationsAndCallbacks(newSourceContext); - if (memberPopulations.None()) + if (newSourceContext.MappingExpressions.None()) { continue; } context.UpdateFrom(newSourceContext); - var mapping = memberPopulations.HasOne() - ? memberPopulations.First() - : Expression.Block(memberPopulations); + var mapping = newSourceContext.MappingExpressions.HasOne() + ? newSourceContext.MappingExpressions.First() + : Expression.Block(newSourceContext.MappingExpressions); + + mapping = MappingFactory.UseLocalToTargetDataSourceVariableIfAppropriate( + context.MapperData, + newSourceContext.MapperData, + configuredToTargetDataSource.Value, + mapping); - if (!configuredRootDataSource.IsConditional) + if (!configuredToTargetDataSource.IsConditional) { yield return mapping; continue; } - if (context.MapperData.TargetMember.IsComplex || (i > 0)) + if (context.MapperData.TargetMember.IsComplex || (i > 1)) { - yield return Expression.IfThen(configuredRootDataSource.Condition, mapping); + yield return Expression.IfThen(configuredToTargetDataSource.Condition, mapping); continue; } @@ -166,11 +156,11 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr var assignFallback = context.MapperData.LocalVariable.AssignTo(fallback); - yield return Expression.IfThenElse(configuredRootDataSource.Condition, mapping, assignFallback); + yield return Expression.IfThenElse(configuredToTargetDataSource.Condition, mapping, assignFallback); } } - protected static bool HasConfiguredRootDataSources(IMemberMapperData mapperData, out IList dataSources) + protected static bool HasConfiguredToTargetDataSources(IMemberMapperData mapperData, out IList dataSources) { dataSources = mapperData .MapperContext diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index f190f377f..2ccd93b48 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -182,7 +182,7 @@ public static Expression GetInlineMappingBlock( if (mapper.MapperData.Context.UsesMappingDataObject) { - return UseLocalSourceValueVariable( + return UseLocalValueVariable( mapper.MapperData.MappingDataObject, createMappingDataCall, mapper.MappingExpression); @@ -230,7 +230,7 @@ private static Expression GetDirectAccessMapping( .Replace(mapperData.MappingDataObject, createMappingDataCall); return useLocalSourceValueVariable - ? UseLocalSourceValueVariable((ParameterExpression)sourceValue, sourceValueVariableValue, mapping) + ? UseLocalValueVariable((ParameterExpression)sourceValue, sourceValueVariableValue, mapping) : mapping; } @@ -282,14 +282,31 @@ public static Expression UseLocalSourceValueVariableIfAppropriate( var sourceValueVariableName = GetSourceValueVariableName(mapperData); var sourceValueVariable = Expression.Variable(mapperData.SourceType, sourceValueVariableName); - return UseLocalSourceValueVariable( + return UseLocalValueVariable( sourceValueVariable, mapperData.SourceObject, mappingExpression, performValueReplacement: true); } - private static Expression UseLocalSourceValueVariable( + public static Expression UseLocalToTargetDataSourceVariableIfAppropriate( + ObjectMapperData mapperData, + ObjectMapperData toTargetMapperData, + Expression toTargetDataSourceValue, + Expression mappingExpression) + { + if (!toTargetMapperData.Context.UsesMappingDataObject) + { + return mappingExpression; + } + + return UseLocalValueVariable( + toTargetMapperData.MappingDataObject, + MappingDataCreationFactory.ForToTarget(mapperData, toTargetDataSourceValue), + mappingExpression); + } + + private static Expression UseLocalValueVariable( ParameterExpression variable, Expression variableValue, Expression body, diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index b1521efaa..ead7fd1e6 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -64,7 +64,7 @@ private ObjectMapperData( var mappingDataType = typeof(IMappingData<,>).MakeGenericType(SourceType, TargetType); SourceObject = GetMappingDataProperty(mappingDataType, RootSourceMemberName); TargetObject = GetMappingDataProperty(RootTargetMemberName); - CreatedObject = GetMappingDataProperty("CreatedObject"); + CreatedObject = GetMappingDataProperty(nameof(CreatedObject)); var isPartOfDerivedTypeMapping = declaredTypeMapperData != null; @@ -75,8 +75,8 @@ private ObjectMapperData( } else { - EnumerableIndex = GetMappingDataProperty(mappingDataType, "EnumerableIndex"); - ParentObject = GetMappingDataProperty("Parent"); + EnumerableIndex = GetMappingDataProperty(mappingDataType, nameof(EnumerableIndex)); + ParentObject = GetMappingDataProperty(nameof(Parent)); } ExpressionInfoFinder = new ExpressionInfoFinder(MappingDataObject); diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index be3ab6ce1..f21e16dfd 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -348,16 +348,19 @@ public void Register(TKey key, TComplex complexType) _mappedObjectsBySource[key] = new List { complexType }; } + public IObjectMappingData WithSource(TNewSource newSource) + => With(newSource, Target, isForDerivedTypeMapping: false); + public IObjectMappingData WithSourceType(bool isForDerivedTypeMapping) where TNewSource : class { - return As(Source as TNewSource, default(TNewTarget), isForDerivedTypeMapping); + return With(Source as TNewSource, default(TNewTarget), isForDerivedTypeMapping); } public IObjectMappingData WithTargetType(bool isForDerivedTypeMapping) where TNewTarget : class { - return As(default(TNewSource), Target as TNewTarget, isForDerivedTypeMapping); + return With(default(TNewSource), Target as TNewTarget, isForDerivedTypeMapping); } public IObjectMappingData WithSource(IQualifiedMember newSourceMember) @@ -401,25 +404,23 @@ public IObjectMappingData WithTypes(Type newSourceType, Type newTargetType, bool return (IObjectMappingData)typedAsCaller.Invoke(this, isForDerivedTypeMapping); } - public IObjectMappingData As() - where TNewSource : class - where TNewTarget : class - { - return As(isForDerivedTypeMapping: true); - } - public IObjectMappingData As(bool isForDerivedTypeMapping) where TNewSource : class where TNewTarget : class { - return As(Source as TNewSource, Target as TNewTarget, isForDerivedTypeMapping); + return With(Source as TNewSource, Target as TNewTarget, isForDerivedTypeMapping); } - private IObjectMappingData As( + private IObjectMappingData With( TNewSource typedSource, TNewTarget typedTarget, bool isForDerivedTypeMapping) { + if (MapperKey == null) + { + EnsureRootMapperKey(); + } + var forceNewKey = isForDerivedTypeMapping && MapperKey.MappingTypes.TargetType.IsInterface(); var mapperKey = MapperKey.WithTypes(typeof(TNewSource), typeof(TNewTarget), forceNewKey); diff --git a/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs b/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs index 1a4942fca..1dee5c560 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs @@ -124,10 +124,10 @@ public static IObjectMappingData ForChild( { var bridgeParameter = Expression.Parameter(typeof(IObjectMappingDataFactoryBridge), "bridge"); var childMembersSourceParameter = Expression.Parameter(typeof(object), "childMembersSource"); - var parentParameter = Expression.Parameter(typeof(object), "parent"); + var parentParameter = Expression.Parameter(typeof(object), nameof(parent)); var typedForChildMethod = bridgeParameter.Type - .GetPublicInstanceMethod("ForChild") + .GetPublicInstanceMethod(nameof(ForChild)) .MakeGenericMethod(k.SourceType, k.TargetType); var typedForChildCall = Expression.Call(