From 3d4077123f4ef92096c840d7680159957dfc735b Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 7 Apr 2019 12:56:10 +0100 Subject: [PATCH 1/3] Adding failing test --- .../WhenConfiguringDerivedTypes.cs | 90 +++++++++++++++++++ .../DerivedPairTargetTypeSpecifier.cs | 67 ++++++++++++-- .../Configuration/MappingConfigInfo.cs | 3 +- 3 files changed, 150 insertions(+), 10 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs index 399f12efc..62936a911 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDerivedTypes.cs @@ -233,6 +233,37 @@ public void ShouldUseATypedToTarget() } } + // 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(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(); + } + } + #region Helper Classes internal class Issue123 @@ -303,6 +334,65 @@ public class Leaf : ILeaf } } + internal 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 Wrapper + { + public enum ConcreteValueType { Situation, Action } + + public ConcreteValueType ConcreteValue { get; set; } + + public SituationObject SituationValue { get; set; } + + public ActionObject ActionValue { 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 SituationObject : ITrafficObj + { + public SituationObject(SituationClass clazz) { CurrentClass = clazz; } + + public ITrafficClass CurrentClass { get; private set; } + } + + public class ActionObject : ITrafficObj + { + public ActionObject(ActionClass clazz) { CurrentClass = clazz; } + + public ITrafficClass CurrentClass { get; private set; } + } + } + } + #endregion } } diff --git a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs index 36a34f78a..e59730ea6 100644 --- a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs +++ b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs @@ -1,8 +1,11 @@ namespace AgileObjects.AgileMapper.Api.Configuration { + using System; using AgileMapper.Configuration; + using Extensions; using Extensions.Internal; using NetStandardPolyfills; + using ObjectPopulation; using Projection; using ReadableExpressions.Extensions; @@ -17,6 +20,8 @@ public DerivedPairTargetTypeSpecifier(MappingConfigInfo configInfo) _configInfo = configInfo; } + private MapperContext MapperContext => _configInfo.MapperContext; + public IMappingConfigContinuation To() where TDerivedTarget : TTarget { @@ -33,7 +38,7 @@ private MappingConfigContinuation SetDerivedTargetType(_configInfo); - _configInfo.MapperContext.UserConfigurations.DerivedTypes.Add(derivedTypePair); + MapperContext.UserConfigurations.DerivedTypes.Add(derivedTypePair); return new MappingConfigContinuation(_configInfo); } @@ -42,12 +47,12 @@ private void ThrowIfUnconstructable() { var mappingData = _configInfo.ToMappingData(); - var objectCreation = _configInfo - .MapperContext - .ConstructionFactory - .GetNewObjectCreation(mappingData); + if (IsConstructableUsing(mappingData)) + { + return; + } - if (objectCreation != null) + if (IsConstructableFromToTargetDataSources(mappingData, typeof(TDerivedTarget))) { return; } @@ -57,12 +62,11 @@ private void ThrowIfUnconstructable() ThrowUnableToCreate(); } - var configuredImplementationPairings = _configInfo - .MapperContext + var configuredImplementationPairings = MapperContext .UserConfigurations .DerivedTypes.GetImplementationTypePairsFor( _configInfo.ToMapperData(), - _configInfo.MapperContext); + MapperContext); if (configuredImplementationPairings.None()) { @@ -70,6 +74,51 @@ private void ThrowIfUnconstructable() } } + private bool IsConstructableUsing(IObjectMappingData mappingData) + => MapperContext.ConstructionFactory.GetNewObjectCreation(mappingData) != null; + + private bool IsConstructableFromToTargetDataSources(IObjectMappingData mappingData, Type derivedTargetType) + { + if (!MapperContext.UserConfigurations.HasConfiguredRootDataSources) + { + return false; + } + + var toTargetDataSources = MapperContext + .UserConfigurations + .GetDataSources(mappingData.MapperData) + .ToArray(); + + if (toTargetDataSources.None()) + { + return false; + } + + var constructionCheckMethod = typeof(DerivedPairTargetTypeSpecifier) + .GetNonPublicInstanceMethod(nameof(IsConstructableFromDataSource)); + + foreach (var dataSource in toTargetDataSources) + { + var isConstructable = (bool)constructionCheckMethod + .MakeGenericMethod(dataSource.SourceMember.Type, derivedTargetType) + .Invoke(this, Enumerable.EmptyArray); + + if (isConstructable) + { + return true; + } + } + + return false; + } + + private bool IsConstructableFromDataSource() + { + var mappingData = _configInfo.ToMappingData(); + + return IsConstructableUsing(mappingData); + } + private static void ThrowUnableToCreate() { throw new MappingConfigurationException( diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index d2f36b391..c7ad43150 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -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; } From b74f33c390859cb7dad3a085012818bcb35ab275 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 11 Apr 2019 10:53:06 +0100 Subject: [PATCH 2/3] Short-circuiting expression replacement when target and replacement are the same --- .../Extensions/Internal/ExpressionExtensions.Replace.cs | 5 +++++ 1 file changed, 5 insertions(+) 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; From c67562953c44008b2761d1e2d186ba8a18ae572c Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 11 Apr 2019 11:43:06 +0100 Subject: [PATCH 3/3] Specifically retrieving ToTarget data sources where required / Returning existing arrays in ICollection.ToArray if possible --- .../DerivedPairTargetTypeSpecifier.cs | 9 +------- .../Configuration/ConfiguredLambdaInfo.cs | 3 +-- .../Configuration/UserConfigurationSet.cs | 23 ++++++++++++++++--- .../Finders/SourceMemberDataSourceFinder.cs | 2 +- .../Internal/EnumerableExtensions.cs | 9 ++++++++ .../Extensions/PublicEnumerableExtensions.cs | 21 ++++++++--------- .../MappingExpressionFactoryBase.cs | 9 +------- 7 files changed, 43 insertions(+), 33 deletions(-) diff --git a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs index e59730ea6..c72771948 100644 --- a/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs +++ b/AgileMapper/Api/Configuration/DerivedPairTargetTypeSpecifier.cs @@ -2,7 +2,6 @@ { using System; using AgileMapper.Configuration; - using Extensions; using Extensions.Internal; using NetStandardPolyfills; using ObjectPopulation; @@ -79,15 +78,9 @@ private bool IsConstructableUsing(IObjectMappingData mappingData) private bool IsConstructableFromToTargetDataSources(IObjectMappingData mappingData, Type derivedTargetType) { - if (!MapperContext.UserConfigurations.HasConfiguredRootDataSources) - { - return false; - } - var toTargetDataSources = MapperContext .UserConfigurations - .GetDataSources(mappingData.MapperData) - .ToArray(); + .GetDataSourcesForToTarget(mappingData.MapperData); if (toTargetDataSources.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/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index e1265c403..4852dd2d6 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -358,10 +358,27 @@ public ConfiguredDataSourceFactory GetDataSourceFactoryFor(MappingConfigInfo con public bool HasConfiguredRootDataSources { get; private set; } public IList GetDataSources(IMemberMapperData mapperData) + => GetDataSources(QueryDataSourceFactories(mapperData), mapperData); + + public IList GetDataSourcesForToTarget(IMemberMapperData mapperData) + { + if (!HasConfiguredRootDataSources) + { + 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/DataSources/Finders/SourceMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs index 27be4f7d3..0ea0d4a7c 100644 --- a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs @@ -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/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/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/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 37c0e269b..6264f8bff 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -172,17 +172,10 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr protected static bool HasConfiguredRootDataSources(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(); }