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/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/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/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/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index 8af37e956..b9c853b8e 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -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 diff --git a/AgileMapper/MappingDataExtensions.cs b/AgileMapper/MappingDataExtensions.cs index eea14457d..fac6dc77e 100644 --- a/AgileMapper/MappingDataExtensions.cs +++ b/AgileMapper/MappingDataExtensions.cs @@ -7,6 +7,7 @@ #endif using DataSources; using Extensions.Internal; + using Members; using ObjectPopulation; internal static class MappingDataExtensions @@ -54,5 +55,12 @@ public static Expression GetTargetObjectCreation(this IObjectMappingData mapping .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/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 67ea4d890..a2ef0e3a8 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -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/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/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index 2ccd93b48..8d34258f7 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -177,6 +177,12 @@ 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; }