From 61409975f6a11557e6dcf172742ea906c4638769 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 16 Sep 2018 09:00:38 +0100 Subject: [PATCH 1/6] Taking complex type argument data source conditions into account when selecting a constructor --- .../WhenMappingToNewComplexTypes.cs | 34 +++ .../Configuration/PotentialCloneExtensions.cs | 2 +- AgileMapper/DataSources/DataSourceSet.cs | 30 ++- .../Internal/EnumerableExtensions.cs | 14 ++ .../ComplexTypeConstructionFactory.cs | 219 +++++++++++++----- 5 files changed, 235 insertions(+), 64 deletions(-) diff --git a/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs b/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs index 09f11ab77..f3aa2cd3a 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs @@ -1,5 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests { + using System; + using AgileMapper.Extensions; using Common; using TestClasses; #if !NET35 @@ -112,5 +114,37 @@ public void ShouldHandleAnUnconstructableRootTargetType() result.ShouldBeNull(); } + + [Fact] + public void ShouldConditionallyUseConstructorsWhereArgumentsAreNull() + { + var source = new CtorTester("Test"); + var result = source.DeepClone(); + + result.Value.ShouldBe("Test"); + result.Address.ShouldBeNull(); + } + + #region Helper Classes + + private class CtorTester + { + public CtorTester(string value) + { + Value = value; + } + + public CtorTester(string value, Address address) + { + Value = value; + Address = address ?? throw new ArgumentNullException(nameof(address)); + } + + public string Value { get; } + + public Address Address { get; } + } + + #endregion } } diff --git a/AgileMapper/Configuration/PotentialCloneExtensions.cs b/AgileMapper/Configuration/PotentialCloneExtensions.cs index 85366a02e..5f4e3d9cc 100644 --- a/AgileMapper/Configuration/PotentialCloneExtensions.cs +++ b/AgileMapper/Configuration/PotentialCloneExtensions.cs @@ -21,7 +21,7 @@ public static IList CloneItems(this IList cloneableItems) return clonedItems; } - public static void AddSorted(this List items, T newItem) + public static void AddSorted(this IList items, T newItem) where T : IComparable { if (items.None()) diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index 910289a6a..0a9c0a10b 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Collections; using System.Collections.Generic; + using Extensions; using Extensions.Internal; using Members; #if NET35 @@ -16,9 +17,7 @@ internal class DataSourceSet : IEnumerable private readonly List _variables; private Expression _value; - public DataSourceSet( - IMemberMapperData mapperData, - params IDataSource[] dataSources) + public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSources) { MapperData = mapperData; _dataSources = dataSources; @@ -39,6 +38,11 @@ public DataSourceSet( HasValue = true; } + if (dataSource.IsConditional) + { + IsConditional = true; + } + if (dataSource.Variables.Any()) { _variables.AddRange(dataSource.Variables); @@ -57,6 +61,26 @@ public DataSourceSet( public bool HasValue { get; } + public bool IsConditional { get; } + + public Expression BuildConditions() + { + var conditions = default(Expression); + + foreach (var dataSource in _dataSources.Filter(ds => ds.IsConditional)) + { + if (conditions == null) + { + conditions = dataSource.Condition; + continue; + } + + conditions = Expression.AndAlso(conditions, dataSource.Condition); + } + + return conditions; + } + public Expression SourceMemberTypeTest { get; } public ICollection Variables => _variables; diff --git a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index b106f25ac..9eabfce77 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -58,6 +58,20 @@ public static T First(this IList items, Func predicate) public static T FirstOrDefault(this IList items, Func predicate) => TryFindMatch(items, predicate, out var match) ? match : default(T); + [DebuggerStepThrough] + public static IEnumerable TakeUntil(this IEnumerable items, Func predicate) + { + foreach (var item in items) + { + yield return item; + + if (predicate.Invoke(item)) + { + yield break; + } + } + } + [DebuggerStepThrough] public static bool TryFindMatch(this IList items, Func predicate, out T match) { diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index 138f48fd0..8f7e36f62 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -5,6 +5,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using System.Linq; using System.Reflection; using Caching; + using Configuration; using DataSources; using DataSources.Finders; using Extensions; @@ -41,7 +42,7 @@ public Expression GetNewObjectCreation(IObjectMappingData mappingData) if (otherConstructionRequired && !key.MappingData.MapperData.TargetType.IsAbstract()) { - AddAutoConstruction(constructions, key); + AddAutoConstructions(constructions, key); } if (constructions.None()) @@ -73,9 +74,11 @@ public Expression GetNewObjectCreation(IObjectMappingData mappingData) public Expression GetFactoryMethodObjectCreationOrNull(IObjectMappingData mappingData) { var key = new ConstructionKey(mappingData); - var factoryData = GetGreediestAvailableFactoryData(key); + var factoryData = GetGreediestAvailableFactories(key); - return factoryData?.Construction.With(key).GetConstruction(mappingData); + return factoryData.Any() + ? factoryData.First().Construction.With(key).GetConstruction(mappingData) + : null; } private static void AddConfiguredConstructions( @@ -106,54 +109,50 @@ private static void AddConfiguredConstructions( } } - private static void AddAutoConstruction(ICollection constructions, ConstructionKey key) + private static void AddAutoConstructions(IList constructions, ConstructionKey key) { var mapperData = key.MappingData.MapperData; - var greediestAvailableFactory = GetGreediestAvailableFactoryData(key); + var greediestAvailableFactories = GetGreediestAvailableFactories(key); + var greediestUnconditionalFactory = greediestAvailableFactories.LastOrDefault(f => f.IsUnconditional); var constructors = mapperData.TargetInstance.Type .GetPublicInstanceConstructors() .ToArray(); - var greedierAvailableNewing = constructors.Any() - ? constructors - .Filter(ctor => IsCandidateCtor(ctor, greediestAvailableFactory)) - .Project(ctor => new ConstructionData(ctor, Expression.New, key)) - .Filter(ctor => ctor.CanBeInvoked) - .OrderByDescending(ctor => ctor.NumberOfParameters) - .FirstOrDefault() - : null; + var greediestAvailableNewings = constructors.Any() + ? GetGreediestAvailableNewings(constructors, key, greediestUnconditionalFactory) + : Enumerable>.EmptyArray; + + int i; - if (greedierAvailableNewing != null) + for (i = 0; i < greediestAvailableFactories.Length; i++) { - greedierAvailableNewing.AddTo(constructions, key); - return; + greediestAvailableFactories[i].AddTo(constructions, key); } - if (greediestAvailableFactory != null) + for (i = 0; i < greediestAvailableNewings.Length; i++) { - greediestAvailableFactory.AddTo(constructions, key); - return; + greediestAvailableNewings[i].AddTo(constructions, key); } - if (constructors.None() && mapperData.TargetMemberIsUserStruct()) + if (constructions.None() && mapperData.TargetMemberIsUserStruct()) { constructions.Add(Construction.NewStruct(mapperData.TargetInstance.Type)); } } - private static ConstructionData GetGreediestAvailableFactoryData(ConstructionKey key) + private static ConstructionData[] GetGreediestAvailableFactories(ConstructionKey key) { var mapperData = key.MappingData.MapperData; - return mapperData.TargetInstance.Type + var candidateFactoryMethods = mapperData.TargetInstance.Type .GetPublicStaticMethods() - .Filter(m => IsFactoryMethod(m, mapperData.TargetInstance.Type)) - .Project(fm => new ConstructionData(fm, Expression.Call, key)) - .Filter(fm => fm.CanBeInvoked) - .OrderByDescending(fm => fm.NumberOfParameters) - .FirstOrDefault(); + .Filter(m => IsFactoryMethod(m, mapperData.TargetInstance.Type)); + + return CreateConstructionData( + candidateFactoryMethods, + fm => new ConstructionData(fm, Expression.Call, key, priority: 1)); } private static bool IsFactoryMethod(MethodInfo method, Type targetType) @@ -162,12 +161,25 @@ private static bool IsFactoryMethod(MethodInfo method, Type targetType) (method.Name.StartsWith("Create", Ordinal) || method.Name.StartsWith("Get", Ordinal)); } + private static ConstructionData[] GetGreediestAvailableNewings( + IEnumerable constructors, + ConstructionKey key, + ConstructionData greediestUnconditionalFactory) + { + var candidateConstructors = constructors + .Filter(ctor => IsCandidateCtor(ctor, greediestUnconditionalFactory)); + + return CreateConstructionData( + candidateConstructors, + ctor => new ConstructionData(ctor, Expression.New, key, priority: 0)); + } + private static bool IsCandidateCtor(MethodBase ctor, ConstructionData candidateFactoryMethod) { var ctorCarameters = ctor.GetParameters(); return ((candidateFactoryMethod == null) || - (candidateFactoryMethod.NumberOfParameters < ctorCarameters.Length)) && + (candidateFactoryMethod.ParameterCount < ctorCarameters.Length)) && IsNotCopyConstructor(ctor.DeclaringType, ctorCarameters); } @@ -178,6 +190,19 @@ private static bool IsNotCopyConstructor(Type type, IList ctorPar return ctorParameters.None(p => p.ParameterType == type); } + private static ConstructionData[] CreateConstructionData( + IEnumerable invokables, + Func> dataFactory) + where T : MethodBase + { + return invokables + .OrderByDescending(fm => fm.GetParameters().Length) + .Project(dataFactory.Invoke) + .Filter(fm => fm.CanBeInvoked) + .TakeUntil(fm => fm.IsUnconditional) + .ToArray(); + } + public void Reset() => _constructorsCache.Empty(); #region Helper Classes @@ -215,55 +240,75 @@ public override bool Equals(object obj) public override int GetHashCode() => 0; } - private class ConstructionData + private class ConstructionData : IConstructionInfo where TInvokable : MethodBase { - private readonly IEnumerable> _argumentDataSources; + private readonly Tuple[] _argumentDataSources; public ConstructionData( TInvokable invokable, Func, Expression> constructionFactory, - ConstructionKey key) + ConstructionKey key, + int priority) { var argumentDataSources = GetArgumentDataSources(invokable, key); CanBeInvoked = argumentDataSources.All(ds => ds.Item2.HasValue); - NumberOfParameters = argumentDataSources.Length; + ParameterCount = argumentDataSources.Length; + Priority = priority; if (!CanBeInvoked) { return; } - IList variables; - IList argumentValues; + IsUnconditional = true; + + Expression constructionExpression; if (argumentDataSources.None()) { - variables = Enumerable.EmptyArray; - argumentValues = Enumerable.EmptyArray; - _argumentDataSources = Enumerable>.Empty; + constructionExpression = constructionFactory.Invoke(invokable, Enumerable.EmptyArray); + Construction = new Construction(this, constructionExpression); + return; } - else + + _argumentDataSources = argumentDataSources; + + var variables = new List(); + var argumentValues = new List(ParameterCount); + var condition = default(Expression); + + foreach (var argumentDataSource in argumentDataSources) { - var vars = new List(); - argumentValues = new List(NumberOfParameters); + var dataSources = argumentDataSource.Item2; + + variables.AddRange(dataSources.Variables); + argumentValues.Add(dataSources.ValueExpression); + + if (!argumentDataSource.Item1.IsComplex || !dataSources.IsConditional) + { + continue; + } + + IsUnconditional = false; - foreach (var argumentDataSource in argumentDataSources) + var dataSourceCondition = dataSources.BuildConditions(); + + if (condition == null) { - vars.AddRange(argumentDataSource.Item2.Variables); - argumentValues.Add(argumentDataSource.Item2.ValueExpression); + condition = dataSourceCondition; + continue; } - variables = vars.Any() ? (IList)vars : Enumerable.EmptyArray; - _argumentDataSources = argumentDataSources; + condition = Expression.AndAlso(condition, dataSourceCondition); } - var constructionExpression = constructionFactory.Invoke(invokable, argumentValues); + constructionExpression = constructionFactory.Invoke(invokable, argumentValues); Construction = variables.None() - ? new Construction(constructionExpression) - : new Construction(Expression.Block(variables, constructionExpression)); + ? new Construction(this, constructionExpression, condition) + : new Construction(this, Expression.Block(variables, constructionExpression), condition); } private static Tuple[] GetArgumentDataSources(TInvokable invokable, ConstructionKey key) @@ -286,13 +331,17 @@ private static Tuple[] GetArgumentDataSources(TI public bool CanBeInvoked { get; } - public int NumberOfParameters { get; } + public bool IsUnconditional { get; } + + public int ParameterCount { get; } + + public int Priority { get; } public Construction Construction { get; } - public void AddTo(ICollection constructions, ConstructionKey key) + public void AddTo(IList constructions, ConstructionKey key) { - if (NumberOfParameters > 0) + if (ParameterCount > 0) { foreach (var memberAndDataSourceSet in _argumentDataSources) { @@ -302,28 +351,44 @@ public void AddTo(ICollection constructions, ConstructionKey key) } } - constructions.Add(Construction); + constructions.AddSorted(Construction); } } - private class Construction : IConditionallyChainable + private interface IConstructionInfo + { + int ParameterCount { get; } + + int Priority { get; } + } + + private class Construction : IConditionallyChainable, IComparable { private readonly Expression _construction; + private readonly bool _isConfigured; + private readonly IConstructionInfo _info; private ParameterExpression _mappingDataObject; - private Construction(IList constructions) - : this(constructions.ReverseChain()) - { - UsesMappingDataObjectParameter = constructions.Any(c => c.UsesMappingDataObjectParameter); - } - public Construction(ConfiguredObjectFactory configuredFactory, IMemberMapperData mapperData) : this(configuredFactory.Create(mapperData), configuredFactory.GetConditionOrNull(mapperData)) { UsesMappingDataObjectParameter = configuredFactory.UsesMappingDataObjectParameter; + _isConfigured = true; } - public Construction(Expression construction, Expression condition = null) + public Construction(IConstructionInfo info, Expression construction, Expression condition = null) + : this(construction, condition) + { + _info = info; + } + + private Construction(IList constructions) + : this(constructions.ReverseChain()) + { + UsesMappingDataObjectParameter = constructions.Any(c => c.UsesMappingDataObjectParameter); + } + + private Construction(Expression construction, Expression condition = null) { _construction = construction; Condition = condition; @@ -367,6 +432,40 @@ public Construction With(ConstructionKey key) public Expression GetConstruction(IObjectMappingData mappingData) => _construction.Replace(_mappingDataObject, mappingData.MapperData.MappingDataObject); + + public int CompareTo(Construction other) + { + // ReSharper disable once ImpureMethodCallOnReadonlyValueField + var isConfiguredComparison = other._isConfigured.CompareTo(_isConfigured); + + if (isConfiguredComparison != 0) + { + return isConfiguredComparison; + } + + var conditionalComparison = IsUnconditional.CompareTo(other.IsUnconditional); + + if (conditionalComparison != 0) + { + return conditionalComparison; + } + + var paramCountComparison = _info.ParameterCount.CompareTo(other._info.ParameterCount); + + if (paramCountComparison != 0) + { + return paramCountComparison; + } + + var priorityComparison = _info.Priority.CompareTo(other._info.Priority); + + if (priorityComparison != 0) + { + return priorityComparison; + } + + return 0; + } } #endregion From d0e2568a3ebb04b40fdfcfeb454cae54c6780cc0 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 16 Sep 2018 09:09:52 +0100 Subject: [PATCH 2/6] Extending test coverage --- .../WhenMappingToNewComplexTypes.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs b/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs index f3aa2cd3a..d766809df 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs @@ -118,11 +118,19 @@ public void ShouldHandleAnUnconstructableRootTargetType() [Fact] public void ShouldConditionallyUseConstructorsWhereArgumentsAreNull() { - var source = new CtorTester("Test"); - var result = source.DeepClone(); + var noAddressSource = new CtorTester("Test 1"); + var noAddressResult = noAddressSource.DeepClone(); - result.Value.ShouldBe("Test"); - result.Address.ShouldBeNull(); + noAddressResult.Value.ShouldBe("Test 1"); + noAddressResult.Address.ShouldBeNull(); + + var addressSource = new CtorTester("Test 2", new Address { Line1 = "Line 1!" }); + var addressResult = addressSource.DeepClone(); + + addressResult.Value.ShouldBe("Test 2"); + addressResult.Address.ShouldNotBeNull(); + addressResult.Address.ShouldNotBeSameAs(addressSource.Address); + addressResult.Address.Line1.ShouldBe("Line 1!"); } #region Helper Classes From 63300886349acef64dba4488adceb143ada8e9c5 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 16 Sep 2018 09:18:23 +0100 Subject: [PATCH 3/6] Test coverage for multiple conditional data sources for a constructor argument --- .../WhenConfiguringConstructorDataSources.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs index a9e3333aa..c8db137f8 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs @@ -65,5 +65,61 @@ public void ShouldApplyAConfiguredExpressionByParameterName() result.Value.ShouldBe(222); } } + + [Fact] + public void ShouldApplyMultipleConfiguredSourceValues() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToANew>() + .If(ctx => ctx.Source.Value < 5) + .Map(0) + .ToCtor() + .But + .If(ctx => ctx.Source.Value < 10) + .Map(5) + .ToCtor() + .But + .If(ctx => ctx.Source.Value < 15) + .Map(10) + .ToCtor(); + + var lessThenFiveSource = new PublicField { Value = 4 }; + var lessthanFiveResult = mapper.Map(lessThenFiveSource).ToANew>(); + + lessthanFiveResult.Value.ShouldBe(0); + + var lessThenTenSource = new PublicField { Value = 8 }; + var lessthanTenResult = mapper.Map(lessThenTenSource).ToANew>(); + + lessthanTenResult.Value.ShouldBe(5); + + var lessThenFifteenSource = new PublicField { Value = 11 }; + var lessthanFifteenResult = mapper.Map(lessThenFifteenSource).ToANew>(); + + lessthanFifteenResult.Value.ShouldBe(10); + + var moreThanFifteenSource = new PublicField { Value = 123 }; + var morethanFifteenResult = mapper.Map(moreThanFifteenSource).ToANew>(); + + morethanFifteenResult.Value.ShouldBe(123); + } + } + + #region Helper Classes + + private class CtorTester + { + public CtorTester(T value) + { + Value = value; + } + + public T Value { get; } + } + + #endregion } } \ No newline at end of file From 622649326de9c8f766259acc7f65548be60641b0 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 16 Sep 2018 09:35:49 +0100 Subject: [PATCH 4/6] Support for multiple conditional complex type ctor argument data sources --- .../WhenConfiguringConstructorDataSources.cs | 63 +++++++++++++++++++ AgileMapper/DataSources/DataSourceSet.cs | 19 ------ .../Members/MemberMapperDataExtensions.cs | 1 - .../ComplexTypeConstructionFactory.cs | 20 +++++- 4 files changed, 82 insertions(+), 21 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs index c8db137f8..28a151d86 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { using System; + using AgileMapper.Extensions; using Common; using TestClasses; #if !NET35 @@ -108,6 +109,60 @@ public void ShouldApplyMultipleConfiguredSourceValues() } } + [Fact] + public void ShouldApplyMultipleConfiguredComplexTypeSourceValues() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToANew>() + .If(ctx => ctx.Source.Value < 5) + .Map(new Address { Line1 = "< 5" }) + .ToCtor
() + .But + .If(ctx => ctx.Source.Value < 10) + .Map(new Address { Line1 = "< 10" }) + .ToCtor
() + .But + .If(ctx => ctx.Source.Value < 15) + .Map(new Address { Line1 = "< 15" }) + .ToCtor
(); + + var lessThanFiveSource = new CtorTester(3); + var lessThanFiveResult = lessThanFiveSource.DeepCloneUsing(mapper); + + lessThanFiveResult.Value.ShouldBe(3); + lessThanFiveResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 5"); + + var lessThanTenSource = new CtorTester(6); + var lessThanTenResult = lessThanTenSource.DeepCloneUsing(mapper); + + lessThanTenResult.Value.ShouldBe(6); + lessThanTenResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 10"); + + var lessThanFifteenSource = new CtorTester(14); + var lessThanFifteenResult = lessThanFifteenSource.DeepCloneUsing(mapper); + + lessThanFifteenResult.Value.ShouldBe(14); + lessThanFifteenResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 15"); + + var addressSource = new CtorTester(123, new Address { Line1 = "One!", Line2 = "Two!" }); + var addressResult = addressSource.DeepCloneUsing(mapper); + + addressResult.Value.ShouldBe(123); + addressResult.Address.ShouldNotBeNull().ShouldNotBeSameAs(addressSource.Address); + addressResult.Address.Line1.ShouldBe("One!"); + addressResult.Address.Line2.ShouldBe("Two!"); + + var noAddressSource = new CtorTester(789); + var noAddressResult = noAddressSource.DeepCloneUsing(mapper); + + noAddressResult.Value.ShouldBe(789); + noAddressResult.Address.ShouldBeNull(); + } + } + #region Helper Classes private class CtorTester @@ -117,7 +172,15 @@ public CtorTester(T value) Value = value; } + public CtorTester(T value, Address address) + { + Value = value; + Address = address; + } + public T Value { get; } + + public Address Address { get; } } #endregion diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index 0a9c0a10b..a636d0b52 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -2,7 +2,6 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Collections; using System.Collections.Generic; - using Extensions; using Extensions.Internal; using Members; #if NET35 @@ -63,24 +62,6 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour public bool IsConditional { get; } - public Expression BuildConditions() - { - var conditions = default(Expression); - - foreach (var dataSource in _dataSources.Filter(ds => ds.IsConditional)) - { - if (conditions == null) - { - conditions = dataSource.Condition; - continue; - } - - conditions = Expression.AndAlso(conditions, dataSource.Condition); - } - - return conditions; - } - public Expression SourceMemberTypeTest { get; } public ICollection Variables => _variables; diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index cabeda6e4..2358c66ac 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -180,7 +180,6 @@ public static bool TargetMemberIsUnmappable(this IMemberMapperData mapperData, o } if (mapperData.TargetMember.LeafMember.HasMatchingCtorParameter && - mapperData.TargetMember.LeafMember.IsWriteable && ((mapperData.Parent?.IsRoot != true) || !mapperData.RuleSet.Settings.RootHasPopulatedTarget)) { diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index 8f7e36f62..342ab51b3 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -293,7 +293,7 @@ public ConstructionData( IsUnconditional = false; - var dataSourceCondition = dataSources.BuildConditions(); + var dataSourceCondition = BuildConditions(dataSources); if (condition == null) { @@ -329,6 +329,24 @@ private static Tuple[] GetArgumentDataSources(TI .ToArray(); } + private static Expression BuildConditions(DataSourceSet dataSources) + { + var conditions = default(Expression); + + foreach (var dataSource in dataSources.Filter(ds => ds.IsConditional)) + { + if (conditions == null) + { + conditions = dataSource.Condition; + continue; + } + + conditions = Expression.OrElse(conditions, dataSource.Condition); + } + + return conditions; + } + public bool CanBeInvoked { get; } public bool IsUnconditional { get; } From 9b41f185421c6c4674a66a6439961621da592985 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 16 Sep 2018 10:32:39 +0100 Subject: [PATCH 5/6] Fixing creation method ordering / Extending test coverage --- .../WhenConfiguringObjectCreation.cs | 77 +++++++++++++++++++ .../ComplexTypeConstructionFactory.cs | 9 +-- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs index 2de842076..e7a714c7b 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs @@ -334,6 +334,50 @@ public void ShouldUseAConfiguredFactoryForAnUnconstructableType() } } + [Fact] + public void ShouldPrioritiseCreationMethods() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToANew() + .If(ctx => ctx.Source.Value1 < 5) + .CreateInstancesUsing(ctx => new ConstructionTester(100, 100)) + .But + .If(ctx => ctx.Source.Value1 < 10) + .CreateInstancesUsing(ctx => new ConstructionTester(500, 500)); + + var lessThanFiveSource = new ConstructionTester(2); + var lessThanFiveResult = mapper.Map(lessThanFiveSource).ToANew(); + + lessThanFiveResult.Value1.ShouldBe(100); + lessThanFiveResult.Value2.ShouldBe(100); + lessThanFiveResult.Address.ShouldBeNull(); + + var lessThanTenSource = new ConstructionTester(8); + var lessThanTenResult = mapper.Map(lessThanTenSource).ToANew(); + + lessThanTenResult.Value1.ShouldBe(500); + lessThanTenResult.Value2.ShouldBe(500); + lessThanTenResult.Address.ShouldBeNull(); + + var addressSource = new ConstructionTester(123, 456, new Address { Line1 = "One!" }); + var addressResult = mapper.Map(addressSource).ToANew(); + + addressResult.Value1.ShouldBe(123); + addressResult.Value2.ShouldBe(456); + addressResult.Address.ShouldNotBeNull().Line1.ShouldBe("One!"); + + var noAddressSource = new ConstructionTester(123, 456); + var noAddressResult = mapper.Map(noAddressSource).ToANew(); + + noAddressResult.Value1.ShouldBe(123); + noAddressResult.Value2.ShouldBe(456); + noAddressResult.Address.ShouldBeNull(); + } + } + [Fact] public void ShouldHandleAnObjectMappingDataCreationException() { @@ -562,6 +606,7 @@ public class Parent { public Status.StatusId ParentStatusId { get; set; } + // ReSharper disable once UnusedMember.Global public Status ParentStatus => Status.GetStatus(ParentStatusId); } @@ -573,6 +618,38 @@ public class ParentDto } } + private class ConstructionTester + { + public ConstructionTester(int value1) + : this(value1, default(int)) + { + } + + public ConstructionTester(int value1, int value2) + : this(value1, value2, default(Address)) + { + } + + public ConstructionTester(int value1, int value2, Address address) + { + Value1 = value1; + Value2 = value2; + Address = address; + } + + public static ConstructionTester Create(int value1, int value2) + => new ConstructionTester(value1, value2); + + public static ConstructionTester GetInstance(int value1, int value2, Address address) + => new ConstructionTester(value1, value2, address); + + public int Value1 { get; } + + public int Value2 { get; } + + public Address Address { get; } + } + #endregion } } diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index 342ab51b3..7725b8565 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -475,14 +475,9 @@ public int CompareTo(Construction other) return paramCountComparison; } - var priorityComparison = _info.Priority.CompareTo(other._info.Priority); + var priorityComparison = other._info.Priority.CompareTo(_info.Priority); - if (priorityComparison != 0) - { - return priorityComparison; - } - - return 0; + return priorityComparison; } } From 1a28336663a2f5691f8a19dbd166e4cf956df1f9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 16 Sep 2018 10:42:12 +0100 Subject: [PATCH 6/6] Fixing projection validation tests --- .../Configuration/Inline/WhenValidatingProjectionsInline.cs | 6 +++--- AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AgileMapper.UnitTests.Orms.EfCore2/Configuration/Inline/WhenValidatingProjectionsInline.cs b/AgileMapper.UnitTests.Orms.EfCore2/Configuration/Inline/WhenValidatingProjectionsInline.cs index 716e73746..ae6c39b54 100644 --- a/AgileMapper.UnitTests.Orms.EfCore2/Configuration/Inline/WhenValidatingProjectionsInline.cs +++ b/AgileMapper.UnitTests.Orms.EfCore2/Configuration/Inline/WhenValidatingProjectionsInline.cs @@ -49,15 +49,15 @@ public Task ShouldErrorIfCachedProjectionTargetTypeIsUnconstructableInline() await context .Addresses .ProjectUsing(mapper) - .To(cfg => cfg + .To(cfg => cfg .ThrowNowIfMappingPlanIsIncomplete()) .FirstOrDefaultAsync(); }); - validationEx.Message.ShouldContain("IQueryable
-> IQueryable"); + validationEx.Message.ShouldContain("IQueryable
-> IQueryable"); validationEx.Message.ShouldContain("Rule set: Project"); validationEx.Message.ShouldContain("Unconstructable target Types"); - validationEx.Message.ShouldContain("Address -> ProductStruct"); + validationEx.Message.ShouldContain("Address -> PublicStringCtorDto"); }); } } diff --git a/AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs b/AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs index 9c5d4aa77..e40faa267 100644 --- a/AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs +++ b/AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs @@ -53,15 +53,15 @@ public Task ShouldErrorIfCachedProjectionTargetTypeIsUnconstructable() { return RunTest((context, mapper) => { - mapper.GetPlanForProjecting(context.Addresses).To(); + mapper.GetPlanForProjecting(context.Addresses).To(); var validationEx = Should.Throw(() => mapper.ThrowNowIfAnyMappingPlanIsIncomplete()); - validationEx.Message.ShouldContain("IQueryable
-> IQueryable"); + validationEx.Message.ShouldContain("IQueryable
-> IQueryable"); validationEx.Message.ShouldContain("Rule set: Project"); validationEx.Message.ShouldContain("Unconstructable target Types"); - validationEx.Message.ShouldContain("Address -> ProductStruct"); + validationEx.Message.ShouldContain("Address -> PublicStringCtorDto"); return Task.CompletedTask; });