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