diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index 628fd0feb..c2bdfaf12 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -340,7 +340,7 @@ public void ShouldApplyAConfiguredRootSourceMember() .MapUsing(mapper) .ToANew>(cfg => cfg .Map((s, ptf) => s.Value) - .ToRootTarget()); + .ToTarget()); result.Value1.ShouldBe(8392); result.Value2.ShouldBe(5482); @@ -361,7 +361,7 @@ public void ShouldApplyAConfiguredRootSourceObjectMember() .Map(source1) .ToANew>(cfg => cfg .Map((s, t) => s.Value) - .ToRootTarget()); + .ToTarget()); result1.Value.ShouldBe("Hello!"); @@ -374,7 +374,7 @@ public void ShouldApplyAConfiguredRootSourceObjectMember() .Map(source2) .ToANew>(cfg => cfg .Map((s, t) => s.Value) - .ToRootTarget()); + .ToTarget()); result2.Value.ShouldBe("Goodbye!"); diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 1e70ee7cf..5e253594c 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -996,7 +996,7 @@ public void ShouldAllowIdAndIdentifierConfiguration() // See https://github.com/agileobjects/AgileMapper/issues/64 [Fact] - public void ShouldApplyAConfiguredRootSourceMember() + public void ShouldApplyAConfiguredRootSource() { using (var mapper = Mapper.CreateNew()) { @@ -1005,8 +1005,8 @@ public void ShouldApplyAConfiguredRootSourceMember() mapper.WhenMapping .From(source) .To>() - .Map((s, ptf) => s.Value) - .ToRootTarget(); + .Map(ctx => ctx.Source.Value) + .ToTarget(); var result = source .MapUsing(mapper) @@ -1018,7 +1018,7 @@ public void ShouldApplyAConfiguredRootSourceMember() } [Fact] - public void ShouldApplyANestedOverwriteConfiguredRootSourceMember() + public void ShouldApplyANestedOverwriteConfiguredRootSource() { using (var mapper = Mapper.CreateNew()) { @@ -1026,7 +1026,7 @@ public void ShouldApplyANestedOverwriteConfiguredRootSourceMember() .From>>>() .Over>() .Map((s, t) => s.Value2.Value) - .ToRootTarget(); + .ToTarget(); var source = new PublicTwoFields>> { @@ -1054,7 +1054,7 @@ public void ShouldApplyANestedOverwriteConfiguredRootSourceMember() } [Fact] - public void ShouldHandleAConfiguredRootSourceMemberNullValue() + public void ShouldHandleAConfiguredRootSourceNullValue() { using (var mapper = Mapper.CreateNew()) { @@ -1065,7 +1065,7 @@ public void ShouldHandleAConfiguredRootSourceMemberNullValue() .To(t => t.Value1) .And .Map((mc, t) => mc.Address) - .ToRootTarget(); + .ToTarget(); var source = new MysteryCustomer { Name = "Nelly", Address = default(Address) }; @@ -1077,7 +1077,7 @@ public void ShouldHandleAConfiguredRootSourceMemberNullValue() } [Fact] - public void ShouldApplyAConfiguredRootSourceMemberConditionally() + public void ShouldApplyAConfiguredRootSourceConditionally() { using (var mapper = Mapper.CreateNew()) { @@ -1086,7 +1086,7 @@ public void ShouldApplyAConfiguredRootSourceMemberConditionally() .OnTo>() .If((s, t) => s.Value1.Value > 5) .Map((s, t) => s.Value1) - .ToRootTarget(); + .ToTarget(); mapper.WhenMapping .From>() @@ -1122,8 +1122,94 @@ public void ShouldApplyAConfiguredRootSourceMemberConditionally() } } + // See https://github.com/agileobjects/AgileMapper/issues/68 [Fact] - public void ShouldApplyAConfiguredRootSourceEnumerableMember() + public void ShouldSupportConfiguringARootSourceUsingMappingContext() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .To() + .Map(ctx => ctx.Source.Statistics) + .ToTarget(); + + var source = new Model + { + SomeOtherProperties = "jyutrgf", + Statistics = new Statistics + { + Ranking = 0.5f, + SomeOtherRankingStuff = "uityjtgrf" + } + }; + + var result = mapper.Map(source).ToANew(); + + result.SomeOtherProperties.ShouldBe("jyutrgf"); + result.Ranking.ShouldBe(0.5f); + result.SomeOtherRankingStuff.ShouldBe("uityjtgrf"); + } + } + + [Fact] + public void ShouldApplyAConfiguredRootSourceToANestedMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToTarget(); + + var source = new PublicField>> + { + Value = new PublicField> + { + Value = new PublicField { Value = "53632" } + } + }; + + var result = mapper.Map(source).ToANew>>(); + + result.Value.Value.ShouldBe(53632); + } + } + + [Fact] + public void ShouldApplyAConfiguredRootSourceToAnEnumerableElement() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>>() + .ToANew>() + .Map(ctx => ctx.Source.Value) + .ToTarget(); + + var source = new[] + { + new PublicField> + { + Value = new PublicField { Value = "kjfcrkjnad" } + }, + new PublicField> + { + Value = new PublicField { Value = "owkjwsnbsgtf" } + } + }; + + var result = mapper.Map(source).ToANew>>(); + + result.Count.ShouldBe(2); + result.First().Value.ShouldBe("kjfcrkjnad"); + result.Second().Value.ShouldBe("owkjwsnbsgtf"); + } + } + + [Fact] + public void ShouldApplyAConfiguredEnumerableRootSource() { using (var mapper = Mapper.CreateNew()) { @@ -1131,7 +1217,7 @@ public void ShouldApplyAConfiguredRootSourceEnumerableMember() .From>() .To>() .Map((s, r) => s.Value2) - .ToRootTarget(); + .ToTarget(); var source = new PublicTwoFields { @@ -1160,7 +1246,7 @@ public void ShouldApplyAConfiguredRootSourceEnumerableMember() } [Fact] - public void ShouldApplyMultipleConfiguredRootSourceComplexTypeMembers() + public void ShouldApplyMultipleConfiguredComplexTypeRootSources() { using (var mapper = Mapper.CreateNew()) { @@ -1174,10 +1260,10 @@ public void ShouldApplyMultipleConfiguredRootSourceComplexTypeMembers() .From(source) .To>() .Map((s, t) => s.PropertyOne) - .ToRootTarget() + .ToTarget() .And .Map((s, t) => s.PropertyTwo) - .ToRootTarget(); + .ToTarget(); var result = mapper.Map(source).ToANew>(); @@ -1187,18 +1273,18 @@ public void ShouldApplyMultipleConfiguredRootSourceComplexTypeMembers() } [Fact] - public void ShouldApplyMultipleConfiguredRootSourceEnumerableMembers() + public void ShouldApplyMultipleConfiguredEnumerableRootSources() { using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping .From>() .To() - .Map((s, t) => s.Value1) - .ToRootTarget() + .Map(xtx => xtx.Source.Value1) + .ToTarget() .And .Map((s, t) => s.Value2) - .ToRootTarget(); + .ToTarget(); var source = new PublicTwoFields { @@ -1212,14 +1298,34 @@ public void ShouldApplyMultipleConfiguredRootSourceEnumerableMembers() } } - // ReSharper disable once ClassNeverInstantiated.Local - // ReSharper disable UnusedAutoPropertyAccessor.Local - private class IdTester + internal class IdTester { public int ClassId { get; set; } public int ClassIdentifier { get; set; } } - // ReSharper restore UnusedAutoPropertyAccessor.Local + + internal class Statistics + { + public float Ranking { get; set; } + + public string SomeOtherRankingStuff { get; set; } + } + + internal class Model + { + public string SomeOtherProperties { get; set; } + + public Statistics Statistics { get; set; } + } + + internal class ModelDto + { + public string SomeOtherProperties { get; set; } + + public float Ranking { get; set; } + + public string SomeOtherRankingStuff { get; set; } + } } } diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs index 04539d17d..5c205d004 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs @@ -254,6 +254,44 @@ public void ShouldErrorIfUnconvertibleConstructorValueConstantSpecified() configurationException.Message.ShouldContain("Unable to convert"); } + [Fact] + public void ShouldErrorIfSimpleTypeConfiguredForComplexTarget() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .To() + .Map((s, t) => s.Id) + .To(t => t.Address); + } + }); + + configurationException.Message.ShouldContain( + "Person.Id of type 'Guid' cannot be mapped to target type 'Address'"); + } + + [Fact] + public void ShouldErrorIfSimpleTypeConfiguredForEnumerableTarget() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map((s, t) => s.Value) + .To(t => t.Value1); + } + }); + + configurationException.Message.ShouldContain( + "PublicField.Value of type 'int' cannot be mapped to target type 'int[]'"); + } + [Fact] public void ShouldErrorIfTargetParameterConfiguredAsTarget() { @@ -283,12 +321,12 @@ public void ShouldErrorIfRootTargetSimpleTypeConstantDataSourceConfigured() .From>() .To>() .Map("No no no no no") - .ToRootTarget(); + .ToTarget(); } }); configurationException.Message.ShouldContain( - "'string' cannot be mapped to root target type 'PublicField'"); + "'string' cannot be mapped to target type 'PublicField'"); } [Fact] @@ -302,14 +340,14 @@ public void ShouldErrorIfRootTargetSimpleTypeMemberDataSourceConfigured() .From>() .To>() .Map(ctx => ctx.Source.Value) - .ToRootTarget(); + .ToTarget(); } }); configurationException.Message.ShouldContain("PublicProperty.Value"); configurationException.Message.ShouldContain( - "'int' cannot be mapped to root target type 'PublicField'"); + "'int' cannot be mapped to target type 'PublicField'"); } } } \ No newline at end of file diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index 11bb7c2d0..0c961e59e 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -346,7 +346,7 @@ public void ShouldApplyACustomConfiguredMemberConditionally() } [Fact] - public void ShouldApplyAConfiguredRootSourceMember() + public void ShouldApplyAConfiguredRootSource() { using (var mapper = Mapper.CreateNew()) { @@ -354,7 +354,7 @@ public void ShouldApplyAConfiguredRootSourceMember() .From>() .ToDictionaries .Map((pf, d) => pf.Value) - .ToRootTarget(); + .ToTarget(); var source = new PublicField
{ @@ -369,5 +369,33 @@ public void ShouldApplyAConfiguredRootSourceMember() result["Line2"].ShouldBe("Here too!"); } } + + [Fact] + public void ShouldApplyAConfiguredRootSourceToANestedMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDictionariesWithValueType() + .Map((pf, d) => pf.Value) + .ToTarget(); + + var source = new PublicProperty> + { + Value = new PublicField
+ { + Value = new Address { Line1 = "Where?!", Line2 = "Here too!" } + } + }; + + var result = mapper.Map(source).ToANew>>(); + + result.Value["Value.Line1"].ShouldBe("Where?!"); + result.Value["Value.Line2"].ShouldBe("Here too!"); + result.Value["Line1"].ShouldBe("Where?!"); + result.Value["Line2"].ShouldBe("Here too!"); + } + } } } \ No newline at end of file diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index a304d62dc..d31e45ae8 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -346,7 +346,7 @@ public void ShouldNotConflictTargetDynamicAndDictionaryConfiguration() } [Fact] - public void ShouldApplyAConfiguredRootSourceMember() + public void ShouldApplyAConfiguredRootSource() { using (var mapper = Mapper.CreateNew()) { @@ -354,7 +354,7 @@ public void ShouldApplyAConfiguredRootSourceMember() .From>() .ToDynamics .Map((pf, d) => pf.Value) - .ToRootTarget(); + .ToTarget(); var source = new PublicField
{ @@ -369,5 +369,34 @@ public void ShouldApplyAConfiguredRootSourceMember() ((string)result.Line2).ShouldBe("There too!"); } } + + [Fact] + public void ShouldApplyAConfiguredRootSourceToANestedMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDynamics + .Map((pf, d) => pf.Value) + .ToTarget(); + + var source = new PublicProperty> + { + Value = new PublicField
+ { + Value = new Address { Line1 = "Where?!", Line2 = "Here too!" } + } + }; + + var result = mapper.Map(source).ToANew>(); + + var targetDynamicDictionary = (IDictionary)result.Value; + targetDynamicDictionary["Value_Line1"].ShouldBe("Where?!"); + targetDynamicDictionary["Value_Line2"].ShouldBe("Here too!"); + targetDynamicDictionary["Line1"].ShouldBe("Where?!"); + targetDynamicDictionary["Line2"].ShouldBe("Here too!"); + } + } } } diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index cbe166a28..8f26eebc2 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -13,7 +13,6 @@ using Members.Dictionaries; using NetStandardPolyfills; using Projection; - using ReadableExpressions; using ReadableExpressions.Extensions; @@ -45,6 +44,7 @@ public IMappingConfigContinuation To( Expression> targetMember) { ThrowIfTargetParameterSpecified(targetMember); + ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TTargetValue)); return RegisterDataSource(() => CreateFromLambda(targetMember)); } @@ -52,20 +52,36 @@ public IMappingConfigContinuation To( IProjectionConfigContinuation ICustomProjectionDataSourceTargetMemberSpecifier.To( Expression> resultMember) { + ThrowIfTargetParameterSpecified(resultMember); + ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TResultValue)); + return RegisterDataSource(() => CreateFromLambda(resultMember)); } public IMappingConfigContinuation To( Expression>> targetSetMethod) - => RegisterDataSource(() => CreateFromLambda(targetSetMethod)); + { + ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TTargetValue)); + + return RegisterDataSource(() => CreateFromLambda(targetSetMethod)); + } - private static void ThrowIfTargetParameterSpecified(LambdaExpression targetParameter) + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private static void ThrowIfTargetParameterSpecified(LambdaExpression targetMember) { - if (targetParameter.Body == targetParameter.Parameters.FirstOrDefault()) + if (targetMember.Body == targetMember.Parameters.FirstOrDefault()) { throw new MappingConfigurationException( "The target parameter is not a valid configured target; use .ToTarget() " + - "to map a custom data source to the root target object"); + "to map a custom data source to the target object"); + } + } + + private void ThrowIfSimpleSourceForNonSimpleTargetMember(Type targetMemberType) + { + if ((targetMemberType != typeof(object)) && !targetMemberType.IsSimple()) + { + ThrowIfSimpleSource(targetMemberType); } } @@ -110,16 +126,18 @@ private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out Dictiona var entryKey = (string)((ConstantExpression)entryKeyExpression).Value; - var rootMember = (DictionaryTargetMember)(_configInfo.TargetType == typeof(ExpandoObject) - ? CreateRootTargetQualifiedMember() - : CreateRootTargetQualifiedMember()); + var rootMember = (DictionaryTargetMember)CreateRootTargetQualifiedMember(); entryMember = rootMember.Append(typeof(TSource), entryKey); return true; } - private QualifiedMember CreateRootTargetQualifiedMember() - => _configInfo.MapperContext.QualifiedMemberFactory.RootTarget(); + private QualifiedMember CreateRootTargetQualifiedMember() + { + return (_configInfo.TargetType == typeof(ExpandoObject)) + ? _configInfo.MapperContext.QualifiedMemberFactory.RootTarget() + : _configInfo.MapperContext.QualifiedMemberFactory.RootTarget(); + } private ConfiguredLambdaInfo GetValueLambdaInfo() { @@ -246,17 +264,17 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par #endregion - public IMappingConfigContinuation ToRootTarget() + public IMappingConfigContinuation ToTarget() { - ThrowIfSimpleSourceTypeConfigured(); + ThrowIfSimpleSource(typeof(TTarget)); return RegisterDataSource(() => new ConfiguredDataSourceFactory( _configInfo, GetValueLambdaInfo(), - CreateRootTargetQualifiedMember())); + CreateRootTargetQualifiedMember())); } - private void ThrowIfSimpleSourceTypeConfigured() + private void ThrowIfSimpleSource(Type targetMemberType) { var customValue = _customValueLambda.Body; @@ -270,9 +288,8 @@ private void ThrowIfSimpleSourceTypeConfigured() if (customValue.NodeType == ExpressionType.MemberAccess) { var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource(); - var configuredMember = Member.RootSource(customValue.ToReadableString(), customValue.Type); - var configuredSourceMember = QualifiedMember.From(configuredMember, _configInfo.MapperContext); - sourceValue = configuredSourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; + var sourceMember = customValue.ToSourceMember(_configInfo.MapperContext); + sourceValue = sourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; } else { @@ -281,10 +298,10 @@ private void ThrowIfSimpleSourceTypeConfigured() throw new MappingConfigurationException(string.Format( CultureInfo.InvariantCulture, - "{0}'{1}' cannot be mapped to root target type '{2}'", + "{0}'{1}' cannot be mapped to target type '{2}'", sourceValue, customValue.Type.GetFriendlyName(), - typeof(TTarget).GetFriendlyName())); + targetMemberType.GetFriendlyName())); } private MappingConfigContinuation RegisterDataSource( diff --git a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs index a7e5fed33..8137556b6 100644 --- a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs @@ -55,12 +55,12 @@ IMappingConfigContinuation To( IMappingConfigContinuation ToCtor(string parameterName); /// - /// Apply the configured source value to the root target object. + /// Apply the configured source value to the target object being configured. /// /// /// An IMappingConfigContinuation to enable further configuration of mappings from and to the source /// and target type being configured. /// - IMappingConfigContinuation ToRootTarget(); + IMappingConfigContinuation ToTarget(); } } \ No newline at end of file diff --git a/AgileMapper/Configuration/UserConfiguredItemBase.cs b/AgileMapper/Configuration/UserConfiguredItemBase.cs index 36fe9bea4..89ffa45ec 100644 --- a/AgileMapper/Configuration/UserConfiguredItemBase.cs +++ b/AgileMapper/Configuration/UserConfiguredItemBase.cs @@ -115,8 +115,7 @@ private bool TargetMembersMatch(IBasicMapperData mapperData) return true; } - if ((TargetMember == mapperData.TargetMember) || - (TargetMember.IsRoot && mapperData.TargetMember.IsRoot)) + if (TargetMembersAreCompatible(mapperData)) { return true; } @@ -131,6 +130,9 @@ private bool TargetMembersMatch(IBasicMapperData mapperData) TargetMember.LeafMember.DeclaringType.IsAssignableTo(mapperData.TargetMember.LeafMember.DeclaringType); } + protected virtual bool TargetMembersAreCompatible(IBasicMapperData mapperData) + => TargetMember == mapperData.TargetMember; + private bool HasCompatibleCondition(IBasicMapperData mapperData) => !HasConfiguredCondition || ConfigInfo.ConditionSupports(mapperData.RuleSet); diff --git a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs b/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs index e28c69fd9..619b59e7b 100644 --- a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs +++ b/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs @@ -77,13 +77,16 @@ public string GetConflictMessage(ConfiguredDataSourceFactory conflictingDataSour } public override bool AppliesTo(IBasicMapperData mapperData) + => base.AppliesTo(mapperData) && _dataSourceLambda.Supports(mapperData.RuleSet); + + protected override bool TargetMembersAreCompatible(IBasicMapperData mapperData) { - if (TargetMember.IsRoot && !mapperData.IsRoot) + if (base.TargetMembersAreCompatible(mapperData)) { - return false; + return true; } - return base.AppliesTo(mapperData) && _dataSourceLambda.Supports(mapperData.RuleSet); + return TargetMember.IsRoot && TargetMember.HasCompatibleType(mapperData.TargetMember.Type); } public IConfiguredDataSource Create(IMemberMapperData mapperData) diff --git a/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs b/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs index bbcce88ff..7a4f916f6 100644 --- a/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs @@ -7,7 +7,7 @@ internal struct ConfiguredDataSourceFinder : IDataSourceFinder { public IEnumerable FindFor(DataSourceFindContext context) { - if (NoDataSourcesAreConfigured(context)) + if (context.ConfiguredDataSources.None()) { yield break; } @@ -24,16 +24,5 @@ public IEnumerable FindFor(DataSourceFindContext context) ++context.DataSourceIndex; } } - - private static bool NoDataSourcesAreConfigured(DataSourceFindContext context) - { - context.ConfiguredDataSources = context - .MapperData - .MapperContext - .UserConfigurations - .GetDataSources(context.MapperData); - - return context.ConfiguredDataSources.None(); - } } } \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs index dce44b4fe..e89bc6369 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs @@ -9,6 +9,11 @@ internal class DataSourceFindContext public DataSourceFindContext(IChildMemberMappingData childMappingData) { ChildMappingData = childMappingData; + + ConfiguredDataSources = MapperData + .MapperContext + .UserConfigurations + .GetDataSources(MapperData); } public IChildMemberMappingData ChildMappingData { get; } @@ -19,7 +24,7 @@ public DataSourceFindContext(IChildMemberMappingData childMappingData) public bool StopFind { get; set; } - public IList ConfiguredDataSources { get; set; } + public IList ConfiguredDataSources { get; } public IDataSource GetFallbackDataSource() => ChildMappingData.RuleSet.FallbackDataSourceFactory.Create(MapperData); diff --git a/AgileMapper/DataSources/Finders/DataSourceFinder.cs b/AgileMapper/DataSources/Finders/DataSourceFinder.cs index 3ecc78a6c..cf2cab9ca 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFinder.cs @@ -4,16 +4,8 @@ using System.Linq; using Members; - internal static class DataSourceFinder + internal struct DataSourceFinder { - private static readonly IDataSourceFinder[] _finders = - { - default(ConfiguredDataSourceFinder), - default(MaptimeDataSourceFinder), - default(SourceMemberDataSourceFinder), - default(MetaMemberDataSourceFinder) - }; - public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) { var findContext = new DataSourceFindContext(childMappingData); @@ -24,18 +16,20 @@ public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) private static IEnumerable EnumerateDataSources(DataSourceFindContext context) { - foreach (var finder in _finders) + foreach (var finder in EnumerateFinders()) { foreach (var dataSource in finder.FindFor(context)) { - if (dataSource.IsValid) + if (!dataSource.IsValid) { - yield return dataSource; + continue; + } + + yield return dataSource; - if (!dataSource.IsConditional) - { - yield break; - } + if (!dataSource.IsConditional) + { + yield break; } } @@ -45,5 +39,13 @@ private static IEnumerable EnumerateDataSources(DataSourceFindConte } } } + + private static IEnumerable EnumerateFinders() + { + yield return default(ConfiguredDataSourceFinder); + yield return default(MaptimeDataSourceFinder); + yield return default(SourceMemberDataSourceFinder); + yield return default(MetaMemberDataSourceFinder); + } } } \ No newline at end of file diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 6bf71a56d..c6abe5da6 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -284,6 +284,8 @@ internal static QualifiedMember CreateMember( expression = memberExpression.GetParentOrNull(); } + AdjustMemberAccessesIfRootedInMappingData(memberAccesses, ref expression); + var rootMember = rootMemberFactory.Invoke(expression.Type); var parentMember = rootMember; @@ -309,6 +311,24 @@ internal static QualifiedMember CreateMember( return QualifiedMember.From(memberChain, mapperContext); } + private static void AdjustMemberAccessesIfRootedInMappingData(IList memberAccesses, ref Expression expression) + { + if (!expression.Type.IsClosedTypeOf(typeof(IMappingData<,>))) + { + return; + } + + var mappingDataRoot = memberAccesses[0]; + expression = Parameters.Create(mappingDataRoot.Type); + + memberAccesses.RemoveAt(0); + + for (var i = 0; i < memberAccesses.Count; i++) + { + memberAccesses[i] = memberAccesses[i].Replace(mappingDataRoot, expression); + } + } + private static Expression GetMemberAccess(Expression expression) { while (true) diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 4bd971fee..bd2874a3b 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -166,8 +166,7 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr protected static bool HasConfiguredRootDataSources(IMemberMapperData mapperData, out IList dataSources) { - if (!mapperData.IsRoot || - !mapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources) + if (!mapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources) { dataSources = null; return false; diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index 9bb347041..bef9e042a 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -171,8 +171,13 @@ public Type GetSourceMemberRuntimeType(IQualifiedMember childSourceMember) { var sourceParameter = Parameters.Create("source"); var relativeMember = sm.RelativeTo(MapperData.SourceMember); - var memberAccess = relativeMember.GetQualifiedAccess(MapperData); - memberAccess = memberAccess.Replace(MapperData.SourceObject, sourceParameter); + + var memberAccess = relativeMember + .GetQualifiedAccess(MapperData) + .Replace( + MapperData.SourceObject, + sourceParameter, + ExpressionEvaluation.Equivalator); var getRuntimeTypeCall = Expression.Call( ObjectExtensions.GetRuntimeSourceTypeMethod.MakeGenericMethod(sm.Type),