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/WhenConfiguringDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs index 4cb2ce33a..ac94ae97c 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs @@ -112,7 +112,48 @@ public void ShouldErrorIfDuplicateDataSourceIsConfigured() [Fact] public void ShouldErrorIfRedundantDataSourceIsConfigured() { - Should.Throw(() => + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(pp => pp.Value, pf => pf.Value); + } + }); + + configEx.Message.ShouldContain("PublicProperty.Value"); + configEx.Message.ShouldContain("PublicField.Value"); + configEx.Message.ShouldContain("does not need to be configured"); + } + + [Fact] + public void ShouldErrorIfRedundantConstructorParameterDataSourceIsConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToCtor(); + } + }); + + configEx.Message.ShouldContain("PublicProperty.Value"); + configEx.Message.ShouldContain("will automatically be mapped"); + configEx.Message.ShouldContain("target constructor parameter"); + configEx.Message.ShouldContain("PublicCtor.value"); + configEx.Message.ShouldContain("does not need to be configured"); + } + + [Fact] + public void ShouldErrorIfRedundantDerivedTypeDataSourceIsConfigured() + { + var configEx = Should.Throw(() => { using (var mapper = Mapper.CreateNew()) { @@ -129,6 +170,8 @@ public void ShouldErrorIfRedundantDataSourceIsConfigured() .To(x => x.Value); } }); + + configEx.Message.ShouldContain("already has configured data source"); } [Fact] @@ -262,6 +305,24 @@ public void ShouldErrorIfUnconvertibleConstructorValueConstantSpecified() configurationException.Message.ShouldContain("Unable to convert"); } + [Fact] + public void ShouldErrorIfUnconvertibleConstructorSourceValueSpecified() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToCtor(); + } + }); + + configurationException.Message.ShouldContain("Unable to convert"); + } + [Fact] public void ShouldErrorIfSimpleTypeConfiguredForComplexTarget() { @@ -281,6 +342,25 @@ public void ShouldErrorIfSimpleTypeConfiguredForComplexTarget() "Person.Id of type 'Guid' cannot be mapped to target type 'Address'"); } + [Fact] + public void ShouldErrorIfSimpleTypeConfiguredForComplexConstructorParameter() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToCtor
(); + } + }); + + configurationException.Message.ShouldContain( + "PublicField.Value of type 'int' cannot be mapped to target type 'Address'"); + } + [Fact] public void ShouldErrorIfSimpleTypeConfiguredForEnumerableTarget() { @@ -300,6 +380,25 @@ public void ShouldErrorIfSimpleTypeConfiguredForEnumerableTarget() "PublicField.Value of type 'int' cannot be mapped to target type 'int[]'"); } + [Fact] + public void ShouldErrorIfSimpleTypeConfiguredForEnumerableConstructorParameter() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToCtor("value"); + } + }); + + configurationException.Message.ShouldContain( + "PublicField.Value of type 'string' cannot be mapped to target type 'int[]'"); + } + [Fact] public void ShouldErrorIfUnconvertibleEnumerableElementTypeConfigured() { @@ -308,9 +407,9 @@ public void ShouldErrorIfUnconvertibleEnumerableElementTypeConfigured() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From[]>>() + .From[], int[]>>() .To>() - .Map(s => s.Value, t => t.Value); + .Map(s => s.Value1, t => t.Value); } }); diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs index 399f12efc..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,122 @@ 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() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .To() + .Map(d => d.Source.CurrentClass) + .ToCtor(); + + mapper.WhenMapping + .From() + .To() + .Map(ctx => new Issue129.Target.ActionClass()) + .ToCtor(); + + mapper.WhenMapping + .From() + .To() + .If(d => d.Source.ConcreteValue == Issue129.Source.Wrapper.ConcreteValueType.Action) + .Map(d => d.Source.ActionValue) + .ToTarget() + .And + .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"); + } + } + #region Helper Classes internal class Issue123 @@ -303,6 +420,101 @@ public class Leaf : ILeaf } } + internal static class Issue129 + { + public static class Source + { + public class SituationClass + { + public string Name { get; set; } + } + + 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, Delay } + + public ConcreteValueType ConcreteValue { get; set; } + + public SituationObject SituationValue { get; set; } + + public ActionObject ActionValue { get; set; } + + public DelayObject DelayValue { get; set; } + } + } + + public static class Target + { + public interface ITrafficObj { ITrafficClass CurrentClass { get; } } + + public interface ITrafficClass { string Name { get; } } + + 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 TimeSpan Duration { get; set; } + } + + public class SituationObject : ITrafficObj + { + public SituationObject(SituationClass clazz) { CurrentClass = clazz; } + + public ITrafficClass CurrentClass { get; } + } + + public class ActionObject : ITrafficObj + { + public ActionObject(ActionClass clazz) { CurrentClass = clazz; } + + public ITrafficClass CurrentClass { get; } + } + + public class DelayObject : ITrafficObj + { + public DelayObject(TimeSpan duration) + { + CurrentClass = new DelayClass + { + Name = "Delay", + Duration = duration + }; + } + + public ITrafficClass CurrentClass { get; } + } + } + } + #endregion } } 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.UnitTests/Configuration/WhenConfiguringObjectCreation.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs index 8b88d5ee6..d6694e096 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs @@ -36,6 +36,25 @@ public void ShouldUseAConfiguredFactoryForAGivenType() } } + [Fact] + public void ShouldUseAConfiguredFactoryWithASimpleSourceType() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToANew>>() + .Map(ctx => new PublicCtor(ctx.Source.Value)) + .To(t => t.Value); + + var source = new PublicField { Value = "Hello!" }; + var result = mapper.Map(source).ToANew>>(); + + result.Value.ShouldNotBeNull(); + result.Value.Value.ShouldBe("Hello!"); + } + } + [Fact] public void ShouldUseAConfiguredFactoryWithAComplexTypeMemberBinding() { diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs index af52e5c74..b45c36b90 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs @@ -213,9 +213,9 @@ public void ShouldErrorOnMemberScopeOptInOfConfiguredSourceMemberDataSourceForWr using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From>() + .From>() .To>() - .Map((pp, pwop) => pp.Value).To(pwop => pwop.Value) + .Map((pp, pwop) => pp.Value1).To(pwop => pwop.Value) .AndViceVersa(); } }); diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringNestedDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringNestedDictionaryMapping.cs index d4a242d5b..7028e8606 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringNestedDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringNestedDictionaryMapping.cs @@ -36,5 +36,65 @@ public void ShouldPopulateANestedStringFromAConfiguredNestedObjectEntry() result.Address.Line1.ShouldBe("6478 Nested Drive"); } } + + // See https://github.com/agileobjects/AgileMapper/issues/133 + [Fact] + public void ShouldApplyANestedDictionaryToARootTarget() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToDictionaries + .Map(ctx => ctx.Source.Map) + .ToTarget(); + + var source = new Issue133.Source.Wrapper + { + Map = + { + { "first", new Issue133.Source.Data { Name = "First" } }, + { "second", new Issue133.Source.Data { Name = "Second" } } + } + }; + + var result = mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Count.ShouldBe(3); + + result.ShouldContainKey("Map"); + result.ShouldContainKey("first"); + result.ShouldContainKey("second"); + + result["Map"].Name.ShouldBeNull(); + result["first"].Name.ShouldBe("First"); + result["second"].Name.ShouldBe("Second"); + } + } + + private static class Issue133 + { + public static class Source + { + public class Wrapper + { + public IDictionary Map { get; } = new Dictionary(); + } + + public class Data + { + public string Name { get; set; } + } + } + + public static class Target + { + public class Data + { + public string Name { get; set; } + } + } + } } } diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaries.cs index afc7a8228..31e87aff7 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaries.cs @@ -90,16 +90,22 @@ public void ShouldMapBetweenDictionaryImplementations() }; var target = new StringKeyedDictionary { - ["One"] = new ProductDto { ProductId = "One!", Price = 99.99m } + ["One"] = new ProductDto { ProductId = "One!" } }; + var targetProductDtoOne = target["One"]; + var result = Mapper.Map(source).OnTo(target); + result.ShouldBeSameAs(target); result.Count.ShouldBe(2); + result.ShouldContainKey("One"); + result["One"].ShouldBeSameAs(targetProductDtoOne); result["One"].ProductId.ShouldBe("One!"); result["One"].Price.ShouldBe(9.99m); + result.ShouldContainKey("Two"); result["Two"].ProductId.ShouldBe("Two!"); result["Two"].Price.ShouldBe(10.00m); } diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaryMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaryMembers.cs index 7892a7f8f..9af6a086f 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaryMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingOnToDictionaryMembers.cs @@ -32,5 +32,39 @@ public void ShouldMapAnIDictionaryOnToAConvertibleSimpleTypedDictionary() target.Value["TWOah-ah-ah"].ShouldBe(guidTwo.ToString()); target.Value["THREEah-ah-ah"].ShouldBe("gibblets"); } + + [Fact] + public void ShouldMapOnToAComplexTypeDictionary() + { + Address sourceWorkAddress, targetHomeAddress; + + var source = new PublicField> + { + Value = new Dictionary + { + ["Home"] = new Address { Line1 = "Home", Line2 = "My Town" }, + ["Work"] = sourceWorkAddress = new Address { Line1 = "Work", Line2 = "My City" } + } + }; + + var target = new PublicReadOnlyField>(new Dictionary + { + ["Home"] = targetHomeAddress = new Address { Line1 = "My Home" } + }); + + Mapper.Map(source).OnTo(target); + + target.Value.Count.ShouldBe(2); + + target.Value.ShouldContainKey("Home"); + target.Value["Home"].ShouldBeSameAs(targetHomeAddress); + target.Value["Home"].Line1.ShouldBe("My Home"); + target.Value["Home"].Line2.ShouldBe("My Town"); + + target.Value.ShouldContainKey("Work"); + target.Value["Work"].ShouldNotBeSameAs(sourceWorkAddress); + target.Value["Work"].Line1.ShouldBe("Work"); + target.Value["Work"].Line2.ShouldBe("My City"); + } } } \ No newline at end of file diff --git a/AgileMapper.UnitTests/WhenUsingPartialTrust.cs b/AgileMapper.UnitTests/WhenUsingPartialTrust.cs index 3caa285d7..cd0714a43 100644 --- a/AgileMapper.UnitTests/WhenUsingPartialTrust.cs +++ b/AgileMapper.UnitTests/WhenUsingPartialTrust.cs @@ -206,13 +206,13 @@ public MappingException TestMappingException() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From>() + .From>() .To>() - .If((s, t) => int.Parse(s.Value) > 0) - .Map(ctx => ctx.Source.Value) + .If((s, t) => int.Parse(s.Value1) > 0) + .Map(ctx => ctx.Source.Value1) .To(x => x.Value); - var source = new PublicProperty { Value = "CantParseThis" }; + var source = new PublicTwoFields { Value1 = "CantParseThis" }; mapper.Map(source).ToANew>(); } diff --git a/AgileMapper/AgileMapper.csproj b/AgileMapper/AgileMapper.csproj index 30df933f0..c50ec3d3a 100644 --- a/AgileMapper/AgileMapper.csproj +++ b/AgileMapper/AgileMapper.csproj @@ -18,11 +18,11 @@ false AgileObjects.AgileMapper Copyright © AgileObjects Ltd 2019 - - Support for using .MapTo<T>() to specify interface implementations (re: #123) -- Improved support for non-standard collections -- Improved detection of join entities -- Automatically mapping complex type members to null if only an identifier is populated, with a non-default value (re: #121) - 1.3.0 + - Support for mapping callbacks in .ToTarget() data sources +- Fixing constructability check for .ToTarget() data sources re: #129 +- Fixing .ToTarget() calls with source Dictionaries, re: #133 +- Fixing complex type Dictionary merging +- Performance improvements diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index bee6e5e20..5b0ea7525 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -15,6 +15,7 @@ using NetStandardPolyfills; using Projection; using ReadableExpressions.Extensions; + using TypeConversion; #if NET35 using Dlr = Microsoft.Scripting.Ast; using static Microsoft.Scripting.Ast.Expression; @@ -29,7 +30,7 @@ internal class CustomDataSourceTargetMemberSpecifier : private readonly MappingConfigInfo _configInfo; private readonly LambdaExpression _customValueLambda; private readonly bool _valueCouldBeSourceMember; - private readonly ConfiguredLambdaInfo _customValueLambdaInfo; + private ConfiguredLambdaInfo _customValueLambdaInfo; public CustomDataSourceTargetMemberSpecifier( MappingConfigInfo configInfo, @@ -49,11 +50,13 @@ public CustomDataSourceTargetMemberSpecifier( _customValueLambdaInfo = customValueLambda; } + private MapperContext MapperContext => _configInfo.MapperContext; + public ICustomDataSourceMappingConfigContinuation To( Expression> targetMember) { ThrowIfTargetParameterSpecified(targetMember); - ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TTargetValue)); + ThrowIfRedundantSourceMember(targetMember); return RegisterDataSource(() => CreateFromLambda(targetMember)); } @@ -62,7 +65,6 @@ IProjectionConfigContinuation ICustomProjectionDataSourceTarge Expression> resultMember) { ThrowIfTargetParameterSpecified(resultMember); - ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TResultValue)); return RegisterDataSource(() => CreateFromLambda(resultMember)); } @@ -70,8 +72,6 @@ IProjectionConfigContinuation ICustomProjectionDataSourceTarge public IMappingConfigContinuation To( Expression>> targetSetMethod) { - ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TTargetValue)); - return RegisterDataSource(() => CreateFromLambda(targetSetMethod)); } @@ -86,17 +86,66 @@ private static void ThrowIfTargetParameterSpecified(LambdaExpression targetMembe } } - private void ThrowIfSimpleSourceForNonSimpleTargetMember(Type targetMemberType) + private void ThrowIfRedundantSourceMember(LambdaExpression targetMemberLambda) + { + if (!_valueCouldBeSourceMember) + { + return; + } + + var targetMember = targetMemberLambda.ToTargetMember(MapperContext, nt => { }); + + if (targetMember == null) + { + return; + } + + var valueLambdaInfo = GetValueLambdaInfo(); + + ThrowIfRedundantSourceMember(valueLambdaInfo, targetMember); + } + + private void ThrowIfRedundantSourceMember(ConfiguredLambdaInfo valueLambdaInfo, QualifiedMember targetMember) { - if ((targetMemberType != typeof(object)) && !targetMemberType.IsSimple()) + if (!valueLambdaInfo.IsSourceMember(out var sourceMemberLambda)) { - ThrowIfSimpleSource(targetMemberType); + return; + } + + var mappingData = _configInfo.ToMappingData(); + + var targetMemberMapperData = new ChildMemberMapperData(targetMember, mappingData.MapperData); + var targetMemberMappingData = mappingData.GetChildMappingData(targetMemberMapperData); + var bestMatchingSourceMember = SourceMemberMatcher.GetMatchFor(targetMemberMappingData, out _); + + if (bestMatchingSourceMember == null) + { + return; + } + + var sourceMember = sourceMemberLambda.ToSourceMember(MapperContext); + + if (!bestMatchingSourceMember.Matches(sourceMember)) + { + return; } + + var targetMemberType = (targetMember.LeafMember.MemberType == MemberType.ConstructorParameter) + ? "constructor parameter" + : "member"; + + throw new MappingConfigurationException(string.Format( + CultureInfo.InvariantCulture, + "Source member {0} will automatically be mapped to target {1} {2}, " + + "and does not need to be configured", + GetSourceMemberDescription(sourceMember), + targetMemberType, + targetMember.GetFriendlyTargetPath(_configInfo))); } private ConfiguredDataSourceFactory CreateFromLambda(LambdaExpression targetMemberLambda) { - var valueLambdaInfo = GetValueLambdaInfo(typeof(TTargetValue)); + var valueLambdaInfo = GetValueLambdaInfo(); if (IsDictionaryEntry(targetMemberLambda, out var dictionaryEntryMember)) { @@ -106,6 +155,8 @@ private ConfiguredDataSourceFactory CreateFromLambda(LambdaExpress return CreateDataSourceFactory(valueLambdaInfo, targetMemberLambda); } + private ConfiguredLambdaInfo GetValueLambdaInfo() => GetValueLambdaInfo(typeof(TTargetValue)); + private ConfiguredLambdaInfo GetValueLambdaInfo(Type targetValueType) { if (_customValueLambdaInfo != null) @@ -124,11 +175,10 @@ private ConfiguredLambdaInfo GetValueLambdaInfo(Type targetValueType) (targetValueType == typeof(object)) || customValueLambda.ReturnType.IsAssignableTo(targetValueType)) { - return ConfiguredLambdaInfo.For(customValueLambda); + return _customValueLambdaInfo = ConfiguredLambdaInfo.For(customValueLambda); } - var convertedConstantValue = _configInfo - .MapperContext + var convertedConstantValue = MapperContext .ValueConverters .GetConversion(customValueLambda.Body, targetValueType); @@ -139,7 +189,7 @@ private ConfiguredLambdaInfo GetValueLambdaInfo(Type targetValueType) var constantValueLambda = Lambda(funcType, value); var valueLambdaInfo = ConfiguredLambdaInfo.For(constantValueLambda); - return valueLambdaInfo; + return _customValueLambdaInfo = valueLambdaInfo; } private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out DictionaryTargetMember entryMember) @@ -180,8 +230,8 @@ private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out Dictiona private QualifiedMember CreateRootTargetQualifiedMember() { return (_configInfo.TargetType == typeof(ExpandoObject)) - ? _configInfo.MapperContext.QualifiedMemberFactory.RootTarget() - : _configInfo.MapperContext.QualifiedMemberFactory.RootTarget(); + ? MapperContext.QualifiedMemberFactory.RootTarget() + : MapperContext.QualifiedMemberFactory.RootTarget(); } private ConfiguredDataSourceFactory CreateDataSourceFactory( @@ -206,21 +256,25 @@ IProjectionConfigContinuation ICustomProjectionDataSourceTarge => RegisterDataSource(CreateForCtorParam); public IMappingConfigContinuation ToCtor(string parameterName) - => RegisterDataSource(() => CreateForCtorParam(parameterName)); + => RegisterNamedContructorParameterDataSource(parameterName); IProjectionConfigContinuation ICustomProjectionDataSourceTargetMemberSpecifier.ToCtor( string parameterName) { - return RegisterDataSource(() => CreateForCtorParam(parameterName)); + return RegisterNamedContructorParameterDataSource(parameterName); } #region Ctor Helpers private ConfiguredDataSourceFactory CreateForCtorParam() - => CreateForCtorParam(GetUniqueConstructorParameterOrThrow()); + => CreateForCtorParam(GetUniqueConstructorParameterOrThrow()); + + private MappingConfigContinuation RegisterNamedContructorParameterDataSource(string name) + { + var parameter = GetUniqueConstructorParameterOrThrow(name); - private ConfiguredDataSourceFactory CreateForCtorParam(string name) - => CreateForCtorParam(GetUniqueConstructorParameterOrThrow(name)); + return RegisterDataSource(parameter.ParameterType, () => CreateForCtorParam(parameter)); + } private static ParameterInfo GetUniqueConstructorParameterOrThrow(string name = null) { @@ -260,7 +314,7 @@ private static ParameterInfo GetUniqueConstructorParameterOrThrow(string } private static string GetParameterMatchInfo(string name, bool matchParameterType) - => matchParameterType ? "of type " + typeof(TParam).GetFriendlyName() : "named '" + name + "'"; + => matchParameterType ? GetTypeDescription(typeof(TParam)) : $"named '{name}'"; private static Exception MissingParameterException(string parameterMatchInfo) { @@ -280,11 +334,13 @@ private static Exception AmbiguousParameterException(string parameterMatchInfo) typeof(TTarget).GetFriendlyName())); } - private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo parameter) + private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo parameter) { - var valueLambda = GetValueLambdaInfo(typeof(TParam)); + var valueLambda = GetValueLambdaInfo(parameter.ParameterType); var constructorParameter = CreateRootTargetQualifiedMember().Append(Member.ConstructorParameter(parameter)); + ThrowIfRedundantSourceMember(valueLambda, constructorParameter); + return new ConfiguredDataSourceFactory(_configInfo, valueLambda, constructorParameter); } @@ -292,12 +348,9 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par public IMappingConfigContinuation ToTarget() { - ThrowIfSimpleSourceForNonSimpleTargetMember(typeof(TTarget)); - ThrowIfEnumerableSourceAndTargetMismatch(typeof(TTarget)); - return RegisterDataSource(() => new ConfiguredDataSourceFactory( _configInfo, - GetValueLambdaInfo(typeof(TTarget)), + GetValueLambdaInfo(), CreateRootTargetQualifiedMember())); } @@ -322,27 +375,66 @@ private void SetDerivedToTargetSource(MappingConfigInfo derivedT .ToTarget(); } - private void ThrowIfSimpleSource(Type targetMemberType) + private static string GetTypeDescription(Type type) => $"of type '{type.GetFriendlyName()}'"; + + private MappingConfigContinuation RegisterDataSource( + Func dataSourceFactoryFactory) + { + return RegisterDataSource(typeof(TTargetValue), dataSourceFactoryFactory); + } + + private MappingConfigContinuation RegisterDataSource( + Type targetMemberType, + Func dataSourceFactoryFactory) { - var customValue = _customValueLambda.Body; + ThrowIfInvalid(targetMemberType); + + MapperContext.UserConfigurations.Add(dataSourceFactoryFactory.Invoke()); + + return new MappingConfigContinuation(_configInfo); + } - if (!customValue.Type.IsSimple()) + private void ThrowIfInvalid(Type targetMemberType) + { + ThrowIfSimpleSourceForNonSimpleTargetMember(targetMemberType); + ThrowIfEnumerableSourceAndTargetMismatch(targetMemberType); + + _configInfo.ThrowIfSourceTypeUnconvertible(targetMemberType); + } + + private void ThrowIfSimpleSourceForNonSimpleTargetMember(Type targetMemberType) + { + if ((targetMemberType == typeof(object)) || + targetMemberType.IsSimple() || + !_customValueLambda.Body.Type.IsSimple() || + ConversionOperatorExists(targetMemberType)) { return; } - var sourceValue = GetSourceValue(customValue); + var sourceValue = GetSourceValueDescription(_customValueLambda.Body); throw new MappingConfigurationException(string.Format( CultureInfo.InvariantCulture, - "{0}'{1}' cannot be mapped to target type '{2}'", + "{0} cannot be mapped to target type '{1}'", sourceValue, - customValue.Type.GetFriendlyName(), targetMemberType.GetFriendlyName())); } + private bool ConversionOperatorExists(Type targetMemberType) + { + return default(OperatorConverter).CanConvert( + _customValueLambda.Body.Type.GetNonNullableType(), + targetMemberType.GetNonNullableType()); + } + private void ThrowIfEnumerableSourceAndTargetMismatch(Type targetMemberType) { + if (_customValueLambda == null) + { + return; + } + var customValue = _customValueLambda.Body; if ((targetMemberType.IsDictionary() || customValue.Type.IsDictionary()) || @@ -364,39 +456,35 @@ private void ThrowIfEnumerableSourceAndTargetMismatch(Type targetMemberType) targetEnumerableState = "non-enumerable"; } - var sourceValue = GetSourceValue(customValue); + var sourceValue = GetSourceValueDescription(customValue); throw new MappingConfigurationException(string.Format( CultureInfo.InvariantCulture, - "{0} {1}'{2}' cannot be mapped to {3} target type '{4}'", + "{0} {1} cannot be mapped to {2} target type '{3}'", sourceEnumerableState, sourceValue, - customValue.Type.GetFriendlyName(), targetEnumerableState, targetMemberType.GetFriendlyName())); } - private string GetSourceValue(Expression customValue) + private string GetSourceValueDescription(Expression customValue) { if (customValue.NodeType != ExpressionType.MemberAccess) { - return "Source type "; + return $"Source type '{customValue.Type.GetFriendlyName()}'"; } - var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource(); - var sourceMember = customValue.ToSourceMember(_configInfo.MapperContext); - var sourceValue = sourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; + var sourceMember = customValue.ToSourceMember(MapperContext); - return sourceValue; + return GetSourceMemberDescription(sourceMember); } - private MappingConfigContinuation RegisterDataSource( - Func factoryFactory) + private string GetSourceMemberDescription(IQualifiedMember sourceMember) { - _configInfo.ThrowIfSourceTypeUnconvertible(); - _configInfo.MapperContext.UserConfigurations.Add(factoryFactory.Invoke()); + var rootSourceMember = MapperContext.QualifiedMemberFactory.RootSource(); + var sourceMemberPath = sourceMember.GetFriendlyMemberPath(rootSourceMember); - return new MappingConfigContinuation(_configInfo); + return sourceMemberPath + " " + GetTypeDescription(sourceMember.Type); } private struct AnyParameterType { } diff --git a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs index 36a34f78a..b68318d80 100644 --- a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs +++ b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs @@ -17,6 +17,8 @@ public DerivedPairTargetTypeSpecifier(MappingConfigInfo configInfo) _configInfo = configInfo; } + private MapperContext MapperContext => _configInfo.MapperContext; + public IMappingConfigContinuation To() where TDerivedTarget : TTarget { @@ -33,7 +35,7 @@ private MappingConfigContinuation SetDerivedTargetType(_configInfo); - _configInfo.MapperContext.UserConfigurations.DerivedTypes.Add(derivedTypePair); + MapperContext.UserConfigurations.DerivedTypes.Add(derivedTypePair); return new MappingConfigContinuation(_configInfo); } @@ -42,12 +44,8 @@ private void ThrowIfUnconstructable() { var mappingData = _configInfo.ToMappingData(); - var objectCreation = _configInfo - .MapperContext - .ConstructionFactory - .GetNewObjectCreation(mappingData); - - if (objectCreation != null) + if (mappingData.IsTargetConstructable() || + mappingData.IsConstructableFromToTargetDataSource()) { return; } @@ -57,12 +55,10 @@ private void ThrowIfUnconstructable() ThrowUnableToCreate(); } - var configuredImplementationPairings = _configInfo - .MapperContext + var configuredImplementationPairings = MapperContext .UserConfigurations - .DerivedTypes.GetImplementationTypePairsFor( - _configInfo.ToMapperData(), - _configInfo.MapperContext); + .DerivedTypes + .GetImplementationTypePairsFor(_configInfo.ToMapperData(), MapperContext); if (configuredImplementationPairings.None()) { diff --git a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs index 955c0c869..03bf31412 100644 --- a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs +++ b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs @@ -2,7 +2,6 @@ { using System; using System.Linq; - using Extensions; using Extensions.Internal; using Members; using Members.Dictionaries; @@ -243,7 +242,7 @@ public Expression GetBody( (mapperData.TargetMember is DictionaryTargetMember dictionaryMember) && (dictionaryMember.HasCompatibleType(contextTypes[1]))) { - contextTypes = contextTypes.ToArray(); + contextTypes = contextTypes.CopyToArray(); contextTypes[1] = mapperData.TargetType; } 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 d2f36b391..b9c853b8e 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 { @@ -99,8 +99,8 @@ public MappingConfigInfo ForSourceValueType(Type sourceValueType) return this; } - public void ThrowIfSourceTypeUnconvertible() - => MapperContext.ValueConverters.ThrowIfUnconvertible(SourceValueType, typeof(TTargetValue)); + public void ThrowIfSourceTypeUnconvertible(Type targetValueType) + => MapperContext.ValueConverters.ThrowIfUnconvertible(SourceValueType, targetValueType); #region Conditions @@ -199,7 +199,8 @@ public MappingConfigInfo Set(T value) public IObjectMappingData ToMappingData() { - if (_mappingData != null) + if ((_mappingData != null) && + _mappingData.MappingTypes.Equals(MappingTypes.Fixed)) { return _mappingData; } diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index e1265c403..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,13 +355,30 @@ 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 (!HasConfiguredToTargetDataSources) + { + return Enumerable.EmptyArray; + } + + var toTargetDataSourceFactories = + QueryDataSourceFactories(mapperData) + .Filter(dsf => dsf.TargetMember.IsRoot); + + return GetDataSources(toTargetDataSourceFactories, mapperData); + } + + private static IList GetDataSources( + IEnumerable factories, + IMemberMapperData mapperData) { - return (_dataSourceFactories != null) - ? QueryDataSourceFactories(mapperData).Project(dsf => dsf.Create(mapperData)).ToArray() - : Enumerable.EmptyArray; + return factories.Project(dsf => dsf.Create(mapperData)).ToArray(); } public IEnumerable QueryDataSourceFactories(IBasicMapperData mapperData) 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 27be4f7d3..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, @@ -47,7 +47,7 @@ public IEnumerable FindFor(DataSourceFindContext context) .MapperData .MapperContext .UserConfigurations - .GetDataSources(updatedMapperData); + .GetDataSourcesForToTarget(updatedMapperData); foreach (var configuredRootDataSource in configuredRootDataSources) { 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/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index e8311344a..492cab8f1 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -150,6 +150,15 @@ public static TResult[] ProjectToArray(this IList items, return result; } + public static T[] CopyToArray(this IList items) + { + var clonedArray = new T[items.Count]; + + clonedArray.CopyFrom(items); + + return clonedArray; + } + public static Expression ReverseChain(this IList items) where T : IConditionallyChainable { diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs index 090267f63..a8d5116b1 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs @@ -48,6 +48,11 @@ public static TExpression Replace( IEqualityComparer comparer = null) where TExpression : Expression { + if (target == replacement) + { + return expression; + } + if (expression == null) { return null; diff --git a/AgileMapper/Extensions/PublicEnumerableExtensions.cs b/AgileMapper/Extensions/PublicEnumerableExtensions.cs index 914704fb3..33760728b 100644 --- a/AgileMapper/Extensions/PublicEnumerableExtensions.cs +++ b/AgileMapper/Extensions/PublicEnumerableExtensions.cs @@ -150,29 +150,28 @@ public static bool None(this IEnumerable items, Func predicate) => items.All(item => !predicate.Invoke(item)); /// - /// Convert this list of to an array. + /// Copies this list of into a new array. /// /// The type of object stored in the list. /// The list of items to convert. /// This list of items, converted to an array. - public static T[] ToArray(this IList items) - { - var array = new T[items.Count]; - - array.CopyFrom(items); - - return array; - } + public static T[] ToArray(this IList items) => items.CopyToArray(); /// - /// Convert this collection of to an array. + /// Copies this collection of into a new array, or returns this + /// object if it is an array. /// /// The type of object stored in the list. /// The collection of items to convert. /// This collection of items, converted to an array. public static T[] ToArray(this ICollection items) { - var array = new T[items.Count]; + if (items is T[] array) + { + return array; + } + + array = new T[items.Count]; items.CopyTo(array, 0); diff --git a/AgileMapper/MappingDataExtensions.cs b/AgileMapper/MappingDataExtensions.cs new file mode 100644 index 000000000..4b369be75 --- /dev/null +++ b/AgileMapper/MappingDataExtensions.cs @@ -0,0 +1,67 @@ +namespace AgileObjects.AgileMapper +{ +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using DataSources; + using Extensions.Internal; + using Members; + 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; + } + } + + // TODO: Cover: Unconstructable ToTarget data source + return null; + } + + public static Expression GetTargetObjectCreation(this IObjectMappingData mappingData) + { + return mappingData + .MapperData + .MapperContext + .ConstructionFactory + .GetNewObjectCreation(mappingData); + } + + public static bool HasSameTypedConfiguredDataSource(this IObjectMappingData mappingData) + { + return + (mappingData.MapperData.SourceType == mappingData.MapperData.TargetType) && + (mappingData.MapperData.SourceMember is ConfiguredSourceMember); + } + } +} diff --git a/AgileMapper/MappingTypes.cs b/AgileMapper/MappingTypes.cs index aac26cc5e..84caa8536 100644 --- a/AgileMapper/MappingTypes.cs +++ b/AgileMapper/MappingTypes.cs @@ -99,6 +99,11 @@ public static MappingTypes For(TSource source, TTarget target) public bool Equals(MappingTypes otherTypes) => otherTypes._hashCode == _hashCode; + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion public override int GetHashCode() => _hashCode; public MappingTypes WithTypes(Type newSourceType, Type newTargetType) diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 26dca9c88..546aeebad 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -19,7 +19,6 @@ internal class DictionaryTargetMember : QualifiedMember { private readonly DictionaryTargetMember _rootDictionaryMember; private bool _createDictionaryChildMembers; - private Expression _key; public DictionaryTargetMember(QualifiedMember wrappedTargetMember) : base(wrappedTargetMember.MemberChain, wrappedTargetMember) @@ -52,6 +51,10 @@ public override bool IsRoot public Type ValueType { get; } + public Expression Key { get; private set; } + + public bool HasKey => Key != null; + public bool HasObjectEntries => ValueType == typeof(object); public bool HasSimpleEntries => ValueType.IsSimple(); @@ -92,7 +95,7 @@ public DictionaryTargetMember Append(ParameterExpression key) var memberKey = new DictionaryMemberKey(ValueType, key.Name, this); var childMember = Append(memberKey); - childMember._key = key; + childMember.Key = key; return childMember; } @@ -142,19 +145,12 @@ protected override QualifiedMember CreateRuntimeTypedMember(Type runtimeType) return new DictionaryTargetMember(runtimeTypedTargetEntryMember, _rootDictionaryMember) { _createDictionaryChildMembers = _createDictionaryChildMembers, - _key = _key + Key = Key }; } public override bool Matches(IQualifiedMember otherMember) - { - if (_key == null) - { - return base.Matches(otherMember); - } - - return GetKeyNameOrNull() == otherMember.Name; - } + => HasKey ? GetKeyNameOrNull() == otherMember.Name : base.Matches(otherMember); public override Expression GetAccess(Expression instance, IMemberMapperData mapperData) { @@ -184,9 +180,9 @@ private Expression GetKeyedAccess(IMemberMapperData mapperData) private Expression GetKey(IMemberMapperData mapperData) { - return (_key?.NodeType != Parameter) + return (Key?.NodeType != Parameter) ? mapperData.GetValueConversion(mapperData.GetTargetMemberDictionaryKey(), KeyType) - : _key; + : Key; } private Expression GetDictionaryAccess(IMemberMapperData mapperData) @@ -243,10 +239,7 @@ private Expression GetTryGetValueCall(IMemberMapperData mapperData, out Paramete return tryGetValueCall; } - public void SetCustomKey(string key) - { - _key = key.ToConstantExpression(); - } + public void SetCustomKey(string key) => Key = key.ToConstantExpression(); public override Expression GetPopulation(Expression value, IMemberMapperData mapperData) { @@ -278,13 +271,13 @@ public override Expression GetPopulation(Expression value, IMemberMapperData map private bool ValueIsFlattening(Expression value, out Expression flattening) { - if (!(HasObjectEntries || HasSimpleEntries)) + if (HasObjectEntries || HasSimpleEntries) { - flattening = null; - return false; + return value.TryGetMappingBody(out flattening); } - return value.TryGetMappingBody(out flattening); + flattening = null; + return false; } private Expression GetCheckedValueOrNull(Expression value, Expression keyedAccess, IMemberMapperData mapperData) @@ -294,21 +287,15 @@ private Expression GetCheckedValueOrNull(Expression value, Expression keyedAcces return null; } - if (mapperData.SourceMember.IsEnumerable) + if ((value.NodeType != Block) && (value.NodeType != Try) || mapperData.TargetIsDefinitelyUnpopulated()) { - return value.GetConversionTo(ValueType); - } - - - if ((value.NodeType != Block) && (value.NodeType != Try)) - { - return null; + return mapperData.SourceMember.IsEnumerable ? value.GetConversionTo(ValueType) : null; } var checkedAccess = GetAccessChecked(mapperData); var existingValue = checkedAccess.Variables.First(); - if (value.NodeType != Block) + if (value.NodeType == Try) { return GetCheckedTryCatch((TryExpression)value, keyedAccess, checkedAccess, existingValue); } @@ -397,7 +384,7 @@ public override string ToString() return $"[\"{path}\"]: {Type.GetFriendlyName()}"; } - private string GetKeyNameOrNull() => (string)((ConstantExpression)_key)?.Value; + private string GetKeyNameOrNull() => (string)((ConstantExpression)Key)?.Value; #region Helper Classes diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index c3b019866..753acc74f 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -192,7 +192,7 @@ protected override Expression VisitMember(MemberExpression memberAccess) private bool IsNotRootObject(MemberExpression memberAccess) { - if (memberAccess.Member.Name == "Parent") + if (memberAccess.Member.Name == nameof(IMappingData.Parent)) { // ReSharper disable once PossibleNullReferenceException return !memberAccess.Member.DeclaringType.Name @@ -204,7 +204,7 @@ private bool IsNotRootObject(MemberExpression memberAccess) return true; } - if (memberAccess.Member.Name == "EnumerableIndex") + if (memberAccess.Member.Name == nameof(IMappingData.EnumerableIndex)) { return false; } @@ -220,7 +220,7 @@ private bool IsNotRootObject(MemberExpression memberAccess) protected override Expression VisitIndex(IndexExpression indexAccess) { if ((indexAccess.Object.Type != typeof(string)) && - !indexAccess.Object.Type.IsDictionary() && + !indexAccess.Object.Type.IsDictionary() && IndexDoesNotUseParameter(indexAccess.Arguments[0])) { AddMemberAccess(indexAccess); @@ -253,26 +253,28 @@ private static bool IndexDoesNotUseParameter(Expression indexExpression) protected override Expression VisitMethodCall(MethodCallExpression methodCall) { - if ((methodCall.Object != _mappingDataObject) && - (methodCall.Method.DeclaringType != typeof(IMappingData))) + if ((methodCall.Object == _mappingDataObject) || + (methodCall.Method.DeclaringType == typeof(IMappingData))) { - if (IsNullableGetValueOrDefaultCall(methodCall)) - { - AddExistingNullCheck(methodCall.Object); - } + return base.VisitMethodCall(methodCall); + } - AddStringMemberAccessSubjectIfAppropriate(methodCall.Object); - AddInvocationIfNecessary(methodCall); - AddMemberAccessIfAppropriate(methodCall); + if (IsNullableGetValueOrDefaultCall(methodCall)) + { + AddExistingNullCheck(methodCall.Object); } + AddStringMemberAccessSubjectIfAppropriate(methodCall.Object); + AddInvocationIfNecessary(methodCall); + AddMemberAccessIfAppropriate(methodCall); + return base.VisitMethodCall(methodCall); } private static bool IsNullableGetValueOrDefaultCall(MethodCallExpression methodCall) { return (methodCall.Object != null) && - (methodCall.Method.Name == "GetValueOrDefault") && + (methodCall.Method.Name == nameof(Nullable.GetValueOrDefault)) && (methodCall.Object.Type.IsNullableType()); } @@ -378,20 +380,20 @@ private static bool IsNonNullReturnMethodCall(Expression memberAccess) switch (method.Name) { - case "ToString" when method.DeclaringType == typeof(object): - case "Split" when method.DeclaringType == typeof(string): - case "GetEnumerator" when method.DeclaringType.IsClosedTypeOf(typeof(IEnumerable<>)): + case nameof(string.ToString) when method.DeclaringType == typeof(object): + case nameof(string.Split) when method.DeclaringType == typeof(string): + case nameof(IEnumerable.GetEnumerator) when method.DeclaringType.IsClosedTypeOf(typeof(IEnumerable<>)): return true; - case "Select": - case "SelectMany": - case "Project": - case "Filter": - case "Where": - case "OrderBy": - case "OrderByDescending": - case "ToList": - case "ToArray": + case nameof(Enumerable.Select): + case nameof(Enumerable.SelectMany): + case nameof(PublicEnumerableExtensions.Project): + case nameof(PublicEnumerableExtensions.Filter): + case nameof(Enumerable.Where): + case nameof(Enumerable.OrderBy): + case nameof(Enumerable.OrderByDescending): + case nameof(Enumerable.ToList): + case nameof(Enumerable.ToArray): return (method.DeclaringType == typeof(Enumerable)) || (method.DeclaringType == typeof(PublicEnumerableExtensions)); diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 0b13804fe..a2ef0e3a8 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 @@ -321,6 +321,14 @@ public static QualifiedMember ToTargetMemberOrNull( #if NET35 public static QualifiedMember ToTargetMember(this LinqExp.LambdaExpression memberAccess, MapperContext mapperContext) => memberAccess.ToDlrExpression().ToTargetMember(mapperContext); + + public static QualifiedMember ToTargetMember( + this LinqExp.LambdaExpression memberAccess, + MapperContext mapperContext, + Action nonMemberAction) + { + return memberAccess.ToDlrExpression().ToTargetMember(mapperContext, nonMemberAction); + } #endif public static QualifiedMember ToTargetMember( this LambdaExpression memberAccess, @@ -443,6 +451,12 @@ private static void AdjustMemberAccessesIfRootedInMappingData(IList } var mappingDataRoot = memberAccesses[0]; + + if (mappingDataRoot.NodeType != ExpressionType.MemberAccess) + { + return; + } + expression = Parameters.Create(mappingDataRoot.Type); memberAccesses.RemoveAt(0); 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/ComplexTypes/TargetObjectResolutionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs index ee6425386..e15376517 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs @@ -43,7 +43,10 @@ public static Expression GetObjectResolution( if (objectValue == null) { - mapperData.TargetMember.IsReadOnly = true; + if (!mappingData.HasSameTypedConfiguredDataSource()) + { + mapperData.TargetMember.IsReadOnly = true; + } // Use the existing target object if it might have a value and // the mapper can't create an instance: 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/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 4240a942a..a9726b3ad 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -108,7 +108,7 @@ private static IEnumerable EnumerateTargetMembers( targetEntryMember.SetCustomKey(configuredKey); } - if (!sourceMember.IsSimple) + if (!sourceMember.IsSimple && !targetDictionaryMember.HasComplexEntries) { targetEntryMember = targetEntryMember.WithTypeOf(sourceMember); } @@ -303,7 +303,7 @@ private static Func GetDictionaryAssignmentFacto MappingCreationContext context, out bool useAssignmentOnly) { - if (context.MapperData.TargetMember.IsReadOnly) + if (!context.InstantiateLocalVariable || context.MapperData.TargetMember.IsReadOnly) { useAssignmentOnly = false; return null; @@ -324,13 +324,7 @@ private static Func GetDictionaryAssignmentFacto } useAssignmentOnly = false; - - if (context.InstantiateLocalVariable) - { - return GetParameterlessDictionaryAssignment; - } - - return null; + return GetParameterlessDictionaryAssignment; } private static bool SourceMemberIsDictionary( diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 55180fb5c..e4a51e34e 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -232,11 +232,21 @@ private void InsertSourceElementNullCheck( private Expression GetPopulation( IPopulationLoopData loopData, - QualifiedMember dictionaryEntryMember, + DictionaryTargetMember dictionaryEntryMember, IObjectMappingData dictionaryMappingData) { var elementMapping = loopData.GetElementMapping(dictionaryMappingData); + if (dictionaryEntryMember.HasKey && + dictionaryEntryMember.CheckExistingElementValue && + dictionaryMappingData.MapperData.TargetCouldBePopulated()) + { + elementMapping = elementMapping.Replace( + dictionaryMappingData.MapperData.GetTargetMemberDictionaryKey(), + dictionaryEntryMember.Key, + ExpressionEvaluation.Equivalator); + } + return GetPopulation(elementMapping, dictionaryEntryMember, dictionaryMappingData); } diff --git a/AgileMapper/ObjectPopulation/Enumerables/ElementObjectMapperKey.cs b/AgileMapper/ObjectPopulation/Enumerables/ElementObjectMapperKey.cs index 8f6ee0661..47d034eb2 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/ElementObjectMapperKey.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/ElementObjectMapperKey.cs @@ -15,17 +15,11 @@ public ElementObjectMapperKey(MappingTypes mappingTypes) { } - private ElementObjectMapperKey(MappingTypes mappingTypes, IMembersSource membersSource) - : this(mappingTypes) - { - _membersSource = membersSource; - } - public override IMembersSource GetMembersSource(ObjectMapperData enumerableMapperData) => _membersSource ?? (_membersSource = new ElementMembersSource(enumerableMapperData)); protected override ObjectMapperKeyBase CreateInstance(MappingTypes newMappingTypes) - => new ElementObjectMapperKey(newMappingTypes, _membersSource); + => new ElementObjectMapperKey(newMappingTypes) { _membersSource = _membersSource }; public override bool Equals(object obj) { 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/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 046c90079..b185fd4d6 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -566,7 +566,7 @@ public Expression GetElementConversion(Expression sourceElement, IObjectMappingD Expression existingElementValue; - if (targetMember.CheckExistingElementValue) + if (targetMember.CheckExistingElementValue && mappingData.MapperData.TargetCouldBePopulated()) { var existingElementValueCheck = targetMember.GetAccessChecked(mappingData.MapperData); 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..43a388755 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; @@ -49,8 +50,6 @@ public MappingCreationContext( public QualifiedMember TargetMember => MapperData.TargetMember; - public bool IsRoot => MappingData.IsRoot; - public IObjectMappingData MappingData { get; } public Expression PreMappingCallback { get; } @@ -106,7 +105,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 37c0e269b..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); - if (!configuredRootDataSource.IsConditional) + mapping = MappingFactory.UseLocalToTargetDataSourceVariableIfAppropriate( + context.MapperData, + newSourceContext.MapperData, + configuredToTargetDataSource.Value, + mapping); + + 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,23 +156,16 @@ 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) { - if (!mapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources) - { - dataSources = null; - return false; - } - dataSources = mapperData .MapperContext .UserConfigurations - .GetDataSources(mapperData) - .ToArray(); + .GetDataSourcesForToTarget(mapperData); return dataSources.Any(); } diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index f190f377f..8d34258f7 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -177,12 +177,18 @@ public static Expression GetInlineMappingBlock( if (mapper == null) { + if (mappingData.HasSameTypedConfiguredDataSource()) + { + // Configured data source for an otherwise-unconstructable complex type: + return mappingValues.SourceValue; + } + return Constants.EmptyExpression; } if (mapper.MapperData.Context.UsesMappingDataObject) { - return UseLocalSourceValueVariable( + return UseLocalValueVariable( mapper.MapperData.MappingDataObject, createMappingDataCall, mapper.MappingExpression); @@ -230,7 +236,7 @@ private static Expression GetDirectAccessMapping( .Replace(mapperData.MappingDataObject, createMappingDataCall); return useLocalSourceValueVariable - ? UseLocalSourceValueVariable((ParameterExpression)sourceValue, sourceValueVariableValue, mapping) + ? UseLocalValueVariable((ParameterExpression)sourceValue, sourceValueVariableValue, mapping) : mapping; } @@ -282,14 +288,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/ObjectMapper.cs b/AgileMapper/ObjectPopulation/ObjectMapper.cs index 3973fdb41..2984e72f1 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapper.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapper.cs @@ -111,12 +111,7 @@ public bool IsStaticallyCacheable() return false; } - if (_subMappersByKey == null) - { - return true; - } - - return _subMappersByKey.Values.All(subMapperByKey => subMapperByKey.IsStaticallyCacheable()); + return _subMappersByKey?.Values.All(subMapperByKey => subMapperByKey.IsStaticallyCacheable()) == true; } public object Map(IObjectMappingData mappingData) => Map((ObjectMappingData)mappingData); 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( diff --git a/NuGet/AgileObjects.AgileMapper.1.4.0-preview.nupkg b/NuGet/AgileObjects.AgileMapper.1.4.0-preview.nupkg new file mode 100644 index 000000000..05de9e111 Binary files /dev/null and b/NuGet/AgileObjects.AgileMapper.1.4.0-preview.nupkg differ diff --git a/NuGet/AgileObjects.AgileMapper.1.4.0-preview2.nupkg b/NuGet/AgileObjects.AgileMapper.1.4.0-preview2.nupkg new file mode 100644 index 000000000..1ee7e5f10 Binary files /dev/null and b/NuGet/AgileObjects.AgileMapper.1.4.0-preview2.nupkg differ diff --git a/NuGet/AgileObjects.AgileMapper.1.4.0-preview3.nupkg b/NuGet/AgileObjects.AgileMapper.1.4.0-preview3.nupkg new file mode 100644 index 000000000..c6bad85d4 Binary files /dev/null and b/NuGet/AgileObjects.AgileMapper.1.4.0-preview3.nupkg differ diff --git a/NuGet/AgileObjects.AgileMapper.1.4.0.nupkg b/NuGet/AgileObjects.AgileMapper.1.4.0.nupkg new file mode 100644 index 000000000..78eef3333 Binary files /dev/null and b/NuGet/AgileObjects.AgileMapper.1.4.0.nupkg differ diff --git a/VersionInfo.cs b/VersionInfo.cs index 0f2eabcad..46d1940cf 100644 --- a/VersionInfo.cs +++ b/VersionInfo.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("1.3.0")] -[assembly: AssemblyFileVersion("1.3.0")] \ No newline at end of file +[assembly: AssemblyVersion("1.4.0")] +[assembly: AssemblyFileVersion("1.4.0")] \ No newline at end of file diff --git a/common.props b/common.props index 8c22d6e0e..24c678855 100644 --- a/common.props +++ b/common.props @@ -1,4 +1,4 @@ - + AgileObjects Ltd @@ -9,9 +9,9 @@ true git https://github.com/AgileObjects/AgileMapper - 1.3.0-preview - 1.3.0.0 - 1.3.0.0 + 1.4.0 + 1.4.0.0 + 1.4.0.0 \ No newline at end of file