From 62c40275941c7529f9deb2ed6f6ad2c251239acf Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 28 Jun 2018 19:35:06 +0100 Subject: [PATCH 01/16] Support for configuring complex type members as sources for root target mapping, re: issue #64 / Updating type converters to structs --- .../WhenConfiguringDataSources.cs | 24 +++++ ...henCreatingTargetMembersFromExpressions.cs | 2 +- .../MappingConfigStartingPointBase.cs | 2 +- .../Configuration/UserConfigurationSet.cs | 9 +- .../Configuration/UserConfiguredItemBase.cs | 5 +- .../DataSources/ConfiguredDataSource.cs | 2 +- .../DataSources/DefaultValueDataSource.cs | 12 +++ .../DefaultValueDataSourceFactory.cs | 10 ++ .../DictionaryDataSourceFactory.cs | 10 +- .../DictionaryNonSimpleMemberDataSource.cs | 12 +++ .../ExistingMemberValueOrDefaultDataSource.cs | 46 +++++++++ ...ExistingOrDefaultValueDataSourceFactory.cs | 10 ++ .../Finders/ConfiguredDataSourceFinder.cs | 2 +- .../ConfiguredRootSourceDataSourceFinder.cs | 94 +++++++++++++++++++ .../DataSources/Finders/DataSourceFinder.cs | 9 +- .../Finders/MaptimeDataSourceFinder.cs | 4 +- .../Finders/MetaMemberDataSourceFinder.cs | 2 +- .../Finders/SourceMemberDataSourceFinder.cs | 2 +- .../Internal/EnumerableExtensions.cs | 3 + .../Internal/ExpressionExtensions.cs | 82 ++++++++++++++++ AgileMapper/MappingExecutor.cs | 1 - AgileMapper/MappingRuleSetCollection.cs | 2 +- AgileMapper/Members/ChildMemberMapperData.cs | 15 ++- AgileMapper/Members/ConfiguredSourceMember.cs | 2 + .../DictionaryEntrySourceMember.cs | 2 + .../Dictionaries/DictionarySourceMember.cs | 2 + .../Dictionaries/DictionaryTargetMember.cs | 67 +------------ .../Members/IChildMemberMappingData.cs | 3 - AgileMapper/Members/IQualifiedMember.cs | 2 + AgileMapper/Members/MemberExtensions.cs | 16 +++- .../Members/MemberMapperDataExtensions.cs | 2 +- AgileMapper/Members/QualifiedMember.cs | 2 +- AgileMapper/Members/SourceMemberMatcher.cs | 2 +- .../Sources/FixedMembersMembersSource.cs | 2 +- .../ChildMemberMappingData.cs | 55 ----------- .../DefaultValueDataSourceFactory.cs | 19 ---- .../DictionaryMappingExpressionFactory.cs | 2 +- ...ExistingOrDefaultValueDataSourceFactory.cs | 53 ----------- .../ObjectPopulation/IObjectMappingData.cs | 4 + .../MapperKeys/RootObjectMapperKey.cs | 17 ++-- .../MappingExpressionFactoryBase.cs | 14 +-- .../ObjectPopulation/ObjectMappingData.cs | 59 ++++++++++++ AgileMapper/TypeConversion/ConverterSet.cs | 13 +-- .../FallbackNonSimpleTypeValueConverter.cs | 4 +- AgileMapper/TypeConversion/ToBoolConverter.cs | 11 +-- .../TypeConversion/ToCharacterConverter.cs | 14 ++- .../TypeConversion/ToStringConverter.cs | 11 +-- 47 files changed, 451 insertions(+), 287 deletions(-) create mode 100644 AgileMapper/DataSources/DefaultValueDataSource.cs create mode 100644 AgileMapper/DataSources/DefaultValueDataSourceFactory.cs create mode 100644 AgileMapper/DataSources/DictionaryNonSimpleMemberDataSource.cs create mode 100644 AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs create mode 100644 AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs create mode 100644 AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs delete mode 100644 AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs delete mode 100644 AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 1660668f6..d56882512 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; + using AgileMapper.Extensions; using AgileMapper.Members; using TestClasses; using Xunit; @@ -993,6 +994,29 @@ public void ShouldAllowIdAndIdentifierConfiguration() } } + // See https://github.com/agileobjects/AgileMapper/issues/64 + [Fact] + public void ShouldApplyAConfiguredComplexTypeMemberToTheRootTarget() + { + using (var mapper = Mapper.CreateNew()) + { + var source = new { Value1 = 123, Value = new { Value2 = 456 } }; + + mapper.WhenMapping + .From(source) + .To>() + .Map((s, ptf) => s.Value) + .To(t => t); + + var result = source + .MapUsing(mapper) + .ToANew>(); + + result.Value1.ShouldBe(123); + result.Value2.ShouldBe(456); + } + } + // ReSharper disable once ClassNeverInstantiated.Local // ReSharper disable UnusedAutoPropertyAccessor.Local private class IdTester diff --git a/AgileMapper.UnitTests/Members/WhenCreatingTargetMembersFromExpressions.cs b/AgileMapper.UnitTests/Members/WhenCreatingTargetMembersFromExpressions.cs index d9d69fd18..9da189b01 100644 --- a/AgileMapper.UnitTests/Members/WhenCreatingTargetMembersFromExpressions.cs +++ b/AgileMapper.UnitTests/Members/WhenCreatingTargetMembersFromExpressions.cs @@ -44,7 +44,7 @@ public void ShouldCreateASetMethodMember() { Expression, Action>> setMethodAccess = x => x.SetValue; - var fieldMember = setMethodAccess.Body.ToTargetMember(DefaultMapperContext); + var fieldMember = setMethodAccess.ToTargetMember(DefaultMapperContext); fieldMember.Members().Count().ShouldBe(2); diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPointBase.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPointBase.cs index af1340f76..e31729ffa 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPointBase.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPointBase.cs @@ -24,7 +24,7 @@ internal MappingConfigStartingPointBase(MappingConfigInfo configInfo, CallbackPo internal CallbackSpecifier CreateCallbackSpecifier(LambdaExpression targetMemberLambda = null) { var targetMember = - targetMemberLambda?.Body.ToTargetMember(_configInfo.MapperContext) + targetMemberLambda?.ToTargetMember(_configInfo.MapperContext) ?? QualifiedMember.None; return new CallbackSpecifier(_configInfo, _callbackPosition, targetMember); diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index f78ee322b..83a287895 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -179,8 +179,15 @@ public void Add(ConfiguredDataSourceFactory dataSourceFactory) ThrowIfConflictingDataSourceExists(dataSourceFactory, (dsf, cDsf) => dsf.GetConflictMessage(cDsf)); DataSourceFactories.AddSortFilter(dataSourceFactory); + + if (!HasDataSourceFactoriesForRootTarget && dataSourceFactory.TargetMember.IsRoot) + { + HasDataSourceFactoriesForRootTarget = true; + } } + public bool HasDataSourceFactoriesForRootTarget { get; private set; } + public IList GetDataSources(IMemberMapperData mapperData) => QueryDataSourceFactories(mapperData).Select(dsf => dsf.Create(mapperData)).ToArray(); @@ -252,7 +259,7 @@ public bool ShortCircuitRecursion(IBasicMapperData mapperData) #region Validation - internal void ThrowIfMemberIsUnmappable(ConfiguredIgnoredMember ignoredMember) + private void ThrowIfMemberIsUnmappable(ConfiguredIgnoredMember ignoredMember) { if (ignoredMember.ConfigInfo.TargetMemberIsUnmappable( ignoredMember.TargetMember, diff --git a/AgileMapper/Configuration/UserConfiguredItemBase.cs b/AgileMapper/Configuration/UserConfiguredItemBase.cs index e875ec677..36fe9bea4 100644 --- a/AgileMapper/Configuration/UserConfiguredItemBase.cs +++ b/AgileMapper/Configuration/UserConfiguredItemBase.cs @@ -22,7 +22,7 @@ protected UserConfiguredItemBase(MappingConfigInfo configInfo, LambdaExpression private static QualifiedMember GetTargetMemberOrThrow(LambdaExpression lambda, MappingConfigInfo configInfo) { - var targetMember = lambda.Body.ToTargetMember(configInfo.MapperContext); + var targetMember = lambda.ToTargetMember(configInfo.MapperContext); if (targetMember == null) { @@ -115,7 +115,8 @@ private bool TargetMembersMatch(IBasicMapperData mapperData) return true; } - if (TargetMember == mapperData.TargetMember) + if ((TargetMember == mapperData.TargetMember) || + (TargetMember.IsRoot && mapperData.TargetMember.IsRoot)) { return true; } diff --git a/AgileMapper/DataSources/ConfiguredDataSource.cs b/AgileMapper/DataSources/ConfiguredDataSource.cs index a0cf72a13..3ab2d0167 100644 --- a/AgileMapper/DataSources/ConfiguredDataSource.cs +++ b/AgileMapper/DataSources/ConfiguredDataSource.cs @@ -45,7 +45,7 @@ private static Expression GetConvertedValue(Expression value, IMemberMapperData #endregion - private ConfiguredDataSource( + public ConfiguredDataSource( IQualifiedMember sourceMember, Expression configuredCondition, Expression convertedValue, diff --git a/AgileMapper/DataSources/DefaultValueDataSource.cs b/AgileMapper/DataSources/DefaultValueDataSource.cs new file mode 100644 index 000000000..38d1f8191 --- /dev/null +++ b/AgileMapper/DataSources/DefaultValueDataSource.cs @@ -0,0 +1,12 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using Members; + + internal class DefaultValueDataSource : DataSourceBase + { + public DefaultValueDataSource(IMemberMapperData mapperData) + : base(mapperData.SourceMember, mapperData.GetTargetMemberDefault()) + { + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/DefaultValueDataSourceFactory.cs b/AgileMapper/DataSources/DefaultValueDataSourceFactory.cs new file mode 100644 index 000000000..de2890765 --- /dev/null +++ b/AgileMapper/DataSources/DefaultValueDataSourceFactory.cs @@ -0,0 +1,10 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using Members; + + internal struct DefaultValueDataSourceFactory : IDataSourceFactory + { + public IDataSource Create(IMemberMapperData mapperData) + => new DefaultValueDataSource(mapperData); + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs index 2e7657b0a..74b7401b4 100644 --- a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs @@ -6,7 +6,7 @@ using Members; using Members.Dictionaries; - internal class DictionaryDataSourceFactory : IMaptimeDataSourceFactory + internal struct DictionaryDataSourceFactory : IMaptimeDataSourceFactory { public bool IsFor(IMemberMapperData mapperData) { @@ -92,13 +92,5 @@ private static DictionarySourceMember GetSourceMember(IMemberMapperData mapperDa return (DictionarySourceMember)parentMapperData.SourceMember; } - - private class DictionaryNonSimpleMemberDataSource : DataSourceBase - { - public DictionaryNonSimpleMemberDataSource(IQualifiedMember sourceMember, IMemberMapperData mapperData) - : base(sourceMember, sourceMember.GetQualifiedAccess(mapperData)) - { - } - } } } \ No newline at end of file diff --git a/AgileMapper/DataSources/DictionaryNonSimpleMemberDataSource.cs b/AgileMapper/DataSources/DictionaryNonSimpleMemberDataSource.cs new file mode 100644 index 000000000..16693e0d3 --- /dev/null +++ b/AgileMapper/DataSources/DictionaryNonSimpleMemberDataSource.cs @@ -0,0 +1,12 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using Members; + + internal class DictionaryNonSimpleMemberDataSource : DataSourceBase + { + public DictionaryNonSimpleMemberDataSource(IQualifiedMember sourceMember, IMemberMapperData mapperData) + : base(sourceMember, sourceMember.GetQualifiedAccess(mapperData)) + { + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs b/AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs new file mode 100644 index 000000000..77d1ddcca --- /dev/null +++ b/AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs @@ -0,0 +1,46 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using System.Linq.Expressions; + using Members; + using Members.Dictionaries; + + internal class ExistingMemberValueOrDefaultDataSource : DataSourceBase + { + public ExistingMemberValueOrDefaultDataSource(IMemberMapperData mapperData) + : base(mapperData.SourceMember, GetValue(mapperData), mapperData) + { + } + + private static Expression GetValue(IMemberMapperData mapperData) + { + if (mapperData.TargetMember.IsEnumerable) + { + return FallbackToCollection(mapperData) + ? mapperData.GetFallbackCollectionValue() + : mapperData.GetTargetMemberDefault(); + } + + if (mapperData.TargetMember.IsReadable && !mapperData.UseMemberInitialisations()) + { + return mapperData.GetTargetMemberAccess(); + } + + return mapperData.GetTargetMemberDefault(); + } + + private static bool FallbackToCollection(IBasicMapperData mapperData) + { + if (mapperData.TargetMember.IsDictionary) + { + return true; + } + + if (!(mapperData.TargetMember is DictionaryTargetMember dictionaryTargetMember)) + { + return true; + } + + return dictionaryTargetMember.HasEnumerableEntries; + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs b/AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs new file mode 100644 index 000000000..c1335c979 --- /dev/null +++ b/AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs @@ -0,0 +1,10 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using Members; + + internal struct ExistingOrDefaultValueDataSourceFactory : IDataSourceFactory + { + public IDataSource Create(IMemberMapperData mapperData) + => new ExistingMemberValueOrDefaultDataSource(mapperData); + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs b/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs index 54551966e..bbcce88ff 100644 --- a/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/ConfiguredDataSourceFinder.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using Extensions.Internal; - internal class ConfiguredDataSourceFinder : IDataSourceFinder + internal struct ConfiguredDataSourceFinder : IDataSourceFinder { public IEnumerable FindFor(DataSourceFindContext context) { diff --git a/AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs b/AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs new file mode 100644 index 000000000..870d02cd0 --- /dev/null +++ b/AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs @@ -0,0 +1,94 @@ +namespace AgileObjects.AgileMapper.DataSources.Finders +{ + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using Extensions.Internal; + using ObjectPopulation; + + internal struct ConfiguredRootSourceDataSourceFinder : IDataSourceFinder + { + public IEnumerable FindFor(DataSourceFindContext context) + { + if (!context.MapperData.Parent.IsRoot || + !context.MapperData.MapperContext.UserConfigurations.HasDataSourceFactoriesForRootTarget) + { + yield break; + } + + var rootMapperData = context.MapperData.Parent; + + var configuredRootTargetDataSources = context + .MapperData + .MapperContext + .UserConfigurations + .GetDataSources(rootMapperData); + + if (configuredRootTargetDataSources.None()) + { + yield break; + } + + var nullEnumerableIndex = typeof(int?).ToDefaultExpression(); + + foreach (var configuredRootTargetDataSource in configuredRootTargetDataSources) + { + var mappingData = context + .ChildMappingData + .Parent.WithSource(configuredRootTargetDataSource.SourceMember); + + var mappingValues = new MappingValues( + configuredRootTargetDataSource.Value, + context.MapperData.TargetObject, + nullEnumerableIndex); + + var inlineMappingBlock = MappingFactory.GetInlineMappingBlock( + mappingData, + mappingValues, + MappingDataCreationFactory.ForDerivedType(mappingData.MapperData)); + + if (!inlineMappingBlock.TryGetMappingBody(out var mappingBody)) + { + // TODO: Null mappings from a configured root source member + } + + if (mappingBody.NodeType == ExpressionType.Block) + { + IList mappingExpressions = ((BlockExpression)mappingBody).Expressions; + + Expression localVariable; + + if (mappingExpressions.TryGetVariableAssignment(out var localVariableAssignment)) + { + localVariable = localVariableAssignment.Left; + mappingExpressions = mappingExpressions.ToList(); + mappingExpressions.Remove(localVariableAssignment); + } + else + { + localVariable = null; + } + + // TODO: Test coverage for multiple-expression member mappings + mappingBody = mappingExpressions.HasOne() + ? mappingExpressions[0] + : Expression.Block(mappingExpressions); + + if (localVariable != null) + { + mappingBody = mappingBody.Replace( + localVariable, + context.ChildMappingData.MapperData.TargetInstance); + } + } + + // TODO: Test coverage for conditional configured source members + yield return new ConfiguredDataSource( + configuredRootTargetDataSource.SourceMember, + configuredRootTargetDataSource.Condition, + mappingBody, + context.MapperData); + } + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/DataSourceFinder.cs b/AgileMapper/DataSources/Finders/DataSourceFinder.cs index ee5a03a26..cf92b0988 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFinder.cs @@ -8,10 +8,11 @@ internal static class DataSourceFinder { private static readonly IDataSourceFinder[] _finders = { - new ConfiguredDataSourceFinder(), - new MaptimeDataSourceFinder(), - new SourceMemberDataSourceFinder(), - new MetaMemberDataSourceFinder() + default(ConfiguredDataSourceFinder), + default(MaptimeDataSourceFinder), + default(SourceMemberDataSourceFinder), + default(MetaMemberDataSourceFinder), + default(ConfiguredRootSourceDataSourceFinder) }; public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) diff --git a/AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs b/AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs index a396f44cf..fb0a0c944 100644 --- a/AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/MaptimeDataSourceFinder.cs @@ -4,11 +4,11 @@ using Extensions.Internal; - internal class MaptimeDataSourceFinder : IDataSourceFinder + internal struct MaptimeDataSourceFinder : IDataSourceFinder { private static readonly IMaptimeDataSourceFactory[] _mapTimeDataSourceFactories = { - new DictionaryDataSourceFactory() + default(DictionaryDataSourceFactory) }; public IEnumerable FindFor(DataSourceFindContext context) diff --git a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs index 8169bf77e..81a03c00e 100644 --- a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs @@ -12,7 +12,7 @@ using TypeConversion; using static System.StringComparison; - internal class MetaMemberDataSourceFinder : IDataSourceFinder + internal struct MetaMemberDataSourceFinder : IDataSourceFinder { public IEnumerable FindFor(DataSourceFindContext context) { diff --git a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs index 1ddb0808a..a6fda1c74 100644 --- a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs @@ -4,7 +4,7 @@ using Extensions.Internal; using Members; - internal class SourceMemberDataSourceFinder : IDataSourceFinder + internal struct SourceMemberDataSourceFinder : IDataSourceFinder { public IEnumerable FindFor(DataSourceFindContext context) { diff --git a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index 02ebeb1ba..868c1f319 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -67,6 +67,9 @@ public static bool TryFindMatch(this IList items, Func predicate, [DebuggerStepThrough] public static bool None(this ICollection items) => items.Count == 0; + [DebuggerStepThrough] + public static bool None(this IEnumerable items) => !items.GetEnumerator().MoveNext(); + // Used in Dictionary mapping via EnumerableNoneMethod public static bool None(this IEnumerable items, Func predicate) => items.All(item => !predicate.Invoke(item)); diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index 940511554..26874ea9a 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -9,6 +9,7 @@ using NetStandardPolyfills; using ObjectPopulation.Enumerables; using ReadableExpressions.Extensions; + using static System.Linq.Expressions.ExpressionType; internal static partial class ExpressionExtensions { @@ -424,5 +425,86 @@ public static bool IsRootedIn(this Expression expression, Expression possiblePar return false; } + + public static bool TryGetMappingBody(this Expression value, out Expression mapping) + { + if (value.NodeType == Try) + { + value = ((TryExpression)value).Body; + } + + if ((value.NodeType != Block)) + { + mapping = null; + return false; + } + + var mappingBlock = (BlockExpression)value; + var mappingVariables = mappingBlock.Variables.ToList(); + + if (mappingBlock.Expressions[0].NodeType == Try) + { + mappingBlock = (BlockExpression)((TryExpression)mappingBlock.Expressions[0]).Body; + mappingVariables.AddRange(mappingBlock.Variables); + } + else + { + mappingBlock = (BlockExpression)value; + } + + if (mappingBlock.Expressions.HasOne()) + { + mapping = null; + return false; + } + + mapping = mappingBlock; + + var mappingExpressions = GetMappingExpressions(mapping); + + if (mappingExpressions.HasOne() && + (mappingExpressions[0].NodeType == Block)) + { + mappingBlock = (BlockExpression)mappingExpressions[0]; + mappingVariables.AddRange(mappingBlock.Variables); + mapping = mappingBlock.Update(mappingVariables, mappingBlock.Expressions); + return true; + } + + mapping = mappingVariables.Any() + ? Expression.Block(mappingVariables, mappingExpressions) + : mappingExpressions.HasOne() + ? mappingExpressions[0] + : Expression.Block(mappingExpressions); + + return true; + } + + private static IList GetMappingExpressions(Expression mapping) + { + var expressions = new List(); + + while (mapping.NodeType == Block) + { + var mappingBlock = (BlockExpression)mapping; + + expressions.AddRange(mappingBlock.Expressions.Except(new[] { mappingBlock.Result })); + mapping = mappingBlock.Result; + } + + return expressions; + } + + public static bool TryGetVariableAssignment(this IList mappingExpressions, out BinaryExpression binaryExpression) + { + if (mappingExpressions.TryFindMatch(exp => exp.NodeType == Assign, out var assignment)) + { + binaryExpression = (BinaryExpression)assignment; + return true; + } + + binaryExpression = null; + return false; + } } } diff --git a/AgileMapper/MappingExecutor.cs b/AgileMapper/MappingExecutor.cs index a2da62810..034245065 100644 --- a/AgileMapper/MappingExecutor.cs +++ b/AgileMapper/MappingExecutor.cs @@ -8,7 +8,6 @@ using Api; using Api.Configuration; using Extensions.Internal; - using Members; using ObjectPopulation; internal class MappingExecutor : diff --git a/AgileMapper/MappingRuleSetCollection.cs b/AgileMapper/MappingRuleSetCollection.cs index 411e3a2c0..d4f2fb69f 100644 --- a/AgileMapper/MappingRuleSetCollection.cs +++ b/AgileMapper/MappingRuleSetCollection.cs @@ -1,9 +1,9 @@ namespace AgileObjects.AgileMapper { using System.Collections.Generic; + using DataSources; using Extensions.Internal; using Members.Population; - using ObjectPopulation; using ObjectPopulation.Enumerables; using ObjectPopulation.MapperKeys; using ObjectPopulation.Recursion; diff --git a/AgileMapper/Members/ChildMemberMapperData.cs b/AgileMapper/Members/ChildMemberMapperData.cs index b0d2c93f0..192069737 100644 --- a/AgileMapper/Members/ChildMemberMapperData.cs +++ b/AgileMapper/Members/ChildMemberMapperData.cs @@ -6,20 +6,29 @@ internal class ChildMemberMapperData : BasicMapperData, IMemberMapperData { public ChildMemberMapperData(QualifiedMember targetMember, ObjectMapperData parent) + : this( + parent.SourceMember, + targetMember, + parent) + { + } + + public ChildMemberMapperData(IQualifiedMember sourceMember, QualifiedMember targetMember, ObjectMapperData parent) : base( parent.RuleSet, parent.SourceType, parent.TargetType, - parent.SourceMember, + sourceMember, targetMember, parent) { + SourceMember = sourceMember; Parent = parent; Context = new MapperDataContext(this); } public MapperContext MapperContext => Parent.MapperContext; - + public bool IsEntryPoint => Context.IsStandalone || TargetMember.IsRecursion; public ObjectMapperData Parent { get; } @@ -30,7 +39,7 @@ public ChildMemberMapperData(QualifiedMember targetMember, ObjectMapperData pare public ParameterExpression MappingDataObject => Parent.MappingDataObject; - public IQualifiedMember SourceMember => Parent.SourceMember; + public IQualifiedMember SourceMember { get; } public Expression SourceObject => Parent.SourceObject; diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index efb4ceea9..d2a06940b 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -72,6 +72,8 @@ private ConfiguredSourceMember( _childMemberCache = mapperContext.Cache.CreateNew(); } + public bool IsRoot => false; + public Type Type { get; } public Type ElementType { get; } diff --git a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index f5f4e37ac..9f229646d 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -60,6 +60,8 @@ private DictionaryEntrySourceMember( public DictionarySourceMember Parent { get; } + public bool IsRoot => false; + public Type Type { get; } public string GetFriendlyTypeName() => Type.GetFriendlyName(); diff --git a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs index e25439cb9..01359babc 100644 --- a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs @@ -58,6 +58,8 @@ private DictionarySourceMember( HasObjectEntries || (matchedTargetMember.IsEnumerable == ValueType.IsEnumerable()); } + public bool IsRoot => _wrappedSourceMember.IsRoot; + public Type Type { get; } public Type ElementType => ValueType; diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 75db15f3f..5e78ba9d3 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -1,7 +1,6 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; - using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Linq.Expressions; @@ -278,71 +277,7 @@ private bool ValueIsFlattening(Expression value, out Expression flattening) return false; } - if (value.NodeType == Try) - { - value = ((TryExpression)value).Body; - } - - if ((value.NodeType != Block)) - { - flattening = null; - return false; - } - - var flatteningBlock = (BlockExpression)value; - var flatteningVariables = flatteningBlock.Variables.ToList(); - - if (flatteningBlock.Expressions[0].NodeType == Try) - { - flatteningBlock = (BlockExpression)((TryExpression)flatteningBlock.Expressions[0]).Body; - flatteningVariables.AddRange(flatteningBlock.Variables); - } - else - { - flatteningBlock = (BlockExpression)value; - } - - if (flatteningBlock.Expressions.HasOne()) - { - flattening = null; - return false; - } - - flattening = flatteningBlock; - - var flatteningExpressions = GetMappingExpressions(flattening); - - if (flatteningExpressions.HasOne() && - (flatteningExpressions[0].NodeType == Block)) - { - flatteningBlock = (BlockExpression)flatteningExpressions[0]; - flatteningVariables.AddRange(flatteningBlock.Variables); - flattening = flatteningBlock.Update(flatteningVariables, flatteningBlock.Expressions); - return true; - } - - flattening = flatteningVariables.Any() - ? Expression.Block(flatteningVariables, flatteningExpressions) - : flatteningExpressions.HasOne() - ? flatteningExpressions[0] - : Expression.Block(flatteningExpressions); - - return true; - } - - private static IList GetMappingExpressions(Expression mapping) - { - var expressions = new List(); - - while (mapping.NodeType == Block) - { - var mappingBlock = (BlockExpression)mapping; - - expressions.AddRange(mappingBlock.Expressions.Except(new[] { mappingBlock.Result })); - mapping = mappingBlock.Result; - } - - return expressions; + return value.TryGetMappingBody(out flattening); } private Expression GetCheckedValueOrNull(Expression value, Expression keyedAccess, IMemberMapperData mapperData) diff --git a/AgileMapper/Members/IChildMemberMappingData.cs b/AgileMapper/Members/IChildMemberMappingData.cs index 9c4d0b113..0844d96cf 100644 --- a/AgileMapper/Members/IChildMemberMappingData.cs +++ b/AgileMapper/Members/IChildMemberMappingData.cs @@ -1,6 +1,5 @@ namespace AgileObjects.AgileMapper.Members { - using System; using ObjectPopulation; internal interface IChildMemberMappingData @@ -10,7 +9,5 @@ internal interface IChildMemberMappingData IObjectMappingData Parent { get; } IMemberMapperData MapperData { get; } - - Type GetSourceMemberRuntimeType(IQualifiedMember sourceMember); } } \ No newline at end of file diff --git a/AgileMapper/Members/IQualifiedMember.cs b/AgileMapper/Members/IQualifiedMember.cs index 8b4757e9a..ce997244d 100644 --- a/AgileMapper/Members/IQualifiedMember.cs +++ b/AgileMapper/Members/IQualifiedMember.cs @@ -5,6 +5,8 @@ namespace AgileObjects.AgileMapper.Members internal interface IQualifiedMember { + bool IsRoot { get; } + Type Type { get; } Type ElementType { get; } diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index daa73a579..a71ee4f92 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -57,6 +57,13 @@ public static bool IsEntityId(this Member member) public static bool IsUnmappable(this QualifiedMember member, out string reason) { + if (member.MemberChain.Length < 2) + { + // Either the root member, QualifiedMember.All or QualifiedMember.None: + reason = null; + return false; + } + if (IsStructNonSimpleMember(member)) { reason = member.Type.GetFriendlyName() + " member on a struct"; @@ -246,10 +253,15 @@ public static QualifiedMember ToSourceMember(this Expression memberAccess, Mappe mapperContext); } - public static QualifiedMember ToTargetMember(this Expression memberAccess, MapperContext mapperContext) + public static QualifiedMember ToTargetMember(this LambdaExpression memberAccess, MapperContext mapperContext) { + if (memberAccess.Body == memberAccess.Parameters.FirstOrDefault()) + { + return QualifiedMember.From(Member.RootTarget(memberAccess.Type), mapperContext); + } + return CreateMember( - memberAccess, + memberAccess.Body, Member.RootTarget, GlobalContext.Instance.MemberCache.GetTargetMembers, mapperContext); diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 19ee0a1ac..b31fe93f5 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -204,7 +204,7 @@ public static bool TargetMemberIsUnmappable( if ((typePair.TargetType != typePair.SourceType) && targetMember.LeafMember.IsEntityId() && - configuredDataSourcesFactory.Invoke(typePair).ToArray().None()) + configuredDataSourcesFactory.Invoke(typePair).None()) { reason = "Entity key member"; return true; diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index 67df0c556..b601f5008 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -140,7 +140,7 @@ public static QualifiedMember From(Member[] memberChain, MapperContext mapperCon #endregion - public virtual bool IsRoot => LeafMember.IsRoot; + public virtual bool IsRoot => LeafMember?.IsRoot == true; public Member[] MemberChain { get; } diff --git a/AgileMapper/Members/SourceMemberMatcher.cs b/AgileMapper/Members/SourceMemberMatcher.cs index f82294b6c..b5ac9d413 100644 --- a/AgileMapper/Members/SourceMemberMatcher.cs +++ b/AgileMapper/Members/SourceMemberMatcher.cs @@ -148,7 +148,7 @@ private static IEnumerable EnumerateSourceMembers( yield break; } - var parentMemberType = rootData.GetSourceMemberRuntimeType(parentMember); + var parentMemberType = rootData.Parent.GetSourceMemberRuntimeType(parentMember); if (parentMemberType != parentMember.Type) { diff --git a/AgileMapper/Members/Sources/FixedMembersMembersSource.cs b/AgileMapper/Members/Sources/FixedMembersMembersSource.cs index 787aee7ff..3e6cae79f 100644 --- a/AgileMapper/Members/Sources/FixedMembersMembersSource.cs +++ b/AgileMapper/Members/Sources/FixedMembersMembersSource.cs @@ -8,7 +8,7 @@ internal class FixedMembersMembersSource : IChildMembersSource public FixedMembersMembersSource( IQualifiedMember sourceMember, QualifiedMember targetMember, - int dataSourceIndex) + int dataSourceIndex = 0) { _sourceMember = sourceMember; _targetMember = targetMember; diff --git a/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs b/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs index f8e295ff5..618bac1b9 100644 --- a/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs +++ b/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs @@ -1,16 +1,10 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { - using System; - using System.Linq.Expressions; - using Caching; - using Extensions.Internal; using Members; - using NetStandardPolyfills; internal class ChildMemberMappingData : IChildMemberMappingData { private readonly ObjectMappingData _parent; - private ICache> _runtimeTypeGettersCache; public ChildMemberMappingData(ObjectMappingData parent, IMemberMapperData mapperData) { @@ -23,54 +17,5 @@ public ChildMemberMappingData(ObjectMappingData parent, IMembe public IObjectMappingData Parent => _parent; public IMemberMapperData MapperData { get; } - - public Type GetSourceMemberRuntimeType(IQualifiedMember sourceMember) - { - if (_parent.Source == null) - { - return sourceMember.Type; - } - - if (sourceMember.Type.IsSealed()) - { - return sourceMember.Type; - } - - var mapperData = MapperData; - - while (mapperData != null) - { - if (sourceMember == mapperData.SourceMember) - { - return mapperData.SourceMember.Type; - } - - mapperData = mapperData.Parent; - } - - if (_runtimeTypeGettersCache == null) - { - _runtimeTypeGettersCache = _parent.MapperContext.Cache.CreateScoped>(); - } - - var getRuntimeTypeFunc = _runtimeTypeGettersCache.GetOrAdd(sourceMember, sm => - { - var sourceParameter = Parameters.Create("source"); - var relativeMember = sm.RelativeTo(MapperData.SourceMember); - var memberAccess = relativeMember.GetQualifiedAccess(MapperData); - memberAccess = memberAccess.Replace(MapperData.SourceObject, sourceParameter); - - var getRuntimeTypeCall = Expression.Call( - ObjectExtensions.GetRuntimeSourceTypeMethod.MakeGenericMethod(sm.Type), - memberAccess); - - var getRuntimeTypeLambda = Expression - .Lambda>(getRuntimeTypeCall, sourceParameter); - - return getRuntimeTypeLambda.Compile(); - }); - - return getRuntimeTypeFunc.Invoke(_parent.Source); - } } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs b/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs deleted file mode 100644 index c3936609e..000000000 --- a/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation -{ - using DataSources; - using Members; - - internal struct DefaultValueDataSourceFactory : IDataSourceFactory - { - public IDataSource Create(IMemberMapperData mapperData) - => new DefaultValueDataSource(mapperData); - - private class DefaultValueDataSource : DataSourceBase - { - public DefaultValueDataSource(IMemberMapperData mapperData) - : base(mapperData.SourceMember, mapperData.GetTargetMemberDefault()) - { - } - } - } -} \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 7ebdc2e54..35c9a262c 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -324,7 +324,7 @@ private static bool UseDictionaryCloneConstructor( ((cloneConstructor = GetDictionaryCloneConstructor(mapperData)) != null); } - private static ConstructorInfo GetDictionaryCloneConstructor(IBasicMapperData mapperData) + private static ConstructorInfo GetDictionaryCloneConstructor(ITypePair mapperData) { var dictionaryTypes = mapperData.TargetType.GetDictionaryTypes(); var dictionaryInterfaceType = typeof(IDictionary<,>).MakeGenericType(dictionaryTypes.Key, dictionaryTypes.Value); diff --git a/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs b/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs deleted file mode 100644 index 2f95d3dec..000000000 --- a/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation -{ - using System.Linq.Expressions; - using DataSources; - using Members; - using Members.Dictionaries; - - internal struct ExistingOrDefaultValueDataSourceFactory : IDataSourceFactory - { - public IDataSource Create(IMemberMapperData mapperData) - => new ExistingMemberValueOrDefaultDataSource(mapperData); - - private class ExistingMemberValueOrDefaultDataSource : DataSourceBase - { - public ExistingMemberValueOrDefaultDataSource(IMemberMapperData mapperData) - : base(mapperData.SourceMember, GetValue(mapperData), mapperData) - { - } - - private static Expression GetValue(IMemberMapperData mapperData) - { - if (mapperData.TargetMember.IsEnumerable) - { - return FallbackToCollection(mapperData) - ? mapperData.GetFallbackCollectionValue() - : mapperData.GetTargetMemberDefault(); - } - - if (mapperData.TargetMember.IsReadable && !mapperData.UseMemberInitialisations()) - { - return mapperData.GetTargetMemberAccess(); - } - - return mapperData.GetTargetMemberDefault(); - } - - private static bool FallbackToCollection(IBasicMapperData mapperData) - { - if (mapperData.TargetMember.IsDictionary) - { - return true; - } - - if (!(mapperData.TargetMember is DictionaryTargetMember dictionaryTargetMember)) - { - return true; - } - - return dictionaryTargetMember.HasEnumerableEntries; - } - } - } -} \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/IObjectMappingData.cs b/AgileMapper/ObjectPopulation/IObjectMappingData.cs index cc1e1a770..6c2f73bd7 100644 --- a/AgileMapper/ObjectPopulation/IObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/IObjectMappingData.cs @@ -26,8 +26,12 @@ internal interface IObjectMappingData : IObjectMappingDataUntyped IChildMemberMappingData GetChildMappingData(IMemberMapperData childMapperData); + Type GetSourceMemberRuntimeType(IQualifiedMember childSourceMember); + object MapStart(); + IObjectMappingData WithSource(IQualifiedMember newSourceMember); + IObjectMappingData WithTypes(Type newSourceType, Type newTargetType); } diff --git a/AgileMapper/ObjectPopulation/MapperKeys/RootObjectMapperKey.cs b/AgileMapper/ObjectPopulation/MapperKeys/RootObjectMapperKey.cs index 809a109c0..7d6086033 100644 --- a/AgileMapper/ObjectPopulation/MapperKeys/RootObjectMapperKey.cs +++ b/AgileMapper/ObjectPopulation/MapperKeys/RootObjectMapperKey.cs @@ -1,32 +1,33 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.MapperKeys { using Members.Sources; +#if DEBUG using ReadableExpressions.Extensions; +#endif internal class RootObjectMapperKey : ObjectMapperKeyBase, IRootMapperKey { - private readonly MapperContext _mapperContext; + private readonly IMembersSource _membersSource; public RootObjectMapperKey(MappingTypes mappingTypes, IMappingContext mappingContext) - : this(mappingContext.RuleSet, mappingTypes, mappingContext.MapperContext) + : this(mappingContext.RuleSet, mappingTypes, mappingContext.MapperContext.RootMembersSource) { } - public RootObjectMapperKey(MappingRuleSet ruleSet, MappingTypes mappingTypes, MapperContext mapperContext) + public RootObjectMapperKey(MappingRuleSet ruleSet, MappingTypes mappingTypes, IMembersSource membersSource) : base(mappingTypes) { - _mapperContext = mapperContext; + _membersSource = membersSource; RuleSet = ruleSet; } public MappingRuleSet RuleSet { get; } - public override IMembersSource GetMembersSource(ObjectMapperData parentMapperData) - => _mapperContext.RootMembersSource; + public override IMembersSource GetMembersSource(ObjectMapperData parentMapperData) => _membersSource; protected override ObjectMapperKeyBase CreateInstance(MappingTypes newMappingTypes) - => new RootObjectMapperKey(RuleSet, newMappingTypes, _mapperContext); - + => new RootObjectMapperKey(RuleSet, newMappingTypes, _membersSource); + public bool Equals(IRootMapperKey otherKey) => base.Equals(otherKey); #region ToString diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 10167d9a8..642221bd5 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -275,7 +275,7 @@ private static bool TryAdjustForUnusedLocalVariableIfApplicable( return false; } - if (!TryGetVariableAssignment(mappingExpressions, out var localVariableAssignment)) + if (!mappingExpressions.TryGetVariableAssignment(out var localVariableAssignment)) { returnExpression = null; return false; @@ -306,18 +306,6 @@ private static bool TryAdjustForUnusedLocalVariableIfApplicable( return true; } - private static bool TryGetVariableAssignment(IList mappingExpressions, out BinaryExpression binaryExpression) - { - if (mappingExpressions.TryFindMatch(exp => exp.NodeType == Assign, out var assignment)) - { - binaryExpression = (BinaryExpression)assignment; - return true; - } - - binaryExpression = null; - return false; - } - private static Expression GetReturnExpression(Expression returnValue, MappingExtras mappingExtras) { return (mappingExtras.MapToNullCondition != null) diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index 73b89ea4f..b74341a8e 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -3,9 +3,11 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System; using System.Collections.Generic; using System.Linq.Expressions; + using Caching; using Extensions.Internal; using MapperKeys; using Members; + using NetStandardPolyfills; using Validation; internal class ObjectMappingData : @@ -14,6 +16,7 @@ internal class ObjectMappingData : IObjectMappingData, IObjectCreationMappingData { + private ICache> _runtimeTypeGettersCache; private Dictionary> _mappedObjectsBySource; private ObjectMapper _mapper; private ObjectMapperData _mapperData; @@ -135,6 +138,55 @@ private Dictionary> MappedObjectsBySource IChildMemberMappingData IObjectMappingData.GetChildMappingData(IMemberMapperData childMapperData) => new ChildMemberMappingData(this, childMapperData); + public Type GetSourceMemberRuntimeType(IQualifiedMember childSourceMember) + { + if (Source == null) + { + return childSourceMember.Type; + } + + if (childSourceMember.Type.IsSealed()) + { + return childSourceMember.Type; + } + + var mapperData = MapperData; + + while (mapperData != null) + { + if (childSourceMember == mapperData.SourceMember) + { + return mapperData.SourceMember.Type; + } + + mapperData = mapperData.Parent; + } + + if (_runtimeTypeGettersCache == null) + { + _runtimeTypeGettersCache = MapperContext.Cache.CreateScoped>(); + } + + var getRuntimeTypeFunc = _runtimeTypeGettersCache.GetOrAdd(childSourceMember, sm => + { + var sourceParameter = Parameters.Create("source"); + var relativeMember = sm.RelativeTo(MapperData.SourceMember); + var memberAccess = relativeMember.GetQualifiedAccess(MapperData); + memberAccess = memberAccess.Replace(MapperData.SourceObject, sourceParameter); + + var getRuntimeTypeCall = Expression.Call( + ObjectExtensions.GetRuntimeSourceTypeMethod.MakeGenericMethod(sm.Type), + memberAccess); + + var getRuntimeTypeLambda = Expression + .Lambda>(getRuntimeTypeCall, sourceParameter); + + return getRuntimeTypeLambda.Compile(); + }); + + return getRuntimeTypeFunc.Invoke(Source); + } + #endregion #region Map Methods @@ -265,6 +317,13 @@ public IObjectMappingData WithTargetType() return As(Source, Target as TNewTarget); } + public IObjectMappingData WithSource(IQualifiedMember newSourceMember) + { + var sourceMemberRuntimeType = GetSourceMemberRuntimeType(newSourceMember); + + return WithTypes(sourceMemberRuntimeType, MapperData.TargetType); + } + public IObjectMappingData WithTypes(Type newSourceType, Type newTargetType) { var typesKey = new SourceAndTargetTypesKey(newSourceType, newTargetType); diff --git a/AgileMapper/TypeConversion/ConverterSet.cs b/AgileMapper/TypeConversion/ConverterSet.cs index 1ce1a56a0..fbbe95c99 100644 --- a/AgileMapper/TypeConversion/ConverterSet.cs +++ b/AgileMapper/TypeConversion/ConverterSet.cs @@ -17,26 +17,23 @@ public ConverterSet(UserConfigurationSet userConfigurations) { _converters = new List { - ToStringConverter.Instance, + default(ToStringConverter), ToNumericConverter.Instance, - ToBoolConverter.Instance, + default(ToBoolConverter), new ToEnumConverter(userConfigurations), TryParseConverter.Instance, TryParseConverter.Instance, ToNumericConverter.Instance, ToNumericConverter.Instance, ToNumericConverter.Instance, - ToCharacterConverter.Instance, + default(ToCharacterConverter), ToNumericConverter.Instance, ToNumericConverter.Instance, - FallbackNonSimpleTypeValueConverter.Instance + default(FallbackNonSimpleTypeValueConverter) }; } - public void Add(IValueConverter converter) - { - _converters.Insert(0, converter); - } + public void Add(IValueConverter converter) => _converters.Insert(0, converter); public void ThrowIfUnconvertible(Type sourceType, Type targetType) { diff --git a/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs b/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs index d9c2e61a9..3c6394cb8 100644 --- a/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs +++ b/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs @@ -4,10 +4,8 @@ namespace AgileObjects.AgileMapper.TypeConversion using System.Linq.Expressions; using Extensions.Internal; - internal class FallbackNonSimpleTypeValueConverter : IValueConverter + internal struct FallbackNonSimpleTypeValueConverter : IValueConverter { - public static readonly IValueConverter Instance = new FallbackNonSimpleTypeValueConverter(); - public bool CanConvert(Type nonNullableSourceType, Type nonNullableTargetType) { if (nonNullableTargetType.IsSimple()) diff --git a/AgileMapper/TypeConversion/ToBoolConverter.cs b/AgileMapper/TypeConversion/ToBoolConverter.cs index 6309965ac..cd635cd77 100644 --- a/AgileMapper/TypeConversion/ToBoolConverter.cs +++ b/AgileMapper/TypeConversion/ToBoolConverter.cs @@ -8,18 +8,13 @@ namespace AgileObjects.AgileMapper.TypeConversion using Extensions.Internal; using ReadableExpressions.Extensions; - internal class ToBoolConverter : IValueConverter + internal struct ToBoolConverter : IValueConverter { - public static readonly ToBoolConverter Instance = new ToBoolConverter(); - - private static readonly Type[] _supportedSourceTypes = Constants - .NumericTypes - .Append(typeof(bool)); - public bool CanConvert(Type nonNullableSourceType, Type nonNullableTargetType) { return (nonNullableTargetType == typeof(bool)) && - ((_supportedSourceTypes.Contains(nonNullableSourceType)) || + ((nonNullableSourceType == typeof(bool)) || + Constants.NumericTypes.Contains(nonNullableSourceType) || ToStringConverter.HasNativeStringRepresentation(nonNullableSourceType)); } diff --git a/AgileMapper/TypeConversion/ToCharacterConverter.cs b/AgileMapper/TypeConversion/ToCharacterConverter.cs index c75cd34de..915398cab 100644 --- a/AgileMapper/TypeConversion/ToCharacterConverter.cs +++ b/AgileMapper/TypeConversion/ToCharacterConverter.cs @@ -6,18 +6,16 @@ using Extensions.Internal; using NetStandardPolyfills; - internal class ToCharacterConverter : IValueConverter + internal struct ToCharacterConverter : IValueConverter { - public static readonly ToCharacterConverter Instance = new ToCharacterConverter(); - - private static readonly Type[] _handledSourceTypes = Constants - .NumericTypes - .Append(typeof(char), typeof(string), typeof(object)); - public bool CanConvert(Type nonNullableSourceType, Type nonNullableTargetType) { return (nonNullableTargetType == typeof(char)) && - (nonNullableSourceType.IsEnum() || _handledSourceTypes.Contains(nonNullableSourceType)); + (nonNullableSourceType.IsEnum() || + (nonNullableSourceType == typeof(char)) || + (nonNullableSourceType == typeof(string)) || + (nonNullableSourceType == typeof(object)) || + Constants.NumericTypes.Contains(nonNullableSourceType)); } public Expression GetConversion(Expression sourceValue, Type targetType) diff --git a/AgileMapper/TypeConversion/ToStringConverter.cs b/AgileMapper/TypeConversion/ToStringConverter.cs index 38a3b40e0..a840abd6e 100644 --- a/AgileMapper/TypeConversion/ToStringConverter.cs +++ b/AgileMapper/TypeConversion/ToStringConverter.cs @@ -8,10 +8,8 @@ using Extensions.Internal; using NetStandardPolyfills; - internal class ToStringConverter : IValueConverter + internal struct ToStringConverter : IValueConverter { - public static readonly IValueConverter Instance = new ToStringConverter(); - public bool CanConvert(Type nonNullableSourceType, Type nonNullableTargetType) => nonNullableTargetType == typeof(string); @@ -68,12 +66,11 @@ public static Expression GetConversion(Expression sourceValue) #region Byte[] Conversion - private static readonly MethodInfo _toBase64String = typeof(Convert) - .GetPublicStaticMethod("ToBase64String", parameterCount: 1); - private static Expression GetByteArrayToBase64StringConversion(Expression sourceValue) { - return Expression.Call(_toBase64String, sourceValue); + return Expression.Call( + typeof(Convert).GetPublicStaticMethod(nameof(Convert.ToBase64String), parameterCount: 1), + sourceValue); } #endregion From 9b8777c1952b2586c6830d931bd2c1ef54aa43de Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 28 Jun 2018 21:08:33 +0100 Subject: [PATCH 02/16] Adding ToTarget() configuration API method --- .../Configuration/WhenConfiguringDataSources.cs | 2 +- .../CustomDataSourceTargetMemberSpecifier.cs | 13 +++++++++---- ...ICustomMappingDataSourceTargetMemberSpecifier.cs | 9 +++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index d56882512..d0128e82b 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1006,7 +1006,7 @@ public void ShouldApplyAConfiguredComplexTypeMemberToTheRootTarget() .From(source) .To>() .Map((s, ptf) => s.Value) - .To(t => t); + .ToTarget(); var result = source .MapUsing(mapper) diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 20418ac6d..7293ab838 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -66,10 +66,7 @@ private ConfiguredDataSourceFactory CreateFromLambda(LambdaExpress return new ConfiguredDictionaryDataSourceFactory(_configInfo, valueLambda, dictionaryEntryMember); } - return new ConfiguredDataSourceFactory( - _configInfo, - valueLambda, - targetMemberLambda); + return new ConfiguredDataSourceFactory(_configInfo, valueLambda, targetMemberLambda); } private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out DictionaryTargetMember entryMember) @@ -236,6 +233,14 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par #endregion + public IMappingConfigContinuation ToTarget() + { + return RegisterDataSource(() => new ConfiguredDataSourceFactory( + _configInfo, + GetValueLambda(), + QualifiedMember.From(Member.RootTarget(), _configInfo.MapperContext))); + } + private MappingConfigContinuation RegisterDataSource( Func factoryFactory) { diff --git a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs index 1dd84ef36..9d40e7352 100644 --- a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs @@ -53,5 +53,14 @@ IMappingConfigContinuation To( /// target type being configured. /// IMappingConfigContinuation ToCtor(string parameterName); + + /// + /// Apply the configured source value to the root target object. + /// + /// + /// An IMappingConfigContinuation to enable further configuration of mappings from and to the source + /// and target type being configured. + /// + IMappingConfigContinuation ToTarget(); } } \ No newline at end of file From 1c22c58b9689f51cd3a8f1206a06315a11ace9f7 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 28 Jun 2018 21:31:09 +0100 Subject: [PATCH 03/16] Catching invalid .ToTarget() API configurations --- .../WhenConfiguringDataSourcesIncorrectly.cs | 88 ++++++++++++++----- .../CustomDataSourceTargetMemberSpecifier.cs | 39 ++++++-- AgileMapper/Members/MemberExtensions.cs | 5 -- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs index 4e43840d0..5414d367d 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs @@ -173,8 +173,7 @@ public void ShouldErrorIfReadOnlyMemberSpecified() { using (var mapper = Mapper.CreateNew()) { - mapper - .WhenMapping + mapper.WhenMapping .From>() .To>() .Map(ctx => ctx.Source.Value) @@ -186,65 +185,110 @@ public void ShouldErrorIfReadOnlyMemberSpecified() [Fact] public void ShouldErrorIfMissingConstructorParameterTypeSpecified() { - using (var mapper = Mapper.CreateNew()) + var configurationException = Should.Throw(() => { - var configurationException = Should.Throw(() => + using (var mapper = Mapper.CreateNew()) + { mapper.WhenMapping .From>() .To>() .Map(Guid.NewGuid()) - .ToCtor()); + .ToCtor(); + } + }); - configurationException.Message.ShouldContain("No constructor parameter of type"); - } + configurationException.Message.ShouldContain("No constructor parameter of type"); } [Fact] public void ShouldErrorIfMissingConstructorParameterNameSpecified() { - using (var mapper = Mapper.CreateNew()) + var configurationException = Should.Throw(() => { - var configurationException = Should.Throw(() => + using (var mapper = Mapper.CreateNew()) + { mapper.WhenMapping .From>() .To>() .Map(Guid.NewGuid()) - .ToCtor("boing")); + .ToCtor("boing"); + } + }); - configurationException.Message.ShouldContain("No constructor parameter named"); - } + configurationException.Message.ShouldContain("No constructor parameter named"); } [Fact] public void ShouldErrorIfNonUniqueConstructorParameterTypeSpecified() { - using (var mapper = Mapper.CreateNew()) + var configurationException = Should.Throw(() => { - var configurationException = Should.Throw(() => + using (var mapper = Mapper.CreateNew()) + { mapper.WhenMapping .From>() .To>() .Map(DateTime.Today) - .ToCtor()); + .ToCtor(); + } + }); - configurationException.Message.ShouldContain("Multiple constructor parameters"); - } + configurationException.Message.ShouldContain("Multiple constructor parameters"); } [Fact] public void ShouldErrorIfUnconvertibleConstructorValueConstantSpecified() { - using (var mapper = Mapper.CreateNew()) + var configurationException = Should.Throw(() => { - var configurationException = Should.Throw(() => + using (var mapper = Mapper.CreateNew()) + { mapper.WhenMapping .From>() .To>() .Map(DateTime.Today) - .ToCtor()); + .ToCtor(); + } + }); - configurationException.Message.ShouldContain("Unable to convert"); - } + configurationException.Message.ShouldContain("Unable to convert"); + } + + [Fact] + public void ShouldErrorIfTargetParameterConfiguredAsTarget() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .To
() + .Map((s, t) => s.Address) + .To(t => t); + } + }); + + configurationException.Message.ShouldContain("not a valid configured target; use .ToTarget()"); + } + + [Fact] + public void ShouldErrorIfSimpleTypeConfiguredAsRootTargetDataSource() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map("No no no no no") + .ToTarget(); + } + }); + + configurationException.Message.ShouldContain( + "'string' cannot be mapped to root target type 'PublicField'"); } } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 7293ab838..634ae801b 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -40,10 +40,11 @@ public CustomDataSourceTargetMemberSpecifier( _customValueLambdaInfo = customValueLambda; } - public IMappingConfigContinuation To( Expression> targetMember) { + ThrowIfTargetParameterSpecified(targetMember); + return RegisterDataSource(() => CreateFromLambda(targetMember)); } @@ -57,16 +58,26 @@ public IMappingConfigContinuation To( Expression>> targetSetMethod) => RegisterDataSource(() => CreateFromLambda(targetSetMethod)); + private static void ThrowIfTargetParameterSpecified(LambdaExpression targetParameter) + { + if (targetParameter.Body == targetParameter.Parameters.FirstOrDefault()) + { + throw new MappingConfigurationException( + "The target parameter is not a valid configured target; use .ToTarget() " + + "to map a custom data source to the root target object"); + } + } + private ConfiguredDataSourceFactory CreateFromLambda(LambdaExpression targetMemberLambda) { - var valueLambda = GetValueLambda(); + var valueLambdaInfo = GetValueLambdaInfo(); if (IsDictionaryEntry(targetMemberLambda, out var dictionaryEntryMember)) { - return new ConfiguredDictionaryDataSourceFactory(_configInfo, valueLambda, dictionaryEntryMember); + return new ConfiguredDictionaryDataSourceFactory(_configInfo, valueLambdaInfo, dictionaryEntryMember); } - return new ConfiguredDataSourceFactory(_configInfo, valueLambda, targetMemberLambda); + return new ConfiguredDataSourceFactory(_configInfo, valueLambdaInfo, targetMemberLambda); } private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out DictionaryTargetMember entryMember) @@ -108,7 +119,7 @@ private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out Dictiona return true; } - private ConfiguredLambdaInfo GetValueLambda() + private ConfiguredLambdaInfo GetValueLambdaInfo() { if (_customValueLambdaInfo != null) { @@ -225,7 +236,7 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par Member.ConstructorParameter(parameter) }; - var valueLambda = GetValueLambda(); + var valueLambda = GetValueLambdaInfo(); var constructorParameter = QualifiedMember.From(memberChain, _configInfo.MapperContext); return new ConfiguredDataSourceFactory(_configInfo, valueLambda, constructorParameter); @@ -235,12 +246,26 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par public IMappingConfigContinuation ToTarget() { + ThrowIfSimpleSourceTypeConfigured(); + return RegisterDataSource(() => new ConfiguredDataSourceFactory( _configInfo, - GetValueLambda(), + GetValueLambdaInfo(), QualifiedMember.From(Member.RootTarget(), _configInfo.MapperContext))); } + private void ThrowIfSimpleSourceTypeConfigured() + { + if (_customValueLambda.Body.Type.IsSimple()) + { + throw new MappingConfigurationException(string.Format( + CultureInfo.InvariantCulture, + "Source type '{0}' cannot be mapped to root target type '{1}'", + _customValueLambda.Body.Type.GetFriendlyName(), + typeof(TTarget).GetFriendlyName())); + } + } + private MappingConfigContinuation RegisterDataSource( Func factoryFactory) { diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index a71ee4f92..d89668ade 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -255,11 +255,6 @@ public static QualifiedMember ToSourceMember(this Expression memberAccess, Mappe public static QualifiedMember ToTargetMember(this LambdaExpression memberAccess, MapperContext mapperContext) { - if (memberAccess.Body == memberAccess.Parameters.FirstOrDefault()) - { - return QualifiedMember.From(Member.RootTarget(memberAccess.Type), mapperContext); - } - return CreateMember( memberAccess.Body, Member.RootTarget, From 1ddf29d701acd7610da2b9451ee3961f6401921a Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 29 Jun 2018 17:00:27 +0100 Subject: [PATCH 04/16] Start of handling runtime-type-checking of configured root target source members --- .../WhenConfiguringDataSources.cs | 36 +++++++++++++++++++ .../DataSources/ConfiguredDataSource.cs | 4 +-- AgileMapper/Members/ConfiguredSourceMember.cs | 14 ++++++-- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index d0128e82b..1873658c0 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1017,6 +1017,42 @@ public void ShouldApplyAConfiguredComplexTypeMemberToTheRootTarget() } } + [Fact] + public void ShouldApplyAComplexTypeToRootTargetOverwriteConfiguration() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>>>() + .Over>() + .Map((s, t) => s.Value2) + .ToTarget(); + + var source = new PublicTwoFields>> + { + Value1 = 6372, + Value2 = new PublicField> + { + Value = new PublicTwoFields + { + Value2 = 8262 + } + } + }; + + var target = new PublicTwoFields + { + Value1 = 637, + Value2 = 728 + }; + + mapper.Map(source).Over(target); + + target.Value1.ShouldBe(6372); + target.Value2.ShouldBe(8262); + } + } + // ReSharper disable once ClassNeverInstantiated.Local // ReSharper disable UnusedAutoPropertyAccessor.Local private class IdTester diff --git a/AgileMapper/DataSources/ConfiguredDataSource.cs b/AgileMapper/DataSources/ConfiguredDataSource.cs index 3ab2d0167..18c8b2f00 100644 --- a/AgileMapper/DataSources/ConfiguredDataSource.cs +++ b/AgileMapper/DataSources/ConfiguredDataSource.cs @@ -13,7 +13,7 @@ public ConfiguredDataSource( Expression value, IMemberMapperData mapperData) : this( - GetSourceMember(value, mapperData), + CreateSourceMember(value, mapperData), configuredCondition, GetConvertedValue(value, mapperData), mapperData) @@ -22,7 +22,7 @@ public ConfiguredDataSource( #region Setup - private static IQualifiedMember GetSourceMember(Expression value, IMemberMapperData mapperData) + private static IQualifiedMember CreateSourceMember(Expression value, IMemberMapperData mapperData) { var sourceMember = new ConfiguredSourceMember(value, mapperData); diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index d2a06940b..c92d6a695 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -95,8 +95,18 @@ public IQualifiedMember Append(Member childMember) public IQualifiedMember RelativeTo(IQualifiedMember otherMember) { - var otherConfiguredMember = (ConfiguredSourceMember)otherMember; - var relativeMemberChain = _childMembers.RelativeTo(otherConfiguredMember._childMembers); + Member[] otherMemberChain; + + if (otherMember is ConfiguredSourceMember otherConfiguredMember) + { + otherMemberChain = otherConfiguredMember._childMembers; + } + else + { + + } + + var relativeMemberChain = _childMembers.RelativeTo(otherMemberChain); return new ConfiguredSourceMember( Type, From d041a5d81c397326bb77d8b47cf809e13613d066 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 29 Jun 2018 22:07:19 +0100 Subject: [PATCH 05/16] Support for overwrite mapping from configured root target sources --- .../WhenConfiguringDataSources.cs | 6 +- .../Members/MemberTestsBase.cs | 6 +- AgileMapper/DataSources/AdHocDataSource.cs | 6 +- .../ConfiguredRootSourceDataSourceFinder.cs | 94 ------------ .../DataSources/Finders/DataSourceFinder.cs | 3 +- AgileMapper/Members/ConfiguredSourceMember.cs | 30 ++-- AgileMapper/Members/IMappingData.cs | 3 +- .../Members/MemberMapperDataExtensions.cs | 25 +++- .../ComplexTypeMappingExpressionFactory.cs | 6 +- .../PopulationExpressionFactoryBase.cs | 28 ++-- .../DictionaryMappingExpressionFactory.cs | 10 +- .../EnumerableMappingExpressionFactory.cs | 8 +- .../ObjectPopulation/IObjectMappingData.cs | 2 +- .../IObjectMappingDataUntyped.cs | 6 +- .../ObjectPopulation/MapperDataContext.cs | 2 +- .../MappingDataCreationFactory.cs | 3 +- .../MappingExpressionFactoryBase.cs | 141 ++++++++++++++---- .../ObjectPopulation/ObjectMapperData.cs | 7 +- .../ObjectPopulation/ObjectMappingData.cs | 49 +++--- .../SimpleTypeMappingExpressionFactory.cs | 4 +- .../QueryProjectionExpressionFactory.cs | 8 +- 21 files changed, 237 insertions(+), 210 deletions(-) delete mode 100644 AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 1873658c0..33ba74e80 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1018,14 +1018,14 @@ public void ShouldApplyAConfiguredComplexTypeMemberToTheRootTarget() } [Fact] - public void ShouldApplyAComplexTypeToRootTargetOverwriteConfiguration() + public void ShouldApplyANestedComplexTypeToRootTargetOverwriteConfiguration() { using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping .From>>>() .Over>() - .Map((s, t) => s.Value2) + .Map((s, t) => s.Value2.Value) .ToTarget(); var source = new PublicTwoFields>> @@ -1048,7 +1048,7 @@ public void ShouldApplyAComplexTypeToRootTargetOverwriteConfiguration() mapper.Map(source).Over(target); - target.Value1.ShouldBe(6372); + target.Value1.ShouldBeDefault(); // <- Because Value2.Value.Value1 will overwrite 6372 target.Value2.ShouldBe(8262); } } diff --git a/AgileMapper.UnitTests/Members/MemberTestsBase.cs b/AgileMapper.UnitTests/Members/MemberTestsBase.cs index 6daa0a7c5..9401f9be0 100644 --- a/AgileMapper.UnitTests/Members/MemberTestsBase.cs +++ b/AgileMapper.UnitTests/Members/MemberTestsBase.cs @@ -33,11 +33,7 @@ private static IQualifiedMember SourceMemberFor(Member rootSourceMember, Express { return (childMemberExpression == null) ? QualifiedMember.From(rootSourceMember, DefaultMapperContext) - : MemberExtensions.CreateMember( - childMemberExpression, - Member.RootSource, - MemberCache.GetSourceMembers, - DefaultMapperContext); + : childMemberExpression.ToSourceMember(DefaultMapperContext); } internal QualifiedMember TargetMemberFor(Expression> childMemberExpression = null) diff --git a/AgileMapper/DataSources/AdHocDataSource.cs b/AgileMapper/DataSources/AdHocDataSource.cs index 4b168dd08..5340f5797 100644 --- a/AgileMapper/DataSources/AdHocDataSource.cs +++ b/AgileMapper/DataSources/AdHocDataSource.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.DataSources { + using System.Collections.Generic; using System.Linq.Expressions; using Members; @@ -21,8 +22,9 @@ public AdHocDataSource( public AdHocDataSource( IQualifiedMember sourceMember, Expression value, - Expression condition) - : base(sourceMember, Enumerable.EmptyArray, value, condition) + Expression condition, + ICollection variables = null) + : base(sourceMember, variables ?? Enumerable.EmptyArray, value, condition) { } } diff --git a/AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs b/AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs deleted file mode 100644 index 870d02cd0..000000000 --- a/AgileMapper/DataSources/Finders/ConfiguredRootSourceDataSourceFinder.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources.Finders -{ - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using Extensions.Internal; - using ObjectPopulation; - - internal struct ConfiguredRootSourceDataSourceFinder : IDataSourceFinder - { - public IEnumerable FindFor(DataSourceFindContext context) - { - if (!context.MapperData.Parent.IsRoot || - !context.MapperData.MapperContext.UserConfigurations.HasDataSourceFactoriesForRootTarget) - { - yield break; - } - - var rootMapperData = context.MapperData.Parent; - - var configuredRootTargetDataSources = context - .MapperData - .MapperContext - .UserConfigurations - .GetDataSources(rootMapperData); - - if (configuredRootTargetDataSources.None()) - { - yield break; - } - - var nullEnumerableIndex = typeof(int?).ToDefaultExpression(); - - foreach (var configuredRootTargetDataSource in configuredRootTargetDataSources) - { - var mappingData = context - .ChildMappingData - .Parent.WithSource(configuredRootTargetDataSource.SourceMember); - - var mappingValues = new MappingValues( - configuredRootTargetDataSource.Value, - context.MapperData.TargetObject, - nullEnumerableIndex); - - var inlineMappingBlock = MappingFactory.GetInlineMappingBlock( - mappingData, - mappingValues, - MappingDataCreationFactory.ForDerivedType(mappingData.MapperData)); - - if (!inlineMappingBlock.TryGetMappingBody(out var mappingBody)) - { - // TODO: Null mappings from a configured root source member - } - - if (mappingBody.NodeType == ExpressionType.Block) - { - IList mappingExpressions = ((BlockExpression)mappingBody).Expressions; - - Expression localVariable; - - if (mappingExpressions.TryGetVariableAssignment(out var localVariableAssignment)) - { - localVariable = localVariableAssignment.Left; - mappingExpressions = mappingExpressions.ToList(); - mappingExpressions.Remove(localVariableAssignment); - } - else - { - localVariable = null; - } - - // TODO: Test coverage for multiple-expression member mappings - mappingBody = mappingExpressions.HasOne() - ? mappingExpressions[0] - : Expression.Block(mappingExpressions); - - if (localVariable != null) - { - mappingBody = mappingBody.Replace( - localVariable, - context.ChildMappingData.MapperData.TargetInstance); - } - } - - // TODO: Test coverage for conditional configured source members - yield return new ConfiguredDataSource( - configuredRootTargetDataSource.SourceMember, - configuredRootTargetDataSource.Condition, - mappingBody, - context.MapperData); - } - } - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/DataSourceFinder.cs b/AgileMapper/DataSources/Finders/DataSourceFinder.cs index cf92b0988..3ecc78a6c 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFinder.cs @@ -11,8 +11,7 @@ internal static class DataSourceFinder default(ConfiguredDataSourceFinder), default(MaptimeDataSourceFinder), default(SourceMemberDataSourceFinder), - default(MetaMemberDataSourceFinder), - default(ConfiguredRootSourceDataSourceFinder) + default(MetaMemberDataSourceFinder) }; public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index c92d6a695..8a1addf4c 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -10,6 +10,7 @@ namespace AgileObjects.AgileMapper.Members internal class ConfiguredSourceMember : IQualifiedMember { + private readonly Expression _rootValue; private readonly IList _matchedTargetMemberJoinedNames; private readonly MapperContext _mapperContext; private readonly Member[] _childMembers; @@ -17,6 +18,7 @@ internal class ConfiguredSourceMember : IQualifiedMember public ConfiguredSourceMember(Expression value, IMemberMapperData mapperData) : this( + value, value.Type, value.Type.IsEnumerable(), value.Type.IsSimple(), @@ -28,6 +30,7 @@ public ConfiguredSourceMember(Expression value, IMemberMapperData mapperData) private ConfiguredSourceMember(ConfiguredSourceMember parent, Member childMember) : this( + parent._rootValue, childMember.Type, childMember.IsEnumerable, childMember.IsSimple, @@ -41,6 +44,7 @@ private ConfiguredSourceMember(ConfiguredSourceMember parent, Member childMember } private ConfiguredSourceMember( + Expression rootValue, Type type, bool isEnumerable, bool isSimple, @@ -49,6 +53,7 @@ private ConfiguredSourceMember( MapperContext mapperContext, Member[] childMembers = null) { + _rootValue = rootValue; Type = type; IsEnumerable = isEnumerable; IsSimple = isSimple; @@ -95,20 +100,15 @@ public IQualifiedMember Append(Member childMember) public IQualifiedMember RelativeTo(IQualifiedMember otherMember) { - Member[] otherMemberChain; - - if (otherMember is ConfiguredSourceMember otherConfiguredMember) - { - otherMemberChain = otherConfiguredMember._childMembers; - } - else + if (!(otherMember is ConfiguredSourceMember otherConfiguredMember)) { - + return this; } - var relativeMemberChain = _childMembers.RelativeTo(otherMemberChain); + var relativeMemberChain = _childMembers.RelativeTo(otherConfiguredMember._childMembers); return new ConfiguredSourceMember( + _rootValue, Type, IsEnumerable, IsSimple, @@ -139,7 +139,16 @@ public bool Matches(IQualifiedMember otherMember) } public Expression GetQualifiedAccess(Expression parentInstance) - => _childMembers.GetQualifiedAccess(parentInstance); + { + if (_childMembers.HasOne()) + { + return _rootValue; + } + + return _childMembers.GetQualifiedAccess(parentInstance); + } + + //private bool IsMatchedToTargetRoot => _matchedTargetMemberJoinedNames.HasOne(); public IQualifiedMember WithType(Type runtimeType) { @@ -152,6 +161,7 @@ public IQualifiedMember WithType(Type runtimeType) var isSimple = !isEnumerable && (IsSimple || runtimeType.IsSimple()); return new ConfiguredSourceMember( + _rootValue, runtimeType, isEnumerable, isSimple, diff --git a/AgileMapper/Members/IMappingData.cs b/AgileMapper/Members/IMappingData.cs index d599e1520..221031731 100644 --- a/AgileMapper/Members/IMappingData.cs +++ b/AgileMapper/Members/IMappingData.cs @@ -43,7 +43,8 @@ public interface IMappingData /// The type of source object being mapped in the current context. /// The type of target object being mapped in the current context. /// The as a typed . - IMappingData As() where TSource : class where TTarget : class; + IMappingData As() + where TSource : class where TTarget : class; } /// diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index b31fe93f5..52f295f68 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -481,6 +481,9 @@ public static Expression GetAsCall(this IMemberMapperData mapperData, Type sourc => GetAsCall(mapperData.MappingDataObject, sourceType, targetType); public static Expression GetAsCall(this Expression subject, params Type[] contextTypes) + => GetAsCall(subject, contextTypes, Enumerable.EmptyArray); + + public static Expression GetAsCall(this Expression subject, Type[] contextTypes, params Expression[] arguments) { if (subject.Type.IsGenericType() && subject.Type.GetGenericTypeArguments().SequenceEqual(contextTypes)) @@ -497,25 +500,37 @@ public static Expression GetAsCall(this Expression subject, params Type[] contex if (sourceIsStruct) { - return GetAsCall(subject, subject.Type.GetPublicInstanceMethod("WithTargetType"), contextTypes[1]); + return GetAsCall(subject, subject.Type.GetPublicInstanceMethod("WithTargetType"), new[] { contextTypes[1] }); } var targetIsStruct = contextTypes[1].IsValueType(); if (targetIsStruct) { - return GetAsCall(subject, subject.Type.GetPublicInstanceMethod("WithSourceType"), contextTypes[0]); + return GetAsCall(subject, subject.Type.GetPublicInstanceMethod("WithSourceType"), new[] { contextTypes[0] }); + } + + if (arguments.None()) + { + arguments = new Expression[] { true.ToConstantExpression() }; } - return GetAsCall(subject, typeof(IObjectMappingDataUntyped).GetPublicInstanceMethod("As"), contextTypes); + return GetAsCall( + subject, + typeof(IObjectMappingDataUntyped).GetPublicInstanceMethod("As"), + contextTypes, + arguments); } private static Expression GetAsCall( Expression subject, MethodInfo asMethod, - params Type[] typeArguments) + Type[] typeArguments, + params Expression[] arguments) { - return Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments)); + return arguments.Any() + ? Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments), arguments) + : Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments)); } public static Expression GetSourceAccess( diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs index ca22f918e..e901d8cf4 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs @@ -135,13 +135,13 @@ private bool TryGetShortCircuitFactory(ObjectMapperData mapperData, out ISourceS protected override Expression GetDerivedTypeMappings(IObjectMappingData mappingData) => DerivedComplexTypeMappingsFactory.CreateFor(mappingData); - protected override IEnumerable GetObjectPopulation(IObjectMappingData mappingData) + protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { - var expressionFactory = mappingData.MapperData.UseMemberInitialisations() + var expressionFactory = context.MapperData.UseMemberInitialisations() ? _memberInitPopulationFactory : _multiStatementPopulationFactory; - return expressionFactory.GetPopulation(mappingData); + return expressionFactory.GetPopulation(context); } } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs index b2169f0a9..ffcc4180b 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs @@ -11,24 +11,25 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes internal abstract class PopulationExpressionFactoryBase { - public IEnumerable GetPopulation(IObjectMappingData mappingData) + public IEnumerable GetPopulation(MappingExpressionFactoryBase.MappingCreationContext context) { - var mapperData = mappingData.MapperData; + var mappingData = context.MappingData; + var mapperData = context.MapperData; - GetCreationCallbacks(mapperData, out var preCreationCallback, out var postCreationCallback); + GetCreationCallbacks(context, out var preCreationCallback, out var postCreationCallback); var populationsAndCallbacks = GetPopulationsAndCallbacks(mappingData).ToList(); - yield return preCreationCallback; - - if (mapperData.Context.UseLocalVariable) + if (context.InstantiateLocalVariable && mapperData.Context.UseLocalVariable) { + yield return preCreationCallback; + var assignCreatedObject = postCreationCallback != null; yield return GetLocalVariableInstantiation(assignCreatedObject, populationsAndCallbacks, mappingData); - } - yield return postCreationCallback; + yield return postCreationCallback; + } yield return GetObjectRegistrationCallOrNull(mapperData); @@ -41,19 +42,20 @@ public IEnumerable GetPopulation(IObjectMappingData mappingData) } private static void GetCreationCallbacks( - IMemberMapperData mapperData, + MappingExpressionFactoryBase.MappingCreationContext context, out Expression preCreationCallback, out Expression postCreationCallback) { - if (mapperData.RuleSet.Settings.UseSingleRootMappingExpression || - mapperData.TargetIsDefinitelyPopulated()) + if (context.RuleSet.Settings.UseSingleRootMappingExpression || + !context.InstantiateLocalVariable || + context.MapperData.TargetIsDefinitelyPopulated()) { preCreationCallback = postCreationCallback = null; return; } - preCreationCallback = GetCreationCallbackOrNull(Before, mapperData); - postCreationCallback = GetCreationCallbackOrNull(After, mapperData); + preCreationCallback = GetCreationCallbackOrNull(Before, context.MapperData); + postCreationCallback = GetCreationCallbackOrNull(After, context.MapperData); } private static Expression GetCreationCallbackOrNull(CallbackPosition callbackPosition, IMemberMapperData mapperData) diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 35c9a262c..746f53f9b 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -270,13 +270,13 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out return true; } - protected override IEnumerable GetObjectPopulation(IObjectMappingData mappingData) + protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { - var mapperData = mappingData.MapperData; + var mapperData = context.MapperData; if (!mapperData.TargetMember.IsDictionary) { - yield return GetDictionaryPopulation(mappingData); + yield return GetDictionaryPopulation(context.MappingData); yield break; } @@ -297,8 +297,8 @@ protected override IEnumerable GetObjectPopulation(IObjectMappingDat assignmentFactory = (dsm, md) => GetParameterlessDictionaryAssignment(md); } - var population = GetDictionaryPopulation(mappingData); - var assignment = assignmentFactory.Invoke(sourceDictionaryMember, mappingData); + var population = GetDictionaryPopulation(context.MappingData); + var assignment = assignmentFactory.Invoke(sourceDictionaryMember, context.MappingData); yield return assignment; yield return population; diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs index 752e00e12..6d2de072f 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs @@ -26,11 +26,11 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out return true; } - protected override IEnumerable GetObjectPopulation(IObjectMappingData mappingData) + protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { - yield return mappingData.MappingContext.RuleSet.EnumerablePopulationStrategy.GetPopulation( - mappingData.MapperData.EnumerablePopulationBuilder, - mappingData); + yield return context.RuleSet.EnumerablePopulationStrategy.GetPopulation( + context.MapperData.EnumerablePopulationBuilder, + context.MappingData); } protected override Expression GetReturnValue(ObjectMapperData mapperData) diff --git a/AgileMapper/ObjectPopulation/IObjectMappingData.cs b/AgileMapper/ObjectPopulation/IObjectMappingData.cs index 6c2f73bd7..005e4fcde 100644 --- a/AgileMapper/ObjectPopulation/IObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/IObjectMappingData.cs @@ -32,7 +32,7 @@ internal interface IObjectMappingData : IObjectMappingDataUntyped IObjectMappingData WithSource(IQualifiedMember newSourceMember); - IObjectMappingData WithTypes(Type newSourceType, Type newTargetType); + IObjectMappingData WithTypes(Type newSourceType, Type newTargetType, bool isForDerivedTypeMapping = true); } /// diff --git a/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs b/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs index 060a3b36c..b4efca627 100644 --- a/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs +++ b/AgileMapper/ObjectPopulation/IObjectMappingDataUntyped.cs @@ -74,11 +74,15 @@ TDeclaredTarget MapRecursion( /// /// The type of source object being mapped in the current context. /// The type of target object being mapped in the current context. + /// + /// Whether the new, typed is needed for the creation + /// of a derived type mapping. + /// /// /// The typed as a /// . /// - new IObjectMappingData As() + IObjectMappingData As(bool isForDerivedTypeMapping) where TNewSource : class where TNewTarget : class; } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/MapperDataContext.cs b/AgileMapper/ObjectPopulation/MapperDataContext.cs index 3ae6dde8d..07180082c 100644 --- a/AgileMapper/ObjectPopulation/MapperDataContext.cs +++ b/AgileMapper/ObjectPopulation/MapperDataContext.cs @@ -19,7 +19,7 @@ public MapperDataContext(IMemberMapperData childMapperData) { } - private static bool IsForStandaloneMapping(IBasicMapperData mapperData) + private static bool IsForStandaloneMapping(ITypePair mapperData) => mapperData.SourceType.RuntimeTypeNeeded() || mapperData.TargetType.RuntimeTypeNeeded(); public MapperDataContext(ObjectMapperData mapperData, bool isStandalone, bool isForDerivedType) diff --git a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs index 28bd05396..f9d998427 100644 --- a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs @@ -20,7 +20,8 @@ private static bool UseAsConversion(ObjectMapperData childMapperData, out Expres { if (childMapperData.Context.IsStandalone) { - conversion = childMapperData.DeclaredTypeMapperData + conversion = childMapperData + .DeclaredTypeMapperData .GetAsCall(childMapperData.SourceType, childMapperData.TargetType); return true; diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 642221bd5..4de21acc4 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -5,7 +5,9 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Linq; using System.Linq.Expressions; using Extensions.Internal; + using MapperKeys; using Members; + using Members.Sources; using NetStandardPolyfills; using static System.Linq.Expressions.ExpressionType; using static CallbackPosition; @@ -36,13 +38,14 @@ public Expression Create(IObjectMappingData mappingData) : derivedTypeMappings; } - var mappingExtras = GetMappingExtras(mapperData); + var context = GetCreationContext(mappingData); var mappingExpressions = new List(); mappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings); - mappingExpressions.AddUnlessNullOrEmpty(mappingExtras.PreMappingCallback); - mappingExpressions.AddRange(GetObjectPopulation(mappingData).WhereNotNull()); - mappingExpressions.AddUnlessNullOrEmpty(mappingExtras.PostMappingCallback); + mappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); + mappingExpressions.AddRange(GetObjectPopulation(context).WhereNotNull()); + mappingExpressions.AddRange(GetConfiguredRootSourceMemberPopulations(context)); + mappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); if (NothingIsBeingMapped(mappingExpressions, mapperData)) { @@ -51,7 +54,7 @@ public Expression Create(IObjectMappingData mappingData) mappingExpressions.InsertRange(0, GetShortCircuitReturns(returnNull, mappingData)); - var mappingBlock = GetMappingBlock(mappingExpressions, mappingExtras, mapperData); + var mappingBlock = GetMappingBlock(mappingExpressions, context, mapperData); if (mapperData.Context.UseMappingTryCatch) { @@ -67,9 +70,6 @@ protected virtual bool TargetCannotBeMapped(IObjectMappingData mappingData, out return false; } - protected virtual IEnumerable GetShortCircuitReturns(GotoExpression returnNull, IObjectMappingData mappingData) - => Enumerable.Empty; - private bool MappingAlwaysBranchesToDerivedType(IObjectMappingData mappingData, out Expression derivedTypeMappings) { derivedTypeMappings = GetDerivedTypeMappings(mappingData); @@ -86,28 +86,94 @@ private bool MappingAlwaysBranchesToDerivedType(IObjectMappingData mappingData, protected virtual Expression GetDerivedTypeMappings(IObjectMappingData mappingData) => Constants.EmptyExpression; - private static MappingExtras GetMappingExtras(ObjectMapperData mapperData) + protected virtual IEnumerable GetShortCircuitReturns(GotoExpression returnNull, IObjectMappingData mappingData) + => Enumerable.Empty; + + private static MappingCreationContext GetCreationContext(IObjectMappingData mappingData) { + var mapperData = mappingData.MapperData; var mapToNullCondition = GetMapToNullConditionOrNull(mapperData); if (mapperData.RuleSet.Settings.UseSingleRootMappingExpression) { - return (mapToNullCondition != null) - ? new MappingExtras(mapToNullCondition) - : MappingExtras.Empty; + return new MappingCreationContext(mappingData, mapToNullCondition); } var basicMapperData = mapperData.WithNoTargetMember(); var preMappingCallback = basicMapperData.GetMappingCallbackOrNull(Before, mapperData); var postMappingCallback = basicMapperData.GetMappingCallbackOrNull(After, mapperData); - return new MappingExtras(preMappingCallback, postMappingCallback, mapToNullCondition); + return new MappingCreationContext( + mappingData, + preMappingCallback, + postMappingCallback, + mapToNullCondition); } private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData) => mapperData.MapperContext.UserConfigurations.GetMapToNullConditionOrNull(mapperData); - protected abstract IEnumerable GetObjectPopulation(IObjectMappingData mappingData); + protected abstract IEnumerable GetObjectPopulation(MappingCreationContext context); + + private IEnumerable GetConfiguredRootSourceMemberPopulations(MappingCreationContext context) + { + if (!context.IsRoot || + !context.MapperContext.UserConfigurations.HasDataSourceFactoriesForRootTarget) + { + yield break; + } + + var configuredRootTargetDataSources = context + .MapperContext + .UserConfigurations + .GetDataSources(context.MapperData); + + if (configuredRootTargetDataSources.None()) + { + yield break; + } + + // TODO: Test coverage for conditional configured source members + // TODO: Test coverage for mappings with variables + foreach (var configuredRootTargetDataSource in configuredRootTargetDataSources) + { + var newSourceMappingData = context.MappingData.WithSource(configuredRootTargetDataSource.SourceMember); + + newSourceMappingData.MapperKey = new RootObjectMapperKey( + context.RuleSet, + newSourceMappingData.MappingTypes, + new FixedMembersMembersSource( + configuredRootTargetDataSource.SourceMember, + context.MapperData.TargetMember)); + + newSourceMappingData.MapperData.SourceObject = configuredRootTargetDataSource.Value; + newSourceMappingData.MapperData.TargetInstance = context.MapperData.TargetInstance; + + var newSourceContext = new MappingCreationContext(newSourceMappingData) + { + InstantiateLocalVariable = false + }; + + var memberPopulations = GetObjectPopulation(newSourceContext).WhereNotNull().ToArray(); + + if (memberPopulations.None()) + { + // TODO: Test coverage for configured sources which match no target members + continue; + } + + var mapping = memberPopulations.HasOne() + ? memberPopulations.First() + : Expression.Block(memberPopulations); + + if (configuredRootTargetDataSource.IsConditional) + { + mapping = Expression.IfThen(configuredRootTargetDataSource.Condition, mapping); + } + + yield return mapping; + } + } private static bool NothingIsBeingMapped(IList mappingExpressions, IMemberMapperData mapperData) { @@ -193,7 +259,7 @@ private static bool IsMapRecursionCall(Expression expression) private Expression GetMappingBlock( IList mappingExpressions, - MappingExtras mappingExtras, + MappingCreationContext mappingCreationContext, ObjectMapperData mapperData) { AdjustForSingleExpressionBlockIfApplicable(ref mappingExpressions); @@ -214,12 +280,12 @@ private Expression GetMappingBlock( { if (mappingExpressions[0].NodeType == MemberAccess) { - return GetReturnExpression(mappingExpressions[0], mappingExtras); + return GetReturnExpression(mappingExpressions[0], mappingCreationContext); } if (TryAdjustForUnusedLocalVariableIfApplicable( mappingExpressions, - mappingExtras, + mappingCreationContext, mapperData, out returnExpression)) { @@ -228,7 +294,7 @@ private Expression GetMappingBlock( } else if (TryAdjustForUnusedLocalVariableIfApplicable( mappingExpressions, - mappingExtras, + mappingCreationContext, mapperData, out returnExpression)) { @@ -237,7 +303,7 @@ private Expression GetMappingBlock( CreateFullMappingBlock: - returnExpression = GetReturnExpression(GetReturnValue(mapperData), mappingExtras); + returnExpression = GetReturnExpression(GetReturnValue(mapperData), mappingCreationContext); mappingExpressions.Add(mapperData.GetReturnLabel(returnExpression)); @@ -265,7 +331,7 @@ private static void AdjustForSingleExpressionBlockIfApplicable(ref IList mappingExpressions, - MappingExtras mappingExtras, + MappingCreationContext context, ObjectMapperData mapperData, out Expression returnExpression) { @@ -293,8 +359,8 @@ private static bool TryAdjustForUnusedLocalVariableIfApplicable( returnExpression = (assignedValue.NodeType == Invoke) ? Expression.Block( new[] { (ParameterExpression)localVariableAssignment.Left }, - GetReturnExpression(localVariableAssignment, mappingExtras)) - : GetReturnExpression(assignedValue, mappingExtras); + GetReturnExpression(localVariableAssignment, context)) + : GetReturnExpression(assignedValue, context); if (mappingExpressions.HasOne()) { @@ -306,11 +372,11 @@ private static bool TryAdjustForUnusedLocalVariableIfApplicable( return true; } - private static Expression GetReturnExpression(Expression returnValue, MappingExtras mappingExtras) + private static Expression GetReturnExpression(Expression returnValue, MappingCreationContext context) { - return (mappingExtras.MapToNullCondition != null) + return (context.MapToNullCondition != null) ? Expression.Condition( - mappingExtras.MapToNullCondition, + context.MapToNullCondition, returnValue.Type.ToDefaultExpression(), returnValue) : returnValue; @@ -374,30 +440,43 @@ public virtual void Reset() #region Helper Class - internal class MappingExtras + internal class MappingCreationContext { - public static readonly MappingExtras Empty = new MappingExtras(null, null, null); - - public MappingExtras(Expression mapToNullCondition) - : this(null, null, mapToNullCondition) + public MappingCreationContext(IObjectMappingData mappingData, Expression mapToNullCondition = null) + : this(mappingData, null, null, mapToNullCondition) { } - public MappingExtras( + public MappingCreationContext( + IObjectMappingData mappingData, Expression preMappingCallback, Expression postMappingCallback, Expression mapToNullCondition) { + MappingData = mappingData; PreMappingCallback = preMappingCallback; PostMappingCallback = postMappingCallback; MapToNullCondition = mapToNullCondition; + InstantiateLocalVariable = true; } + public MapperContext MapperContext => MapperData.MapperContext; + + public MappingRuleSet RuleSet => MappingData.MappingContext.RuleSet; + + public ObjectMapperData MapperData => MappingData.MapperData; + + public bool IsRoot => MappingData.IsRoot; + + public IObjectMappingData MappingData { get; } + public Expression PreMappingCallback { get; } public Expression PostMappingCallback { get; } public Expression MapToNullCondition { get; } + + public bool InstantiateLocalVariable { get; set; } } #endregion diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index f366ac2d6..8b13ca9f3 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -366,14 +366,17 @@ public bool CacheMappedObjects public bool TargetTypeWillNotBeMappedAgain { get; } - public Expression SourceObject { get; } + public Expression SourceObject { get; set; } public Expression TargetObject { get; } public Expression EnumerableIndex { get; } public Expression TargetInstance - => _targetInstance ?? (_targetInstance = GetTargetInstance()); + { + get => _targetInstance ?? (_targetInstance = GetTargetInstance()); + set => _targetInstance = value; + } private Expression GetTargetInstance() => Context.UseLocalVariable ? LocalVariable : TargetObject; diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index b74341a8e..ad029304c 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -35,7 +35,8 @@ public ObjectMappingData( mappingTypes, mappingContext, null, - parent) + parent, + createMapper: true) { } @@ -46,7 +47,8 @@ private ObjectMappingData( MappingTypes mappingTypes, IMappingContext mappingContext, IObjectMappingData declaredTypeMappingData, - IObjectMappingData parent) + IObjectMappingData parent, + bool createMapper) : base(source, target, enumerableIndex, parent) { MappingTypes = mappingTypes; @@ -56,15 +58,11 @@ private ObjectMappingData( if (parent != null) { Parent = parent; - return; } - - if (IsPartOfDerivedTypeMapping) + else if (createMapper) { - return; + _mapper = MapperContext.ObjectMapperFactory.GetOrCreateRoot(this); } - - _mapper = MapperContext.ObjectMapperFactory.GetOrCreateRoot(this); } public IMappingContext MappingContext { get; } @@ -308,52 +306,62 @@ public void Register(TKey key, TComplex complexType) public IObjectMappingData WithSourceType() where TNewSource : class { - return As(Source as TNewSource, Target); + return As(Source as TNewSource, Target, isForDerivedTypeMapping: true); } public IObjectMappingData WithTargetType() where TNewTarget : class { - return As(Source, Target as TNewTarget); + return As(Source, Target as TNewTarget, isForDerivedTypeMapping: true); } public IObjectMappingData WithSource(IQualifiedMember newSourceMember) { var sourceMemberRuntimeType = GetSourceMemberRuntimeType(newSourceMember); - return WithTypes(sourceMemberRuntimeType, MapperData.TargetType); + return WithTypes(sourceMemberRuntimeType, MapperData.TargetType, isForDerivedTypeMapping: false); } - public IObjectMappingData WithTypes(Type newSourceType, Type newTargetType) + public IObjectMappingData WithTypes(Type newSourceType, Type newTargetType, bool isForDerivedTypeMapping) { var typesKey = new SourceAndTargetTypesKey(newSourceType, newTargetType); var typedAsCaller = GlobalContext.Instance.Cache.GetOrAdd(typesKey, k => { var mappingDataParameter = Parameters.Create>("mappingData"); - var withTypesCall = mappingDataParameter.GetAsCall(k.SourceType, k.TargetType); + var isForDerivedTypeParameter = Parameters.Create("isForDerivedType"); + var withTypesCall = mappingDataParameter.GetAsCall(new[] { k.SourceType, k.TargetType }, isForDerivedTypeParameter); var withTypesLambda = Expression - .Lambda, IObjectMappingDataUntyped>>( + .Lambda, bool, IObjectMappingDataUntyped>>( withTypesCall, - mappingDataParameter); + mappingDataParameter, + isForDerivedTypeParameter); return withTypesLambda.Compile(); }); - return (IObjectMappingData)typedAsCaller.Invoke(this); + return (IObjectMappingData)typedAsCaller.Invoke(this, isForDerivedTypeMapping); } public IObjectMappingData As() where TNewSource : class where TNewTarget : class { - return As(Source as TNewSource, Target as TNewTarget); + return As(isForDerivedTypeMapping: true); + } + + public IObjectMappingData As(bool isForDerivedTypeMapping) + where TNewSource : class + where TNewTarget : class + { + return As(Source as TNewSource, Target as TNewTarget, isForDerivedTypeMapping); } private IObjectMappingData As( TNewSource typedSource, - TNewTarget typedTarget) + TNewTarget typedTarget, + bool isForDerivedTypeMapping) { var mapperKey = MapperKey.WithTypes(); @@ -363,8 +371,9 @@ private IObjectMappingData As( GetEnumerableIndex(), mapperKey.MappingTypes, MappingContext, - this, - Parent) + isForDerivedTypeMapping ? this : null, + Parent, + createMapper: false) { MapperKey = mapperKey }; diff --git a/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs index de1bbcb58..03d7816a3 100644 --- a/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs @@ -12,9 +12,9 @@ internal class SimpleTypeMappingExpressionFactory : MappingExpressionFactoryBase public override bool IsFor(IObjectMappingData mappingData) => mappingData.MappingTypes.TargetType.IsSimple(); - protected override IEnumerable GetObjectPopulation(IObjectMappingData mappingData) + protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { - var mapperData = mappingData.MapperData; + var mapperData = context.MapperData; return new[] { diff --git a/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs b/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs index e8e1aadcd..6174b4a1f 100644 --- a/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs +++ b/AgileMapper/Queryables/QueryProjectionExpressionFactory.cs @@ -18,9 +18,9 @@ public override bool IsFor(IObjectMappingData mappingData) mapperData.SourceType.IsQueryable(); } - protected override IEnumerable GetObjectPopulation(IObjectMappingData mappingData) + protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { - var mapperData = mappingData.MapperData; + var mapperData = context.MapperData; var queryProjection = mapperData .EnumerablePopulationBuilder @@ -29,9 +29,9 @@ protected override IEnumerable GetObjectPopulation(IObjectMappingDat sourceParameter => MappingFactory.GetElementMapping( sourceParameter, mapperData.TargetMember.ElementType.ToDefaultExpression(), - mappingData)); + context.MappingData)); - queryProjection = QueryProjectionModifier.Modify(queryProjection, mappingData); + queryProjection = QueryProjectionModifier.Modify(queryProjection, context.MappingData); yield return queryProjection; } From 1e79eebb72f3864b51b67a7065bb072f7b9b4aae Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 08:06:03 +0100 Subject: [PATCH 06/16] Short-circuiting Relative member chain creation when possible --- AgileMapper/Members/ConfiguredSourceMember.cs | 11 ++++++++--- AgileMapper/Members/MemberExtensions.cs | 6 ++++++ .../ObjectPopulation/MappingDataCreationFactory.cs | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index 8a1addf4c..0a6aa9e9d 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -15,6 +15,7 @@ internal class ConfiguredSourceMember : IQualifiedMember private readonly MapperContext _mapperContext; private readonly Member[] _childMembers; private readonly ICache _childMemberCache; + private readonly bool _isMatchedToRootTarget; public ConfiguredSourceMember(Expression value, IMemberMapperData mapperData) : this( @@ -26,6 +27,7 @@ public ConfiguredSourceMember(Expression value, IMemberMapperData mapperData) mapperData.TargetMember.JoinedNames, mapperData.MapperContext) { + _isMatchedToRootTarget = mapperData.TargetMember.IsRoot; } private ConfiguredSourceMember(ConfiguredSourceMember parent, Member childMember) @@ -107,6 +109,11 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) var relativeMemberChain = _childMembers.RelativeTo(otherConfiguredMember._childMembers); + if (relativeMemberChain == _childMembers) + { + return this; + } + return new ConfiguredSourceMember( _rootValue, Type, @@ -140,7 +147,7 @@ public bool Matches(IQualifiedMember otherMember) public Expression GetQualifiedAccess(Expression parentInstance) { - if (_childMembers.HasOne()) + if (_isMatchedToRootTarget && _childMembers.HasOne()) { return _rootValue; } @@ -148,8 +155,6 @@ public Expression GetQualifiedAccess(Expression parentInstance) return _childMembers.GetQualifiedAccess(parentInstance); } - //private bool IsMatchedToTargetRoot => _matchedTargetMemberJoinedNames.HasOne(); - public IQualifiedMember WithType(Type runtimeType) { if (runtimeType == Type) diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index d89668ade..6e097b2c1 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -184,6 +184,12 @@ public static Member GetElementMember(this Type enumerableType) public static Member[] RelativeTo(this Member[] memberChain, Member[] otherMemberChain) { var otherMembersLeafMember = otherMemberChain.Last(); + + if (memberChain.HasOne() && (memberChain[0] == otherMembersLeafMember)) + { + return memberChain; + } + var startIndex = memberChain.Length - 1; if ((memberChain.Length > 2) && diff --git a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs index f9d998427..4a263dba6 100644 --- a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs @@ -31,7 +31,7 @@ private static bool UseAsConversion(ObjectMapperData childMapperData, out Expres return false; } - [DebuggerStepThrough] + //[DebuggerStepThrough] public static Expression ForChild( MappingValues mappingValues, int dataSourceIndex, From e03c3c5eda624c1afd8cdde379390411f795bdfc Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 08:31:43 +0100 Subject: [PATCH 07/16] Increased test coverage for root target data sources - invalid configurations and values with no member matches --- .../WhenConfiguringDataSources.cs | 29 ++++++++++++++-- .../WhenConfiguringDataSourcesIncorrectly.cs | 25 ++++++++++++-- .../CustomDataSourceTargetMemberSpecifier.cs | 34 +++++++++++++++---- ...mMappingDataSourceTargetMemberSpecifier.cs | 2 +- AgileMapper/Members/MemberExtensions.cs | 6 ++-- .../MappingExpressionFactoryBase.cs | 1 - 6 files changed, 80 insertions(+), 17 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 33ba74e80..eb7828e7a 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -996,7 +996,7 @@ public void ShouldAllowIdAndIdentifierConfiguration() // See https://github.com/agileobjects/AgileMapper/issues/64 [Fact] - public void ShouldApplyAConfiguredComplexTypeMemberToTheRootTarget() + public void ShouldApplyARootTargetComplexTypeMemberDataSource() { using (var mapper = Mapper.CreateNew()) { @@ -1006,7 +1006,7 @@ public void ShouldApplyAConfiguredComplexTypeMemberToTheRootTarget() .From(source) .To>() .Map((s, ptf) => s.Value) - .ToTarget(); + .ToRootTarget(); var result = source .MapUsing(mapper) @@ -1026,7 +1026,7 @@ public void ShouldApplyANestedComplexTypeToRootTargetOverwriteConfiguration() .From>>>() .Over>() .Map((s, t) => s.Value2.Value) - .ToTarget(); + .ToRootTarget(); var source = new PublicTwoFields>> { @@ -1053,6 +1053,29 @@ public void ShouldApplyANestedComplexTypeToRootTargetOverwriteConfiguration() } } + [Fact] + public void ShouldHandleARootTargetDataSourceNullValue() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToANew>() + .Map((mc, t) => mc.Name) + .To(t => t.Value1) + .And + .Map((mc, t) => mc.Address) + .ToRootTarget(); + + var source = new MysteryCustomer { Name = "Nelly", Address = default(Address) }; + + var result = mapper.Map(source).ToANew>(); + + result.Value1.ShouldBe("Nelly"); + result.Value2.ShouldBeNull(); + } + } + // ReSharper disable once ClassNeverInstantiated.Local // ReSharper disable UnusedAutoPropertyAccessor.Local private class IdTester diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs index 5414d367d..04539d17d 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSourcesIncorrectly.cs @@ -273,7 +273,7 @@ public void ShouldErrorIfTargetParameterConfiguredAsTarget() } [Fact] - public void ShouldErrorIfSimpleTypeConfiguredAsRootTargetDataSource() + public void ShouldErrorIfRootTargetSimpleTypeConstantDataSourceConfigured() { var configurationException = Should.Throw(() => { @@ -283,12 +283,33 @@ public void ShouldErrorIfSimpleTypeConfiguredAsRootTargetDataSource() .From>() .To>() .Map("No no no no no") - .ToTarget(); + .ToRootTarget(); } }); configurationException.Message.ShouldContain( "'string' cannot be mapped to root target type 'PublicField'"); } + + [Fact] + public void ShouldErrorIfRootTargetSimpleTypeMemberDataSourceConfigured() + { + var configurationException = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map(ctx => ctx.Source.Value) + .ToRootTarget(); + } + }); + + configurationException.Message.ShouldContain("PublicProperty.Value"); + + configurationException.Message.ShouldContain( + "'int' cannot be mapped to root target type 'PublicField'"); + } } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 634ae801b..9d5444e6b 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -13,6 +13,7 @@ using Members.Dictionaries; using NetStandardPolyfills; using Projection; + using ReadableExpressions; using ReadableExpressions.Extensions; @@ -244,7 +245,7 @@ private ConfiguredDataSourceFactory CreateForCtorParam(ParameterInfo par #endregion - public IMappingConfigContinuation ToTarget() + public IMappingConfigContinuation ToRootTarget() { ThrowIfSimpleSourceTypeConfigured(); @@ -256,14 +257,33 @@ public IMappingConfigContinuation ToTarget() private void ThrowIfSimpleSourceTypeConfigured() { - if (_customValueLambda.Body.Type.IsSimple()) + var customValue = _customValueLambda.Body; + + if (!customValue.Type.IsSimple()) + { + return; + } + + string sourceValue; + + if (customValue.NodeType == ExpressionType.MemberAccess) { - throw new MappingConfigurationException(string.Format( - CultureInfo.InvariantCulture, - "Source type '{0}' cannot be mapped to root target type '{1}'", - _customValueLambda.Body.Type.GetFriendlyName(), - typeof(TTarget).GetFriendlyName())); + var rootSourceMember = QualifiedMember.From(Member.RootSource(), _configInfo.MapperContext); + var configuredMember = Member.RootSource(customValue.ToReadableString(), customValue.Type); + var configuredSourceMember = QualifiedMember.From(configuredMember, _configInfo.MapperContext); + sourceValue = configuredSourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; } + else + { + sourceValue = "Source type "; + } + + throw new MappingConfigurationException(string.Format( + CultureInfo.InvariantCulture, + "{0}'{1}' cannot be mapped to root target type '{2}'", + sourceValue, + customValue.Type.GetFriendlyName(), + typeof(TTarget).GetFriendlyName())); } private MappingConfigContinuation RegisterDataSource( diff --git a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs index 9d40e7352..a7e5fed33 100644 --- a/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/ICustomMappingDataSourceTargetMemberSpecifier.cs @@ -61,6 +61,6 @@ IMappingConfigContinuation To( /// An IMappingConfigContinuation to enable further configuration of mappings from and to the source /// and target type being configured. /// - IMappingConfigContinuation ToTarget(); + IMappingConfigContinuation ToRootTarget(); } } \ No newline at end of file diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 6e097b2c1..1a31d2903 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -19,12 +19,12 @@ public static string GetFullName(this IEnumerable members) => string.Join(string.Empty, members.Select(m => m.JoiningName)); public static string GetFriendlySourcePath(this IQualifiedMember sourceMember, IMemberMapperData rootMapperData) - => GetMemberPath(sourceMember, rootMapperData.SourceMember); + => GetFriendlyMemberPath(sourceMember, rootMapperData.SourceMember); public static string GetFriendlyTargetPath(this IQualifiedMember targetMember, IMemberMapperData rootMapperData) - => GetMemberPath(targetMember, rootMapperData.TargetMember); + => GetFriendlyMemberPath(targetMember, rootMapperData.TargetMember); - private static string GetMemberPath(IQualifiedMember member, IQualifiedMember rootMember) + public static string GetFriendlyMemberPath(this IQualifiedMember member, IQualifiedMember rootMember) { var rootTypeName = rootMember.GetFriendlyTypeName(); var memberPath = member.GetPath(); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 4de21acc4..90f06f07c 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -158,7 +158,6 @@ private IEnumerable GetConfiguredRootSourceMemberPopulations(Mapping if (memberPopulations.None()) { - // TODO: Test coverage for configured sources which match no target members continue; } From ad6b84f4e921214c733231fb72905511e4f8445b Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 09:29:34 +0100 Subject: [PATCH 08/16] Support for conditional configured root source members --- .../WhenConfiguringDataSources.cs | 52 +++++++++++++++++-- AgileMapper/Members/ConfiguredSourceMember.cs | 4 +- .../Members/MemberMapperDataExtensions.cs | 37 ++++++------- .../ObjectPopulation/IObjectMappingData.cs | 24 ++++++--- .../MappingExpressionFactoryBase.cs | 1 - .../ObjectPopulation/ObjectMappingData.cs | 10 ++-- 6 files changed, 92 insertions(+), 36 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index eb7828e7a..5c600fe80 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -996,7 +996,7 @@ public void ShouldAllowIdAndIdentifierConfiguration() // See https://github.com/agileobjects/AgileMapper/issues/64 [Fact] - public void ShouldApplyARootTargetComplexTypeMemberDataSource() + public void ShouldApplyAConfiguredRootSourceMember() { using (var mapper = Mapper.CreateNew()) { @@ -1018,7 +1018,7 @@ public void ShouldApplyARootTargetComplexTypeMemberDataSource() } [Fact] - public void ShouldApplyANestedComplexTypeToRootTargetOverwriteConfiguration() + public void ShouldApplyANestedOverwriteConfiguredRootSourceMember() { using (var mapper = Mapper.CreateNew()) { @@ -1054,7 +1054,7 @@ public void ShouldApplyANestedComplexTypeToRootTargetOverwriteConfiguration() } [Fact] - public void ShouldHandleARootTargetDataSourceNullValue() + public void ShouldHandleAConfiguredRootSourceMemberNullValue() { using (var mapper = Mapper.CreateNew()) { @@ -1076,6 +1076,52 @@ public void ShouldHandleARootTargetDataSourceNullValue() } } + [Fact] + public void ShouldApplyAConfiguredRootSourceMemberConditionally() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From, int>>() + .OnTo>() + .If((s, t) => s.Value1.Value > 5) + .Map((s, t) => s.Value1) + .ToRootTarget(); + + mapper.WhenMapping + .From>() + .OnTo>() + .Map((s, t) => s.Value) + .To(t => t.Value1); + + var matchingSource = new PublicTwoFieldsStruct, int> + { + Value1 = new PublicPropertyStruct { Value = 10 }, + Value2 = 627 + }; + + var target = new PublicTwoFields { Value2 = 673282 }; + + mapper.Map(matchingSource).OnTo(target); + + target.Value1.ShouldBe(10); + target.Value2.ShouldBe(673282); + + var nonMatchingSource = new PublicTwoFieldsStruct, int> + { + Value1 = new PublicPropertyStruct { Value = 1 }, + Value2 = 9285 + }; + + target.Value1 = target.Value2 = default(int); + + mapper.Map(nonMatchingSource).OnTo(target); + + target.Value1.ShouldBeDefault(); + target.Value2.ShouldBe(9285); + } + } + // ReSharper disable once ClassNeverInstantiated.Local // ReSharper disable UnusedAutoPropertyAccessor.Local private class IdTester diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index 0a6aa9e9d..f8aa643d9 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.Members { using System; using System.Collections.Generic; + using System.Linq; using System.Linq.Expressions; using Caching; using Extensions.Internal; @@ -109,7 +110,8 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) var relativeMemberChain = _childMembers.RelativeTo(otherConfiguredMember._childMembers); - if (relativeMemberChain == _childMembers) + if ((relativeMemberChain == _childMembers) || + relativeMemberChain.SequenceEqual(_childMembers)) { return this; } diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 52f295f68..ec02926f7 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -481,9 +481,9 @@ public static Expression GetAsCall(this IMemberMapperData mapperData, Type sourc => GetAsCall(mapperData.MappingDataObject, sourceType, targetType); public static Expression GetAsCall(this Expression subject, params Type[] contextTypes) - => GetAsCall(subject, contextTypes, Enumerable.EmptyArray); + => GetAsCall(subject, null, contextTypes); - public static Expression GetAsCall(this Expression subject, Type[] contextTypes, params Expression[] arguments) + public static Expression GetAsCall(this Expression subject, Expression isForDerivedTypeArgument, params Type[] contextTypes) { if (subject.Type.IsGenericType() && subject.Type.GetGenericTypeArguments().SequenceEqual(contextTypes)) @@ -496,40 +496,37 @@ public static Expression GetAsCall(this Expression subject, Type[] contextTypes, return GetAsCall(subject, typeof(IMappingData).GetPublicInstanceMethod("As"), contextTypes); } - var sourceIsStruct = contextTypes[0].IsValueType(); - - if (sourceIsStruct) + if (isForDerivedTypeArgument == null) { - return GetAsCall(subject, subject.Type.GetPublicInstanceMethod("WithTargetType"), new[] { contextTypes[1] }); + isForDerivedTypeArgument = true.ToConstantExpression(); } - var targetIsStruct = contextTypes[1].IsValueType(); + MethodInfo conversionMethod; - if (targetIsStruct) + if (contextTypes[0].IsValueType()) { - return GetAsCall(subject, subject.Type.GetPublicInstanceMethod("WithSourceType"), new[] { contextTypes[0] }); + conversionMethod = subject.Type.GetPublicInstanceMethod("WithTargetType"); } - - if (arguments.None()) + else if (contextTypes[1].IsValueType()) + { + conversionMethod = subject.Type.GetPublicInstanceMethod("WithSourceType"); + } + else { - arguments = new Expression[] { true.ToConstantExpression() }; + conversionMethod = typeof(IObjectMappingDataUntyped).GetPublicInstanceMethod("As"); } - return GetAsCall( - subject, - typeof(IObjectMappingDataUntyped).GetPublicInstanceMethod("As"), - contextTypes, - arguments); + return GetAsCall(subject, conversionMethod, contextTypes, isForDerivedTypeArgument); } private static Expression GetAsCall( Expression subject, MethodInfo asMethod, Type[] typeArguments, - params Expression[] arguments) + Expression isForDerivedTypeArgument = null) { - return arguments.Any() - ? Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments), arguments) + return (isForDerivedTypeArgument != null) + ? Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments), isForDerivedTypeArgument) : Expression.Call(subject, asMethod.MakeGenericMethod(typeArguments)); } diff --git a/AgileMapper/ObjectPopulation/IObjectMappingData.cs b/AgileMapper/ObjectPopulation/IObjectMappingData.cs index 005e4fcde..cd17ac2af 100644 --- a/AgileMapper/ObjectPopulation/IObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/IObjectMappingData.cs @@ -103,26 +103,38 @@ TTargetElement Map( /// /// Gets the typed as a - /// . + /// when the target object definitely + /// cannot be converted to the given . /// /// The type of source object being mapped in the current context. + /// The type of target object being mapped in the current context. + /// + /// Whether the new, typed is needed for the creation + /// of a derived type mapping. + /// /// /// The typed as a - /// . + /// . /// - IObjectMappingData WithSourceType() + IObjectMappingData WithSourceType(bool isForDerivedTypeMapping) where TNewSource : class; /// /// Gets the typed as a - /// . + /// when the source object definitely + /// cannot be converted to the given . /// + /// The type of source object being mapped in the current context. /// The type of target object being mapped in the current context. + /// + /// Whether the new, typed is needed for the creation + /// of a derived type mapping. + /// /// /// The typed as a - /// . + /// . /// - IObjectMappingData WithTargetType() + IObjectMappingData WithTargetType(bool isForDerivedTypeMapping) where TNewTarget : class; } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 90f06f07c..c4f9c5a93 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -133,7 +133,6 @@ private IEnumerable GetConfiguredRootSourceMemberPopulations(Mapping yield break; } - // TODO: Test coverage for conditional configured source members // TODO: Test coverage for mappings with variables foreach (var configuredRootTargetDataSource in configuredRootTargetDataSources) { diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index ad029304c..d69911451 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -303,16 +303,16 @@ public void Register(TKey key, TComplex complexType) _mappedObjectsBySource[key] = new List { complexType }; } - public IObjectMappingData WithSourceType() + public IObjectMappingData WithSourceType(bool isForDerivedTypeMapping) where TNewSource : class { - return As(Source as TNewSource, Target, isForDerivedTypeMapping: true); + return As(Source as TNewSource, default(TNewTarget), isForDerivedTypeMapping); } - public IObjectMappingData WithTargetType() + public IObjectMappingData WithTargetType(bool isForDerivedTypeMapping) where TNewTarget : class { - return As(Source, Target as TNewTarget, isForDerivedTypeMapping: true); + return As(default(TNewSource), Target as TNewTarget, isForDerivedTypeMapping); } public IObjectMappingData WithSource(IQualifiedMember newSourceMember) @@ -330,7 +330,7 @@ public IObjectMappingData WithTypes(Type newSourceType, Type newTargetType, bool { var mappingDataParameter = Parameters.Create>("mappingData"); var isForDerivedTypeParameter = Parameters.Create("isForDerivedType"); - var withTypesCall = mappingDataParameter.GetAsCall(new[] { k.SourceType, k.TargetType }, isForDerivedTypeParameter); + var withTypesCall = mappingDataParameter.GetAsCall(isForDerivedTypeParameter, k.SourceType, k.TargetType); var withTypesLambda = Expression .Lambda, bool, IObjectMappingDataUntyped>>( From fc20aed692b48852ba5be0d9d897d55fdbbe4bf1 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 10:09:36 +0100 Subject: [PATCH 09/16] Tidying / Start of support for configured enumerable root data sources --- .../WhenConfiguringDataSources.cs | 31 ++++++++ .../Configuration/UserConfigurationSet.cs | 6 +- .../EnumerableMappingExpressionFactory.cs | 12 +++ .../MappingExpressionFactoryBase.cs | 75 +++++++++++-------- 4 files changed, 89 insertions(+), 35 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 5c600fe80..8e610e0ac 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1122,6 +1122,37 @@ public void ShouldApplyAConfiguredRootSourceMemberConditionally() } } + [Fact] + public void ShouldApplyAConfiguredRootSourceEnumerableMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To>() + .Map((s, r) => s.Value2) + .ToRootTarget(); + + var source = new PublicTwoFields + { + Value1 = new Address { Line1 = "Here", Line2 = "There" }, + Value2 = new[] + { + new Address { Line1 = "Somewhere", Line2 = "Else" }, + new Address { Line1 = "Elsewhere"} + } + }; + + var result = mapper.Map(source).ToANew>(); + + result.Count.ShouldBe(2); + result.First().Line1.ShouldBe("Somewhere"); + result.First().Line2.ShouldBe("Else"); + result.Second().Line1.ShouldBe("Elsewhere"); + result.Second().Line2.ShouldBeNull(); + } + } + // ReSharper disable once ClassNeverInstantiated.Local // ReSharper disable UnusedAutoPropertyAccessor.Local private class IdTester diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index 83a287895..7095e5a86 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -180,13 +180,13 @@ public void Add(ConfiguredDataSourceFactory dataSourceFactory) DataSourceFactories.AddSortFilter(dataSourceFactory); - if (!HasDataSourceFactoriesForRootTarget && dataSourceFactory.TargetMember.IsRoot) + if (!HasConfiguredRootDataSources && dataSourceFactory.TargetMember.IsRoot) { - HasDataSourceFactoriesForRootTarget = true; + HasConfiguredRootDataSources = true; } } - public bool HasDataSourceFactoriesForRootTarget { get; private set; } + public bool HasConfiguredRootDataSources { get; private set; } public IList GetDataSources(IMemberMapperData mapperData) => QueryDataSourceFactories(mapperData).Select(dsf => dsf.Create(mapperData)).ToArray(); diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs index 6d2de072f..7748802a0 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableMappingExpressionFactory.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables { using System.Collections.Generic; using System.Linq.Expressions; + using Extensions.Internal; using Members; using ReadableExpressions; @@ -19,6 +20,12 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out return base.TargetCannotBeMapped(mappingData, out nullMappingBlock); } + if (HasConfiguredRootDataSources(mappingData.MapperData, out var configuredRootDataSources) && + configuredRootDataSources.Any(ds => ds.SourceMember.IsEnumerable)) + { + return base.TargetCannotBeMapped(mappingData, out nullMappingBlock); + } + nullMappingBlock = Expression.Block( ReadableExpression.Comment("No source enumerable available"), mappingData.MapperData.GetFallbackCollectionValue()); @@ -28,6 +35,11 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { + if (!context.MapperData.SourceMember.IsEnumerable) + { + yield break; + } + yield return context.RuleSet.EnumerablePopulationStrategy.GetPopulation( context.MapperData.EnumerablePopulationBuilder, context.MappingData); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index c4f9c5a93..33d6a7410 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -4,6 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; + using DataSources; using Extensions.Internal; using MapperKeys; using Members; @@ -44,7 +45,7 @@ public Expression Create(IObjectMappingData mappingData) mappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings); mappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); mappingExpressions.AddRange(GetObjectPopulation(context).WhereNotNull()); - mappingExpressions.AddRange(GetConfiguredRootSourceMemberPopulations(context)); + mappingExpressions.AddRange(GetConfiguredRootDataSourcePopulations(context)); mappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); if (NothingIsBeingMapped(mappingExpressions, mapperData)) @@ -115,43 +116,19 @@ private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperDa protected abstract IEnumerable GetObjectPopulation(MappingCreationContext context); - private IEnumerable GetConfiguredRootSourceMemberPopulations(MappingCreationContext context) + private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCreationContext context) { - if (!context.IsRoot || - !context.MapperContext.UserConfigurations.HasDataSourceFactoriesForRootTarget) - { - yield break; - } - - var configuredRootTargetDataSources = context - .MapperContext - .UserConfigurations - .GetDataSources(context.MapperData); - - if (configuredRootTargetDataSources.None()) + if (!HasConfiguredRootDataSources(context.MapperData, out var configuredRootDataSources)) { yield break; } // TODO: Test coverage for mappings with variables - foreach (var configuredRootTargetDataSource in configuredRootTargetDataSources) + foreach (var configuredRootDataSource in configuredRootDataSources) { - var newSourceMappingData = context.MappingData.WithSource(configuredRootTargetDataSource.SourceMember); - - newSourceMappingData.MapperKey = new RootObjectMapperKey( - context.RuleSet, - newSourceMappingData.MappingTypes, - new FixedMembersMembersSource( - configuredRootTargetDataSource.SourceMember, - context.MapperData.TargetMember)); - - newSourceMappingData.MapperData.SourceObject = configuredRootTargetDataSource.Value; - newSourceMappingData.MapperData.TargetInstance = context.MapperData.TargetInstance; + var newSourceContext = context.WithDataSource(configuredRootDataSource); - var newSourceContext = new MappingCreationContext(newSourceMappingData) - { - InstantiateLocalVariable = false - }; + newSourceContext.InstantiateLocalVariable = false; var memberPopulations = GetObjectPopulation(newSourceContext).WhereNotNull().ToArray(); @@ -164,15 +141,32 @@ private IEnumerable GetConfiguredRootSourceMemberPopulations(Mapping ? memberPopulations.First() : Expression.Block(memberPopulations); - if (configuredRootTargetDataSource.IsConditional) + if (configuredRootDataSource.IsConditional) { - mapping = Expression.IfThen(configuredRootTargetDataSource.Condition, mapping); + mapping = Expression.IfThen(configuredRootDataSource.Condition, mapping); } yield return mapping; } } + protected static bool HasConfiguredRootDataSources(ObjectMapperData mapperData, out IList dataSources) + { + if (!mapperData.IsRoot || + !mapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources) + { + dataSources = null; + return false; + } + + dataSources = mapperData + .MapperContext + .UserConfigurations + .GetDataSources(mapperData); + + return dataSources.Any(); + } + private static bool NothingIsBeingMapped(IList mappingExpressions, IMemberMapperData mapperData) { mappingExpressions = mappingExpressions @@ -475,6 +469,23 @@ public MappingCreationContext( public Expression MapToNullCondition { get; } public bool InstantiateLocalVariable { get; set; } + + public MappingCreationContext WithDataSource(IDataSource newDataSource) + { + var newSourceMappingData = MappingData.WithSource(newDataSource.SourceMember); + + newSourceMappingData.MapperKey = new RootObjectMapperKey( + RuleSet, + newSourceMappingData.MappingTypes, + new FixedMembersMembersSource( + newDataSource.SourceMember, + MapperData.TargetMember)); + + newSourceMappingData.MapperData.SourceObject = newDataSource.Value; + newSourceMappingData.MapperData.TargetInstance = MapperData.TargetInstance; + + return new MappingCreationContext(newSourceMappingData); + } } #endregion From 98a97ce844b64d430ece2817f646c168cd667ed9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 11:57:23 +0100 Subject: [PATCH 10/16] Replacing uses of Linq Select() and Where() --- .../CustomDataSourceTargetMemberSpecifier.cs | 6 +- AgileMapper/Api/PlanTargetSelector.cs | 4 +- .../Configuration/ConfiguredItemExtensions.cs | 5 +- .../Configuration/ConfiguredLambdaInfo.cs | 4 +- .../Configuration/DerivedTypePairSet.cs | 6 +- .../Configuration/ParametersSwapper.cs | 2 +- .../Configuration/PotentialCloneExtensions.cs | 4 +- .../Configuration/UserConfigurationSet.cs | 4 +- .../EnumerableMappingDataSource.cs | 2 +- AgileMapper/DerivedTypesCache.cs | 6 +- .../Extensions/Internal/CollectionData.cs | 10 +- .../Internal/EnumerableExtensions.cs | 31 +++++- .../Internal/ExpressionExtensions.Replace.cs | 22 ++--- .../Internal/StringExpressionExtensions.cs | 8 +- .../Extensions/Internal/TypeExtensions.cs | 10 +- AgileMapper/MappingExecutor.cs | 2 +- .../Dictionaries/DictionaryTargetMember.cs | 3 +- AgileMapper/Members/ExpressionInfoFinder.cs | 2 +- AgileMapper/Members/MemberCache.cs | 16 +-- AgileMapper/Members/MemberExtensions.cs | 2 +- .../Members/MemberMapperDataExtensions.cs | 2 +- AgileMapper/Members/NamingSettings.cs | 6 +- .../Population/MemberPopulatorFactory.cs | 5 +- AgileMapper/Members/QualifiedMember.cs | 2 +- AgileMapper/Members/SourceMemberMatcher.cs | 5 +- .../ComplexTypeConstructionFactory.cs | 8 +- .../DerivedComplexTypeMappingsFactory.cs | 8 +- .../DictionaryMappingExpressionFactory.cs | 8 +- .../EnumerablePopulationBuilder.cs | 10 +- .../SourceMemberTypeDependentKeyBase.cs | 2 +- .../MappingExpressionFactoryBase.cs | 99 +++++++++---------- .../ObjectPopulation/ObjectMapperData.cs | 2 +- AgileMapper/Plans/MappingPlan.cs | 6 +- AgileMapper/Plans/MappingPlanSet.cs | 5 +- .../ComplexTypeToNullComparisonConverter.cs | 2 +- AgileMapper/TypeConversion/ToEnumConverter.cs | 6 +- .../TypeConversion/ToStringConverter.cs | 2 +- .../Validation/EnumMappingMismatchFinder.cs | 8 +- .../Validation/EnumMappingMismatchSet.cs | 22 ++--- AgileMapper/Validation/MappingValidator.cs | 16 +-- 40 files changed, 197 insertions(+), 176 deletions(-) diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 9d5444e6b..a928b0d99 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -178,17 +178,17 @@ private static ParameterInfo GetUniqueConstructorParameterOrThrow(string var matchingParameters = typeof(TTarget) .GetPublicInstanceConstructors() - .Select(ctor => new + .Project(ctor => new { Ctor = ctor, MatchingParameters = ctor .GetParameters() - .Where(p => + .Filter(p => (ignoreParameterType || (p.ParameterType == typeof(TParam))) && (ignoreParameterName || (p.Name == name))) .ToArray() }) - .Where(d => d.MatchingParameters.Any()) + .Filter(d => d.MatchingParameters.Any()) .ToArray(); if (matchingParameters.Length == 0) diff --git a/AgileMapper/Api/PlanTargetSelector.cs b/AgileMapper/Api/PlanTargetSelector.cs index f27575997..15b2a749a 100644 --- a/AgileMapper/Api/PlanTargetSelector.cs +++ b/AgileMapper/Api/PlanTargetSelector.cs @@ -39,8 +39,8 @@ public MappingPlanSet To( _mapperContext .RuleSets .All - .Where(ruleSet => ruleSet != _mapperContext.RuleSets.Project) - .Select(rs => GetMappingPlan(rs, configurations)) + .Filter(ruleSet => ruleSet != _mapperContext.RuleSets.Project) + .Project(rs => GetMappingPlan(rs, configurations)) .ToArray()); } diff --git a/AgileMapper/Configuration/ConfiguredItemExtensions.cs b/AgileMapper/Configuration/ConfiguredItemExtensions.cs index 6618bb59b..e658363fc 100644 --- a/AgileMapper/Configuration/ConfiguredItemExtensions.cs +++ b/AgileMapper/Configuration/ConfiguredItemExtensions.cs @@ -2,11 +2,12 @@ { using System.Collections.Generic; using System.Linq; + using Extensions.Internal; using Members; internal static class ConfiguredItemExtensions { - public static TItem FindMatch(this IEnumerable items, IBasicMapperData mapperData) + public static TItem FindMatch(this IList items, IBasicMapperData mapperData) where TItem : UserConfiguredItemBase { return items?.FirstOrDefault(item => item.AppliesTo(mapperData)); @@ -15,7 +16,7 @@ public static TItem FindMatch(this IEnumerable items, IBasicMapper public static IEnumerable FindMatches(this IEnumerable items, IBasicMapperData mapperData) where TItem : UserConfiguredItemBase { - return items?.Where(item => item.AppliesTo(mapperData)).OrderBy(item => item) ?? Enumerable.Empty; + return items?.Filter(item => item.AppliesTo(mapperData)).OrderBy(item => item) ?? Enumerable.Empty; } } } \ No newline at end of file diff --git a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs index ea9e569d0..230df5244 100644 --- a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs +++ b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs @@ -30,7 +30,7 @@ private ConfiguredLambdaInfo( public static ConfiguredLambdaInfo For(LambdaExpression lambda) { - var funcArguments = lambda.Parameters.Select(p => p.Type).ToArray(); + var funcArguments = lambda.Parameters.Project(p => p.Type).ToArray(); var contextTypes = GetContextTypes(funcArguments); var parameterSwapper = ParametersSwapper.For(contextTypes, funcArguments); @@ -108,7 +108,7 @@ private static ConfiguredLambdaInfo For( return null; } - var parameters = funcArguments.Select(Parameters.Create).ToArray(); + var parameters = funcArguments.Project(Parameters.Create).ToArray(); var valueFactory = func.ToConstantExpression(); var valueFactoryInvocation = Expression.Invoke(valueFactory, parameters.Cast()); var valueFactoryLambda = Expression.Lambda(funcType, valueFactoryInvocation, parameters); diff --git a/AgileMapper/Configuration/DerivedTypePairSet.cs b/AgileMapper/Configuration/DerivedTypePairSet.cs index a888174b7..0bed3f42c 100644 --- a/AgileMapper/Configuration/DerivedTypePairSet.cs +++ b/AgileMapper/Configuration/DerivedTypePairSet.cs @@ -74,7 +74,7 @@ public IList GetDerivedTypePairsFor( if (_typePairsByTargetType.TryGetValue(mapperData.TargetType, out var typePairs)) { - return typePairs.Where(tp => tp.AppliesTo(mapperData)).ToArray(); + return typePairs.Filter(tp => tp.AppliesTo(mapperData)).ToArray(); } return Enumerable.EmptyArray; @@ -82,7 +82,7 @@ public IList GetDerivedTypePairsFor( #region Auto-Registration - private void LookForDerivedTypePairs(IBasicMapperData mapperData, MapperContext mapperContext) + private void LookForDerivedTypePairs(ITypePair mapperData, MapperContext mapperContext) { var rootSourceType = GetRootType(mapperData.SourceType); var rootTargetType = GetRootType(mapperData.TargetType); @@ -126,7 +126,7 @@ private void LookForDerivedTypePairs(IBasicMapperData mapperData, MapperContext } var candidatePairsData = derivedSourceTypes - .Select(t => new + .Project(t => new { DerivedSourceType = t, DerivedTargetTypeName = derivedTargetTypeNameFactory.Invoke(t) diff --git a/AgileMapper/Configuration/ParametersSwapper.cs b/AgileMapper/Configuration/ParametersSwapper.cs index c6a734cab..d58bb47f8 100644 --- a/AgileMapper/Configuration/ParametersSwapper.cs +++ b/AgileMapper/Configuration/ParametersSwapper.cs @@ -160,7 +160,7 @@ private static Expression ReplaceParameters( { var contextInfo = GetAppropriateMappingContext(swapArgs); - return swapArgs.Lambda.ReplaceParametersWith(parameterFactories.Select(f => f.Invoke(contextInfo)).ToArray()); + return swapArgs.Lambda.ReplaceParametersWith(parameterFactories.Project(f => f.Invoke(contextInfo)).ToArray()); } private static MappingContextInfo GetAppropriateMappingContext(SwapArgs swapArgs) diff --git a/AgileMapper/Configuration/PotentialCloneExtensions.cs b/AgileMapper/Configuration/PotentialCloneExtensions.cs index 4d92597ef..95b72119b 100644 --- a/AgileMapper/Configuration/PotentialCloneExtensions.cs +++ b/AgileMapper/Configuration/PotentialCloneExtensions.cs @@ -29,8 +29,8 @@ public static void AddSortFilter(this List cloneableItems, T newItem) } var replacedItem = cloneableItems - .Where(item => item.IsClone) - .Select((item, index) => new { Item = item, Index = index }) + .Filter(item => item.IsClone) + .Project((item, index) => new { Item = item, Index = index }) .FirstOrDefault(d => newItem.IsReplacementFor(d.Item)); if (replacedItem != null) diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index 7095e5a86..d0d6e5be6 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -155,7 +155,7 @@ private List EnumPairings public void Add(EnumMemberPair enumPairing) => EnumPairings.Add(enumPairing); public IEnumerable GetEnumPairingsFor(Type sourceEnumType, Type targetEnumType) - => _enumPairings?.Where(ep => ep.IsFor(sourceEnumType, targetEnumType)) ?? Enumerable.Empty; + => _enumPairings?.Filter(ep => ep.IsFor(sourceEnumType, targetEnumType)) ?? Enumerable.Empty; #endregion @@ -189,7 +189,7 @@ public void Add(ConfiguredDataSourceFactory dataSourceFactory) public bool HasConfiguredRootDataSources { get; private set; } public IList GetDataSources(IMemberMapperData mapperData) - => QueryDataSourceFactories(mapperData).Select(dsf => dsf.Create(mapperData)).ToArray(); + => QueryDataSourceFactories(mapperData).Project(dsf => dsf.Create(mapperData)).ToArray(); public IEnumerable QueryDataSourceFactories(IBasicMapperData mapperData) => _dataSourceFactories?.FindMatches(mapperData) ?? Enumerable.Empty; diff --git a/AgileMapper/DataSources/EnumerableMappingDataSource.cs b/AgileMapper/DataSources/EnumerableMappingDataSource.cs index 412d6a4ea..f81ff28a9 100644 --- a/AgileMapper/DataSources/EnumerableMappingDataSource.cs +++ b/AgileMapper/DataSources/EnumerableMappingDataSource.cs @@ -95,7 +95,7 @@ private static bool IsNotMappingFromLinkingType( } var otherComplexTypeMembers = sourceElementMembers - .Where(m => m.IsComplex && m.Type != mapperData.SourceType) + .Filter(m => m.IsComplex && m.Type != mapperData.SourceType) .ToArray(); if (otherComplexTypeMembers.Length != 1) diff --git a/AgileMapper/DerivedTypesCache.cs b/AgileMapper/DerivedTypesCache.cs index 785468ae0..e9b37c7a4 100644 --- a/AgileMapper/DerivedTypesCache.cs +++ b/AgileMapper/DerivedTypesCache.cs @@ -58,7 +58,7 @@ private IList GetDerivedTypesForType(Type type) .SelectMany(assembly => _typesByAssembly .GetOrAdd(assembly, GetRelevantTypesFromAssembly)); - var derivedTypes = assemblyTypes.Where(t => t.IsDerivedFrom(type)).ToArray(); + var derivedTypes = assemblyTypes.Filter(t => t.IsDerivedFrom(type)).ToArray(); if (derivedTypes.None()) { @@ -75,7 +75,7 @@ private IList GetDerivedTypesForType(Type type) private static IEnumerable GetRelevantTypesFromAssembly(Assembly assembly) { return QueryTypesFromAssembly(assembly) - .Where(t => t.IsClass() && !t.IsAbstract()) + .Filter(t => t.IsClass() && !t.IsAbstract()) .ToArray(); } @@ -87,7 +87,7 @@ private static IEnumerable QueryTypesFromAssembly(Assembly assembly) if (Constants.ReflectionNotPermitted) { - types = types.Where(t => t.IsPublic()); + types = types.Filter(t => t.IsPublic()); } return types; diff --git a/AgileMapper/Extensions/Internal/CollectionData.cs b/AgileMapper/Extensions/Internal/CollectionData.cs index 6f9a9572c..572967f92 100644 --- a/AgileMapper/Extensions/Internal/CollectionData.cs +++ b/AgileMapper/Extensions/Internal/CollectionData.cs @@ -85,20 +85,18 @@ public static CollectionData Create( return new CollectionData(absentTargetItems, intersection, newSourceItems); } - private static Dictionary> GetItemsById( - IEnumerable items, - Func idFactory) + private static Dictionary> GetItemsById(IEnumerable items, Func idFactory) { return items .WhereNotNull() - .Select(item => new + .Project(item => new { Id = idFactory.Invoke(item), Item = item }) - .Where(d => d.Id != null) + .Filter(d => d.Id != null) .GroupBy(d => d.Id) - .ToDictionary(grp => grp.Key, grp => grp.Select(d => d.Item).ToList()); + .ToDictionary(grp => grp.Key, grp => grp.Project(d => d.Item).ToList()); } } diff --git a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index 868c1f319..ae3e9dbab 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -23,6 +23,35 @@ public static void AddUnlessNullOrEmpty(this ICollection items, Expr } } + public static IEnumerable Project(this IEnumerable items, Func projector) + { + foreach (var item in items) + { + yield return projector.Invoke(item); + } + } + + public static IEnumerable Project(this IEnumerable items, Func projector) + { + var index = 0; + + foreach (var item in items) + { + yield return projector.Invoke(item, index++); + } + } + + public static IEnumerable Filter(this IEnumerable items, Func predicate) + { + foreach (var item in items) + { + if (predicate.Invoke(item)) + { + yield return item; + } + } + } + [DebuggerStepThrough] public static T First(this IList items) => items[0]; @@ -177,7 +206,7 @@ public static T[] ToArray(this ICollection items) } [DebuggerStepThrough] - public static IEnumerable WhereNotNull(this IEnumerable items) => items.Where(item => item != null); + public static IEnumerable WhereNotNull(this IEnumerable items) => items.Filter(item => item != null); public static T[] Prepend(this IList items, T initialItem) { diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs index 49adb7c01..38fc57487 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs @@ -18,7 +18,7 @@ public static Expression ReplaceParametersWith(this LambdaExpression lambda, par var replacementsByParameter = lambda .Parameters - .Select((p, i) => new { Parameter = p, Replacement = replacements[i] }) + .Project((p, i) => new { Parameter = p, Replacement = replacements[i] }) .ToDictionary(d => (Expression)d.Parameter, d => d.Replacement); return lambda.Body.Replace(replacementsByParameter); @@ -231,7 +231,7 @@ private Expression ReplaceIn(BlockExpression block) { return ReplaceIn( block, - b => b.Update(b.Variables.Select(ReplaceIn), b.Expressions.Select(Replace))); + b => b.Update(b.Variables.Project(ReplaceIn), b.Expressions.Project(Replace))); } private Expression ReplaceIn(MethodCallExpression call) @@ -242,7 +242,7 @@ private Expression ReplaceIn(MethodCallExpression call) private Expression ReplaceIn(GotoExpression @goto) => ReplaceIn(@goto, gt => gt.Update(gt.Target, Replace(gt.Value))); private Expression ReplaceIn(IndexExpression indexAccess) - => ReplaceIn(indexAccess, idx => idx.Update(Replace(idx.Object), idx.Arguments.Select(Replace))); + => ReplaceIn(indexAccess, idx => idx.Update(Replace(idx.Object), idx.Arguments.Project(Replace))); private Expression ReplaceIn(InvocationExpression invocation) => ReplaceIn(invocation, inv => ReplaceInCall(inv.Expression, inv.Arguments, inv.Update)); @@ -252,19 +252,19 @@ private Expression ReplaceInCall( IEnumerable arguments, Func, Expression> replacer) { - return replacer.Invoke(Replace(subject), arguments.Select(Replace).ToArray()); + return replacer.Invoke(Replace(subject), arguments.Project(Replace).ToArray()); } private Expression ReplaceIn(LabelExpression label) => ReplaceIn(label, l => l.Update(l.Target, Replace(l.DefaultValue))); private Expression ReplaceIn(LambdaExpression lambda) - => ReplaceIn(lambda, l => Expression.Lambda(l.Type, Replace(l.Body), l.Parameters.Select(ReplaceIn))); + => ReplaceIn(lambda, l => Expression.Lambda(l.Type, Replace(l.Body), l.Parameters.Project(ReplaceIn))); private Expression ReplaceIn(MemberExpression memberAccess) => ReplaceIn(memberAccess, ma => ma.Update(Replace(ma.Expression))); private Expression ReplaceIn(MemberInitExpression memberInit) - => ReplaceIn(memberInit, mi => mi.Update(ReplaceInNew(mi.NewExpression), mi.Bindings.Select(ReplaceIn))); + => ReplaceIn(memberInit, mi => mi.Update(ReplaceInNew(mi.NewExpression), mi.Bindings.Project(ReplaceIn))); private Expression ReplaceIn(ListInitExpression listInit) => ReplaceIn(listInit, li => li.Update(ReplaceInNew(li.NewExpression), ReplaceIn(li.Initializers))); @@ -285,14 +285,14 @@ private MemberBinding ReplaceIn(MemberBinding binding) case MemberBindingType.MemberBinding: var memberBinding = (MemberMemberBinding)binding; - return memberBinding.Update(memberBinding.Bindings.Select(ReplaceIn)); + return memberBinding.Update(memberBinding.Bindings.Project(ReplaceIn)); } throw new ArgumentOutOfRangeException(); } private IEnumerable ReplaceIn(IEnumerable initializers) - => initializers.Select(init => init.Update(init.Arguments.Select(Replace))); + => initializers.Project(init => init.Update(init.Arguments.Project(Replace))); private Expression ReplaceIn(NewExpression newing) => ReplaceIn(newing, ReplaceInNew); @@ -300,10 +300,10 @@ private NewExpression ReplaceInNew(NewExpression newing) { return newing.Arguments.None() ? newing - : newing.Update(newing.Arguments.Select(Replace)); + : newing.Update(newing.Arguments.Project(Replace)); } - private Expression ReplaceIn(NewArrayExpression newArray) => ReplaceIn(newArray, na => na.Update(na.Expressions.Select(Replace))); + private Expression ReplaceIn(NewArrayExpression newArray) => ReplaceIn(newArray, na => na.Update(na.Expressions.Project(Replace))); private ParameterExpression ReplaceIn(ParameterExpression parameter) => (ParameterExpression)ReplaceIn(parameter, p => p); @@ -315,7 +315,7 @@ private Expression ReplaceIn(TryExpression @try) { return ReplaceIn( @try, - t => t.Update(Replace(t.Body), t.Handlers.Select(ReplaceIn), Replace(t.Finally), Replace(t.Fault))); + t => t.Update(Replace(t.Body), t.Handlers.Project(ReplaceIn), Replace(t.Finally), Replace(t.Fault))); } private CatchBlock ReplaceIn(CatchBlock @catch) diff --git a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs index 8379fd125..ff9554562 100644 --- a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs @@ -16,8 +16,8 @@ static StringExpressionExtensions() { var stringMethods = typeof(string) .GetPublicStaticMethods() - .Where(m => m.Name == "Join" || m.Name == "Concat") - .Select(m => new + .Filter(m => m.Name == "Join" || m.Name == "Concat") + .Project(m => new { Method = m, Parameters = m.GetParameters(), @@ -32,9 +32,9 @@ static StringExpressionExtensions() (m.Parameters[1].ParameterType == typeof(string[]))).Method; _stringConcatMethods = stringMethods - .Where(m => (m.Method.Name == "Concat") && (m.FirstParameterType == typeof(string))) + .Filter(m => (m.Method.Name == "Concat") && (m.FirstParameterType == typeof(string))) .OrderBy(m => m.Parameters.Length) - .Select(m => m.Method) + .Project(m => m.Method) .ToArray(); } diff --git a/AgileMapper/Extensions/Internal/TypeExtensions.cs b/AgileMapper/Extensions/Internal/TypeExtensions.cs index b105e3ae2..92f254978 100644 --- a/AgileMapper/Extensions/Internal/TypeExtensions.cs +++ b/AgileMapper/Extensions/Internal/TypeExtensions.cs @@ -21,7 +21,7 @@ public static string GetShortVariableName(this Type type) variableName[0] + string.Join( string.Empty, - variableName.ToCharArray().Skip(1).Where(char.IsUpper)); + variableName.ToCharArray().Skip(1).Filter(char.IsUpper)); shortVariableName = shortVariableName.ToLowerInvariant(); @@ -78,7 +78,7 @@ private static string GetGenericTypeVariableName(string variableName, Type namin variableName += string.Join( string.Empty, - genericTypeArguments.Select(arg => "_" + arg.GetVariableNameInPascalCase())); + genericTypeArguments.Project(arg => "_" + arg.GetVariableNameInPascalCase())); return variableName; } @@ -209,8 +209,8 @@ public static Type[] GetCoercibleNumericTypes(this Type numericType) return Constants .NumericTypeMaxValuesByType - .Where(kvp => kvp.Value < typeMaxValue) - .Select(kvp => kvp.Key) + .Filter(kvp => kvp.Value < typeMaxValue) + .Project(kvp => kvp.Key) .ToArray(); } @@ -258,7 +258,7 @@ private static double GetValueFor( } private static IEnumerable GetEnumValues(Type enumType) - => Enum.GetValues(enumType).Cast().Select(Convert.ToInt64); + => Enum.GetValues(enumType).Cast().Project(Convert.ToInt64); public static bool StartsWith(this string value, char character) => value[0] == character; diff --git a/AgileMapper/MappingExecutor.cs b/AgileMapper/MappingExecutor.cs index 034245065..ae765a644 100644 --- a/AgileMapper/MappingExecutor.cs +++ b/AgileMapper/MappingExecutor.cs @@ -168,7 +168,7 @@ string IFlatteningSelector.ToQueryString( var queryString = string.Join( "&", - flattened.Select(kvp => Uri.EscapeDataString(kvp.Key) + "=" + Uri.EscapeDataString(kvp.Value))); + flattened.Project(kvp => Uri.EscapeDataString(kvp.Key) + "=" + Uri.EscapeDataString(kvp.Value))); return queryString.Replace(".", "%2E"); } diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 5e78ba9d3..93e05d103 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -2,7 +2,6 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Dynamic; - using System.Linq; using System.Linq.Expressions; using Extensions.Internal; using NetStandardPolyfills; @@ -329,7 +328,7 @@ private static Expression GetCheckedTryCatch( var updatedCatchHandlers = tryCatchValue .Handlers - .Select(handler => handler.Update( + .Project(handler => handler.Update( handler.Variable, handler.Filter.Replace(replacements), handler.Body.Replace(replacements))); diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 5822dd310..db0ff845c 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -80,7 +80,7 @@ private Expression GetNestedAccessChecks() return _nestedAccessesByPath .Values .Reverse() - .Select(GetAccessCheck) + .Project(GetAccessCheck) .Aggregate( default(Expression), (accessChecksSoFar, accessCheck) => (accessChecksSoFar != null) diff --git a/AgileMapper/Members/MemberCache.cs b/AgileMapper/Members/MemberCache.cs index ed6ac7d6d..3df441290 100644 --- a/AgileMapper/Members/MemberCache.cs +++ b/AgileMapper/Members/MemberCache.cs @@ -45,13 +45,13 @@ public IList GetTargetMembers(Type targetType) var constructorParameterNames = key.Type .GetPublicInstanceConstructors() - .SelectMany(ctor => ctor.GetParameters().Select(p => p.Name)) + .SelectMany(ctor => ctor.GetParameters().Project(p => p.Name)) .Distinct() .ToArray(); var fieldsAndProperties = fields .Concat(properties) - .Select(m => + .Project(m => { m.HasMatchingCtorParameter = constructorParameterNames.Contains(m.Name, OrdinalIgnoreCase); return m; @@ -68,8 +68,8 @@ private static IEnumerable GetFields(Type targetType, Func true; @@ -82,8 +82,8 @@ private static IEnumerable GetProperties(Type targetType, Func true; @@ -101,8 +101,8 @@ private static IEnumerable GetMethods( { return targetType .GetPublicInstanceMethods() - .Where(filter) - .Select(memberFactory); + .Filter(filter) + .Project(memberFactory); } private static readonly string[] _methodsToIgnore = { "GetHashCode", "GetType" }; diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 1a31d2903..6bf71a56d 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -16,7 +16,7 @@ internal static class MemberExtensions { public static string GetFullName(this IEnumerable members) - => string.Join(string.Empty, members.Select(m => m.JoiningName)); + => string.Join(string.Empty, members.Project(m => m.JoiningName)); public static string GetFriendlySourcePath(this IQualifiedMember sourceMember, IMemberMapperData rootMapperData) => GetFriendlyMemberPath(sourceMember, rootMapperData.SourceMember); diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index ec02926f7..a2a1301cd 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -271,7 +271,7 @@ private static bool TargetMemberIsRecursionWithin( var nonSimpleChildMembers = GlobalContext.Instance .MemberCache .GetTargetMembers(parentMember.Type) - .Where(m => !m.IsSimple && !checkedTypes.Contains(m.IsEnumerable ? m.ElementType : m.Type)) + .Filter(m => !m.IsSimple && !checkedTypes.Contains(m.IsEnumerable ? m.ElementType : m.Type)) .ToArray(); if (nonSimpleChildMembers.None()) diff --git a/AgileMapper/Members/NamingSettings.cs b/AgileMapper/Members/NamingSettings.cs index 421b32297..b28ac7f78 100644 --- a/AgileMapper/Members/NamingSettings.cs +++ b/AgileMapper/Members/NamingSettings.cs @@ -54,10 +54,10 @@ private static string GetGetOrSetMethodName(Member member) } public void AddNamePrefixes(IEnumerable prefixes) - => AddNameMatchers(prefixes.Select(p => "^" + p + "(.+)$").ToArray()); + => AddNameMatchers(prefixes.Project(p => "^" + p + "(.+)$").ToArray()); public void AddNameSuffixes(IEnumerable suffixes) - => AddNameMatchers(suffixes.Select(s => "^(.+)" + s + "$").ToArray()); + => AddNameMatchers(suffixes.Project(s => "^(.+)" + s + "$").ToArray()); public void AddNameMatchers(IList patterns) { @@ -193,7 +193,7 @@ private bool IsIdentifier(Member member) potentialIds.InsertRange(0, new[] { "Id", "Identifier" }); return _customNameMatchers - .Select(customNameMatcher => customNameMatcher.Match(member.Name)) + .Project(customNameMatcher => customNameMatcher.Match(member.Name)) .Any(memberNameMatch => memberNameMatch.Success && potentialIds.Contains(GetMemberName(memberNameMatch))); diff --git a/AgileMapper/Members/Population/MemberPopulatorFactory.cs b/AgileMapper/Members/Population/MemberPopulatorFactory.cs index e16cd5d43..99ff1b953 100644 --- a/AgileMapper/Members/Population/MemberPopulatorFactory.cs +++ b/AgileMapper/Members/Population/MemberPopulatorFactory.cs @@ -2,7 +2,6 @@ namespace AgileObjects.AgileMapper.Members.Population { using System; using System.Collections.Generic; - using System.Linq; using System.Linq.Expressions; using Configuration; using DataSources.Finders; @@ -16,7 +15,7 @@ internal class MemberPopulatorFactory GlobalContext.Instance .MemberCache .GetTargetMembers(mapperData.TargetType) - .Select(tm => mapperData.TargetMember.Append(tm))); + .Project(tm => mapperData.TargetMember.Append(tm))); private readonly Func> _targetMembersFactory; @@ -29,7 +28,7 @@ public IEnumerable Create(IObjectMappingData mappingData) { return _targetMembersFactory .Invoke(mappingData.MapperData) - .Select(tm => + .Project(tm => { var memberPopulation = Create(tm, mappingData); diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index b601f5008..9b48c0094 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -130,7 +130,7 @@ public static QualifiedMember From(Member member, MapperContext mapperContext) public static QualifiedMember From(Member[] memberChain, MapperContext mapperContext) { var matchingNameSets = memberChain - .Select(mapperContext.Naming.GetMatchingNamesFor) + .Project(mapperContext.Naming.GetMatchingNamesFor) .ToArray(); var joinedNames = mapperContext.Naming.GetJoinedNamesFor(matchingNameSets); diff --git a/AgileMapper/Members/SourceMemberMatcher.cs b/AgileMapper/Members/SourceMemberMatcher.cs index b5ac9d413..bcc8ecdd0 100644 --- a/AgileMapper/Members/SourceMemberMatcher.cs +++ b/AgileMapper/Members/SourceMemberMatcher.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Extensions.Internal; internal static class SourceMemberMatcher { @@ -119,11 +120,11 @@ private static IEnumerable QuerySourceMembers( .Instance .MemberCache .GetSourceMembers(parentMember.Type) - .Where(m => filter.Invoke(mappingData, m)); + .Filter(m => filter.Invoke(mappingData, m)); return mappingData.RuleSet.Settings.AllowGetMethods ? members - : members.Where(m => m.MemberType != MemberType.GetMethod); + : members.Filter(m => m.MemberType != MemberType.GetMethod); } private static IQualifiedMember GetFinalSourceMember( diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index d59e74089..853e77d6e 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -102,9 +102,9 @@ private static void AddNewingConstruction(ICollection construction var greediestAvailableConstructor = constructors.Any() ? constructors - .Where(IsNotCopyConstructor) - .Select(ctor => CreateConstructorData(ctor, key)) - .Where(ctor => ctor.CanBeConstructed) + .Filter(IsNotCopyConstructor) + .Project(ctor => CreateConstructorData(ctor, key)) + .Filter(ctor => ctor.CanBeConstructed) .OrderByDescending(ctor => ctor.NumberOfParameters) .FirstOrDefault() : null; @@ -143,7 +143,7 @@ private static ConstructorData CreateConstructorData(ConstructorInfo ctor, Const var ctorData = new ConstructorData( ctor, ctor.GetParameters() - .Select(p => + .Project(p => { var parameterMapperData = new ChildMemberMapperData( mapperData.TargetMember.Append(Member.ConstructorParameter(p)), diff --git a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs index 7cc8eb4ba..368fa7b28 100644 --- a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs @@ -178,13 +178,13 @@ private static void AddDerivedSourceTypeMappings( var groupedTypePairs = derivedTypePairs .GroupBy(tp => tp.DerivedTargetType) - .Select(group => new TypePairGroup(group)) + .Project(group => new TypePairGroup(group)) .OrderBy(tp => tp.DerivedTargetType, TypeComparer.MostToLeastDerived) .ToArray(); var unconditionalDerivedTargetTypeMapping = groupedTypePairs - .Where(tpg => tpg.TypePairs.None(tp => tp.HasConfiguredCondition)) - .Select(tpg => new + .Filter(tpg => tpg.TypePairs.None(tp => tp.HasConfiguredCondition)) + .Project(tpg => new { tpg.DerivedTargetType, TypePairsCondition = GetTargetValidCheckOrNull(tpg.DerivedTargetType, declaredTypeMapperData) @@ -337,7 +337,7 @@ private static Expression GetTypePairsCondition( IMemberMapperData mapperData) { var conditionalPairs = derivedTypePairs - .Where(pair => pair.HasConfiguredCondition) + .Filter(pair => pair.HasConfiguredCondition) .ToArray(); var pairConditions = conditionalPairs.Chain( diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 746f53f9b..63c83292a 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -35,7 +35,7 @@ private static IEnumerable GetAllTargetMembers(ObjectMapperData var configuredDataSourceFactories = mapperData.MapperContext .UserConfigurations .QueryDataSourceFactories() - .Where(dsf => dsf.IsFor(mapperData)) + .Filter(dsf => dsf.IsFor(mapperData)) .ToArray(); if (configuredDataSourceFactories.None()) @@ -208,7 +208,7 @@ private static DictionaryTargetMember[] GetConfiguredTargetMembers( { return configuredDataSourceFactories .GroupBy(dsf => dsf.TargetDictionaryEntryMember.Name) - .Select(group => + .Project(group => { var factory = group.First(); var targetMember = factory.TargetDictionaryEntryMember; @@ -368,7 +368,7 @@ private static ConstructorInfo FindDictionaryConstructor( return dictionaryType .GetPublicInstanceConstructors() - .Select(ctor => new { Ctor = ctor, Parameters = ctor.GetParameters() }) + .Project(ctor => new { Ctor = ctor, Parameters = ctor.GetParameters() }) .First(ctor => (ctor.Parameters.Length == numberOfParameters) && (ctor.Parameters[0].ParameterType == firstParameterType)) @@ -460,7 +460,7 @@ private Expression GetDictionaryPopulation(IObjectMappingData mappingData) var memberPopulations = _memberPopulatorFactory .Create(mappingData) - .Select(memberPopulation => memberPopulation.GetPopulation()) + .Project(memberPopulation => memberPopulation.GetPopulation()) .ToArray(); if (memberPopulations.None()) diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index fe7c146e2..e8ddb2e53 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -48,14 +48,14 @@ public EnumerablePopulationBuilder(ObjectMapperData mapperData) static EnumerablePopulationBuilder() { var linqSelectMethods = typeof(Enumerable) - .GetPublicStaticMethods("Select") - .Select(m => new + .GetPublicStaticMethods(nameof(Enumerable.Select)) + .Project(m => new { Method = m, Parameters = m.GetParameters() }) - .Where(m => m.Parameters.Length == 2) - .Select(m => new + .Filter(m => m.Parameters.Length == 2) + .Project(m => new { m.Method, ProjectionLambdaParameterCount = m.Parameters[1].ParameterType.GetGenericTypeArguments().Length @@ -69,7 +69,7 @@ static EnumerablePopulationBuilder() .First(m => m.ProjectionLambdaParameterCount == 3).Method; _queryableSelectMethod = typeof(Queryable) - .GetPublicStaticMethods("Select") + .GetPublicStaticMethods(nameof(Enumerable.Select)) .First(m => (m.GetParameters().Length == 2) && (m.GetParameters()[1].ParameterType.GetGenericTypeArguments()[0].GetGenericTypeArguments().Length == 2)); diff --git a/AgileMapper/ObjectPopulation/MapperKeys/SourceMemberTypeDependentKeyBase.cs b/AgileMapper/ObjectPopulation/MapperKeys/SourceMemberTypeDependentKeyBase.cs index b8afb16c2..a3cab72e0 100644 --- a/AgileMapper/ObjectPopulation/MapperKeys/SourceMemberTypeDependentKeyBase.cs +++ b/AgileMapper/ObjectPopulation/MapperKeys/SourceMemberTypeDependentKeyBase.cs @@ -32,7 +32,7 @@ public void AddSourceMemberTypeTesterIfRequired(IObjectMappingData mappingData = .MapperData .DataSourcesByTargetMember .Values - .Select(dataSourceSet => dataSourceSet.SourceMemberTypeTest) + .Project(dataSourceSet => dataSourceSet.SourceMemberTypeTest) .WhereNotNull() .ToArray(); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 33d6a7410..0c0cf2245 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -40,22 +40,21 @@ public Expression Create(IObjectMappingData mappingData) } var context = GetCreationContext(mappingData); - var mappingExpressions = new List(); - mappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings); - mappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); - mappingExpressions.AddRange(GetObjectPopulation(context).WhereNotNull()); - mappingExpressions.AddRange(GetConfiguredRootDataSourcePopulations(context)); - mappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); + context.MappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings); + context.MappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); + context.MappingExpressions.AddRange(GetObjectPopulation(context).WhereNotNull()); + context.MappingExpressions.AddRange(GetConfiguredRootDataSourcePopulations(context)); + context.MappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); - if (NothingIsBeingMapped(mappingExpressions, mapperData)) + if (NothingIsBeingMapped(context)) { return mapperData.IsEntryPoint ? mapperData.TargetObject : Constants.EmptyExpression; } - mappingExpressions.InsertRange(0, GetShortCircuitReturns(returnNull, mappingData)); + context.MappingExpressions.InsertRange(0, GetShortCircuitReturns(returnNull, mappingData)); - var mappingBlock = GetMappingBlock(mappingExpressions, context, mapperData); + var mappingBlock = GetMappingBlock(context); if (mapperData.Context.UseMappingTryCatch) { @@ -167,10 +166,11 @@ protected static bool HasConfiguredRootDataSources(ObjectMapperData mapperData, return dataSources.Any(); } - private static bool NothingIsBeingMapped(IList mappingExpressions, IMemberMapperData mapperData) + private static bool NothingIsBeingMapped(MappingCreationContext context) { - mappingExpressions = mappingExpressions - .Where(IsMemberMapping) + var mappingExpressions = context + .MappingExpressions + .Filter(IsMemberMapping) .ToList(); if (mappingExpressions.None()) @@ -195,7 +195,7 @@ private static bool NothingIsBeingMapped(IList mappingExpressions, I return false; } - if (assignedValue == mapperData.TargetObject) + if (assignedValue == context.MapperData.TargetObject) { return true; } @@ -204,7 +204,7 @@ private static bool NothingIsBeingMapped(IList mappingExpressions, I { var valueCoalesce = (BinaryExpression)assignedValue; - if ((valueCoalesce.Left == mapperData.TargetObject) && + if ((valueCoalesce.Left == context.MapperData.TargetObject) && (valueCoalesce.Right.NodeType == New)) { var objectNewing = (NewExpression)valueCoalesce.Right; @@ -249,14 +249,13 @@ private static bool IsMapRecursionCall(Expression expression) IsCallTo(expression, nameof(IObjectMappingDataUntyped.MapRecursion)); } - private Expression GetMappingBlock( - IList mappingExpressions, - MappingCreationContext mappingCreationContext, - ObjectMapperData mapperData) + private Expression GetMappingBlock(MappingCreationContext context) { + var mappingExpressions = context.MappingExpressions; + AdjustForSingleExpressionBlockIfApplicable(ref mappingExpressions); - if (mapperData.UseSingleMappingExpression()) + if (context.MapperData.UseSingleMappingExpression()) { return mappingExpressions.First(); } @@ -272,41 +271,33 @@ private Expression GetMappingBlock( { if (mappingExpressions[0].NodeType == MemberAccess) { - return GetReturnExpression(mappingExpressions[0], mappingCreationContext); + return GetReturnExpression(mappingExpressions[0], context); } - if (TryAdjustForUnusedLocalVariableIfApplicable( - mappingExpressions, - mappingCreationContext, - mapperData, - out returnExpression)) + if (TryAdjustForUnusedLocalVariableIfApplicable(context, out returnExpression)) { return returnExpression; } } - else if (TryAdjustForUnusedLocalVariableIfApplicable( - mappingExpressions, - mappingCreationContext, - mapperData, - out returnExpression)) + else if (TryAdjustForUnusedLocalVariableIfApplicable(context, out returnExpression)) { return returnExpression; } CreateFullMappingBlock: - returnExpression = GetReturnExpression(GetReturnValue(mapperData), mappingCreationContext); + returnExpression = GetReturnExpression(GetReturnValue(context.MapperData), context); - mappingExpressions.Add(mapperData.GetReturnLabel(returnExpression)); + mappingExpressions.Add(context.MapperData.GetReturnLabel(returnExpression)); - var mappingBlock = mapperData.Context.UseLocalVariable - ? Expression.Block(new[] { mapperData.LocalVariable }, mappingExpressions) + var mappingBlock = context.MapperData.Context.UseLocalVariable + ? Expression.Block(new[] { context.MapperData.LocalVariable }, mappingExpressions) : Expression.Block(mappingExpressions); return mappingBlock; } - private static void AdjustForSingleExpressionBlockIfApplicable(ref IList mappingExpressions) + private static void AdjustForSingleExpressionBlockIfApplicable(ref List mappingExpressions) { if (!mappingExpressions.HasOne() || (mappingExpressions[0].NodeType != Block)) { @@ -321,26 +312,22 @@ private static void AdjustForSingleExpressionBlockIfApplicable(ref IList mappingExpressions, - MappingCreationContext context, - ObjectMapperData mapperData, - out Expression returnExpression) + private static bool TryAdjustForUnusedLocalVariableIfApplicable(MappingCreationContext context, out Expression returnExpression) { - if (!mapperData.Context.UseLocalVariable) + if (!context.MapperData.Context.UseLocalVariable) { returnExpression = null; return false; } - if (!mappingExpressions.TryGetVariableAssignment(out var localVariableAssignment)) + if (!context.MappingExpressions.TryGetVariableAssignment(out var localVariableAssignment)) { returnExpression = null; return false; } if ((localVariableAssignment.Left.NodeType != Parameter) || - (localVariableAssignment != mappingExpressions.Last())) + (localVariableAssignment != context.MappingExpressions.Last())) { returnExpression = null; return false; @@ -354,13 +341,13 @@ private static bool TryAdjustForUnusedLocalVariableIfApplicable( GetReturnExpression(localVariableAssignment, context)) : GetReturnExpression(assignedValue, context); - if (mappingExpressions.HasOne()) + if (context.MappingExpressions.HasOne()) { return true; } - mappingExpressions[mappingExpressions.Count - 1] = mapperData.GetReturnLabel(returnExpression); - returnExpression = Expression.Block(mappingExpressions); + context.MappingExpressions[context.MappingExpressions.Count - 1] = context.MapperData.GetReturnLabel(returnExpression); + returnExpression = Expression.Block(context.MappingExpressions); return true; } @@ -434,8 +421,11 @@ public virtual void Reset() internal class MappingCreationContext { - public MappingCreationContext(IObjectMappingData mappingData, Expression mapToNullCondition = null) - : this(mappingData, null, null, mapToNullCondition) + public MappingCreationContext( + IObjectMappingData mappingData, + Expression mapToNullCondition = null, + List mappingExpressions = null) + : this(mappingData, null, null, mapToNullCondition, mappingExpressions) { } @@ -443,13 +433,15 @@ public MappingCreationContext( IObjectMappingData mappingData, Expression preMappingCallback, Expression postMappingCallback, - Expression mapToNullCondition) + Expression mapToNullCondition, + List mappingExpressions = null) { MappingData = mappingData; PreMappingCallback = preMappingCallback; PostMappingCallback = postMappingCallback; MapToNullCondition = mapToNullCondition; InstantiateLocalVariable = true; + MappingExpressions = mappingExpressions ?? new List(); } public MapperContext MapperContext => MapperData.MapperContext; @@ -468,6 +460,8 @@ public MappingCreationContext( public Expression MapToNullCondition { get; } + public List MappingExpressions { get; } + public bool InstantiateLocalVariable { get; set; } public MappingCreationContext WithDataSource(IDataSource newDataSource) @@ -477,14 +471,13 @@ public MappingCreationContext WithDataSource(IDataSource newDataSource) newSourceMappingData.MapperKey = new RootObjectMapperKey( RuleSet, newSourceMappingData.MappingTypes, - new FixedMembersMembersSource( - newDataSource.SourceMember, - MapperData.TargetMember)); + new FixedMembersMembersSource(newDataSource.SourceMember, MapperData.TargetMember)); newSourceMappingData.MapperData.SourceObject = newDataSource.Value; + newSourceMappingData.MapperData.TargetObject = MapperData.TargetObject; newSourceMappingData.MapperData.TargetInstance = MapperData.TargetInstance; - return new MappingCreationContext(newSourceMappingData); + return new MappingCreationContext(newSourceMappingData, mappingExpressions: MappingExpressions); } } diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index 8b13ca9f3..9c182b9b5 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -368,7 +368,7 @@ public bool CacheMappedObjects public Expression SourceObject { get; set; } - public Expression TargetObject { get; } + public Expression TargetObject { get; set; } public Expression EnumerableIndex { get; } diff --git a/AgileMapper/Plans/MappingPlan.cs b/AgileMapper/Plans/MappingPlan.cs index 76e4ac4f9..1795d15f0 100644 --- a/AgileMapper/Plans/MappingPlan.cs +++ b/AgileMapper/Plans/MappingPlan.cs @@ -2,7 +2,7 @@ { using System; using System.Collections.Generic; - using System.Linq; + using Extensions.Internal; using ObjectPopulation; /// @@ -24,7 +24,7 @@ internal MappingPlan(IObjectMapper cachedMapper) { _mappingPlanFunctions.AddRange(cachedMapper .RecursionMapperFuncs - .Select(mf => new RecursionMapperMappingPlanFunction(mf))); + .Project(mf => new RecursionMapperMappingPlanFunction(mf))); } } @@ -40,7 +40,7 @@ public static implicit operator string(MappingPlan mappingPlan) { return string.Join( Environment.NewLine + Environment.NewLine, - mappingPlan._mappingPlanFunctions.Select(pd => pd.GetDescription())); + mappingPlan._mappingPlanFunctions.Project(pd => pd.GetDescription())); } /// diff --git a/AgileMapper/Plans/MappingPlanSet.cs b/AgileMapper/Plans/MappingPlanSet.cs index 5159f6cda..40ec7071e 100644 --- a/AgileMapper/Plans/MappingPlanSet.cs +++ b/AgileMapper/Plans/MappingPlanSet.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Extensions.Internal; /// /// Contains sets of details of mapping plans for mappings between a particular source and target types, @@ -22,7 +23,7 @@ internal static MappingPlanSet For(MapperContext mapperContext) return new MappingPlanSet(mapperContext .ObjectMapperFactory .RootMappers - .Select(mapper => new MappingPlan(mapper)) + .Project(mapper => new MappingPlan(mapper)) .ToArray()); } @@ -38,7 +39,7 @@ public static implicit operator string(MappingPlanSet mappingPlans) { return string.Join( Environment.NewLine + Environment.NewLine, - mappingPlans._mappingPlans.Select(plan => plan.ToString())); + mappingPlans._mappingPlans.Project(plan => plan.ToString())); } /// diff --git a/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs b/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs index f45f17bc8..365995a75 100644 --- a/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs +++ b/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs @@ -60,7 +60,7 @@ private static bool TryGetEntityMemberIdMemberOrNull( .Instance .MemberCache .GetSourceMembers(entityParentAccess.Type) - .Where(m => m.IsSimple) + .Filter(m => m.IsSimple) .ToArray(); var entityMemberName = entityMemberAccess.GetMemberName(); diff --git a/AgileMapper/TypeConversion/ToEnumConverter.cs b/AgileMapper/TypeConversion/ToEnumConverter.cs index 7ecf1759e..3ec3bfd99 100644 --- a/AgileMapper/TypeConversion/ToEnumConverter.cs +++ b/AgileMapper/TypeConversion/ToEnumConverter.cs @@ -388,7 +388,7 @@ private Expression GetEnumToEnumConversion( }); var enumPairsConversion = sourceEnumValues - .Select(sv => new + .Project(sv => new { SourceValue = sv, PairedValue = enumPairs[sv] @@ -414,7 +414,7 @@ private Expression GetEnumToEnumConversion( } private static IList GetEnumValues(Type enumType) - => enumType.GetPublicStaticFields().Select(f => Expression.Field(null, f)).ToList(); + => enumType.GetPublicStaticFields().Project(f => Expression.Field(null, f)).ToList(); private static Expression GetNumericToEnumConversion( Expression sourceValue, @@ -510,7 +510,7 @@ private static Expression GetNumericStringToEnumConversion( var validEnumValues = Enum .GetValues(nonNullableTargetEnumType) .Cast() - .Select(v => Convert.ChangeType(v, underlyingEnumType).ToString()) + .Project(v => Convert.ChangeType(v, underlyingEnumType).ToString()) .ToArray() .ToConstantExpression(typeof(ICollection)); diff --git a/AgileMapper/TypeConversion/ToStringConverter.cs b/AgileMapper/TypeConversion/ToStringConverter.cs index a840abd6e..6c294b8e6 100644 --- a/AgileMapper/TypeConversion/ToStringConverter.cs +++ b/AgileMapper/TypeConversion/ToStringConverter.cs @@ -95,7 +95,7 @@ public static MethodInfo GetToStringMethodOrNull(Type sourceType, Type argumentT { var toStringMethod = sourceType .GetPublicInstanceMethods("ToString") - .Select(m => new + .Project(m => new { Method = m, Parameters = m.GetParameters() diff --git a/AgileMapper/Validation/EnumMappingMismatchFinder.cs b/AgileMapper/Validation/EnumMappingMismatchFinder.cs index 68409c2d0..0f6177604 100644 --- a/AgileMapper/Validation/EnumMappingMismatchFinder.cs +++ b/AgileMapper/Validation/EnumMappingMismatchFinder.cs @@ -37,8 +37,8 @@ public static ICollection FindMismatches(ObjectMapperDat } var mismatchSets = targetMemberDatas - .Select(d => EnumMappingMismatchSet.For(d.TargetMember, d.DataSources, mapperData)) - .Where(m => m.Any) + .Project(d => EnumMappingMismatchSet.For(d.TargetMember, d.DataSources, mapperData)) + .Filter(m => m.Any) .ToArray(); return mismatchSets; @@ -58,7 +58,7 @@ public static Expression Process(Expression lambda, ObjectMapperData mapperData) finder.Visit(lambda); var assignmentReplacements = finder._assignmentsByMismatchSet - .SelectMany(kvp => kvp.Value.Select(assignment => new + .SelectMany(kvp => kvp.Value.Project(assignment => new { Assignment = assignment, AssignmentWithWarning = (Expression)Expression.Block(kvp.Key.Warnings, assignment) @@ -86,7 +86,7 @@ private static IEnumerable EnumerateTargetMemberDatas(ObjectMa var dataSources = targetMemberAndDataSource .Value - .Where(dataSource => IsValidOtherEnumType(dataSource, targetEnumType)) + .Filter(dataSource => IsValidOtherEnumType(dataSource, targetEnumType)) .ToArray(); if (dataSources.Any()) diff --git a/AgileMapper/Validation/EnumMappingMismatchSet.cs b/AgileMapper/Validation/EnumMappingMismatchSet.cs index 04542b1c5..ceffa3e84 100644 --- a/AgileMapper/Validation/EnumMappingMismatchSet.cs +++ b/AgileMapper/Validation/EnumMappingMismatchSet.cs @@ -11,7 +11,7 @@ internal class EnumMappingMismatchSet { - public static readonly IEqualityComparer Comparer = new MismatchSetComparer(); + public static readonly IEqualityComparer Comparer = default(MismatchSetComparer); private static readonly EnumMappingMismatchSet _emptySet = new EnumMappingMismatchSet(Enumerable.EmptyArray); @@ -32,16 +32,16 @@ public static EnumMappingMismatchSet For( IMemberMapperData mapperData) { var sourceEnumData = dataSources - .Select(ds => new + .Project(ds => new { DataSource = ds, EnumType = ds.SourceMember.Type.GetNonNullableType() }) .GroupBy(dss => dss.EnumType) - .Select(dsGroup => new + .Project(dsGroup => new { EnumType = dsGroup.Key, - SourceMembers = dsGroup.Select(ds => ds.DataSource.SourceMember).ToArray() + SourceMembers = dsGroup.Project(ds => ds.DataSource.SourceMember).ToArray() }) .ToArray(); @@ -49,15 +49,15 @@ public static EnumMappingMismatchSet For( var targetEnumNames = Enum.GetNames(targetEnumType); var mappingMismatches = sourceEnumData - .Where(d => d.EnumType != targetEnumType) - .Select(d => EnumMappingMismatch.For( + .Filter(d => d.EnumType != targetEnumType) + .Project(d => EnumMappingMismatch.For( d.EnumType, d.SourceMembers, targetEnumType, targetEnumNames, targetMember, mapperData)) - .Where(mm => mm.Any) + .Filter(mm => mm.Any) .ToArray(); return mappingMismatches.Any() ? new EnumMappingMismatchSet(mappingMismatches) : _emptySet; @@ -75,7 +75,7 @@ private Expression CreateWarnings() { var warningsText = string.Join( Environment.NewLine + Environment.NewLine, - _mappingMismatches.Select(mm => mm.Warning)); + _mappingMismatches.Project(mm => mm.Warning)); return (warningsText != string.Empty) ? ReadableExpression.Comment(warningsText) @@ -84,7 +84,7 @@ private Expression CreateWarnings() #region Helper Classes - private class MismatchSetComparer : IEqualityComparer + private struct MismatchSetComparer : IEqualityComparer { public bool Equals(EnumMappingMismatchSet x, EnumMappingMismatchSet y) { @@ -181,7 +181,7 @@ private static string[] GetMappingMismatches( } var mismatches = unmatchedSourceValues - .Select(value => $" - {sourceEnumType.Name}.{value} matches no {targetEnumType.Name}") + .Project(value => $" - {sourceEnumType.Name}.{value} matches no {targetEnumType.Name}") .ToArray(); return mismatches; @@ -216,7 +216,7 @@ private static void FilterOutConfiguredPairs( public string TargetMemberPath { get; } public string SourceMemberPaths => - string.Join(" / ", _sourceMembers.Select(sm => sm.GetFriendlySourcePath(_rootMapperData))); + string.Join(" / ", _sourceMembers.Project(sm => sm.GetFriendlySourcePath(_rootMapperData))); public string Warning => _warning ?? (_warning = CreateWarning()); diff --git a/AgileMapper/Validation/MappingValidator.cs b/AgileMapper/Validation/MappingValidator.cs index ab838ce8b..0cf205992 100644 --- a/AgileMapper/Validation/MappingValidator.cs +++ b/AgileMapper/Validation/MappingValidator.cs @@ -14,7 +14,7 @@ internal static class MappingValidator { public static void Validate(Mapper mapper) { - var rootMapperDatas = mapper.Context.ObjectMapperFactory.RootMappers.Select(m => m.MapperData); + var rootMapperDatas = mapper.Context.ObjectMapperFactory.RootMappers.Project(m => m.MapperData); VerifyMappingPlanIsComplete(GetAllMapperDatas(rootMapperDatas)); } @@ -75,7 +75,7 @@ private static ICollection GetIncompleteMappingPlanData( IEnumerable mapperDatas) { return mapperDatas - .Select(md => new + .Project(md => new { MapperData = md, IsUnmappable = @@ -84,19 +84,19 @@ private static ICollection GetIncompleteMappingPlanData( md.DataSourcesByTargetMember.None(), UnmappedMembers = md .DataSourcesByTargetMember - .Where(pair => !pair.Value.HasValue) - .Select(pair => pair) + .Filter(pair => !pair.Value.HasValue) + .Project(pair => pair) .ToArray(), UnpairedEnums = EnumMappingMismatchFinder.FindMismatches(md) }) - .Where(d => d.IsUnmappable || d.UnmappedMembers.Any() || d.UnpairedEnums.Any()) + .Filter(d => d.IsUnmappable || d.UnmappedMembers.Any() || d.UnpairedEnums.Any()) .GroupBy(d => d.MapperData.GetRootMapperData()) - .Select(g => new IncompleteMappingData + .Project(g => new IncompleteMappingData { RootMapperData = g.Key, UnmappableTargetTypes = g - .Where(d => d.IsUnmappable) - .Select(d => d.MapperData) + .Filter(d => d.IsUnmappable) + .Project(d => d.MapperData) .ToList(), UnmappedMembers = g .SelectMany(d => d.UnmappedMembers) From 82cccc5e9af70f2b6f6de228ce70b1850d0ffa5d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 14:03:13 +0100 Subject: [PATCH 11/16] Support for configured enumerable root source members --- .../WhenConfiguringDataSources.cs | 6 ++++ .../WhenMappingToNewEnumerables.cs | 8 +++++ .../EnumerablePopulationBuilder.cs | 13 +++++++- .../MappingExpressionFactoryBase.cs | 33 +++++++++++++++---- .../ObjectPopulation/ObjectMapperData.cs | 5 ++- 5 files changed, 57 insertions(+), 8 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 8e610e0ac..309139e84 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1150,6 +1150,12 @@ public void ShouldApplyAConfiguredRootSourceEnumerableMember() result.First().Line2.ShouldBe("Else"); result.Second().Line1.ShouldBe("Elsewhere"); result.Second().Line2.ShouldBeNull(); + + source.Value2 = null; + + var nullResult = mapper.Map(source).ToANew>(); + + nullResult.ShouldBeEmpty(); } } diff --git a/AgileMapper.UnitTests/WhenMappingToNewEnumerables.cs b/AgileMapper.UnitTests/WhenMappingToNewEnumerables.cs index c8e2f2bef..45dc05b5a 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewEnumerables.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewEnumerables.cs @@ -145,5 +145,13 @@ public void ShouldCreateAnEmptyListByDefault() result.ShouldNotBeNull(); result.ShouldBeEmpty(); } + + [Fact] + public void ShouldHandleANullSource() + { + var result = Mapper.Map(default(PublicProperty)).ToANew>(); + + result.ShouldBeNull(); + } } } diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index e8ddb2e53..ea8c0ecae 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -32,6 +32,7 @@ internal class EnumerablePopulationBuilder private bool? _elementsAreIdentifiable; private ParameterExpression _collectionDataVariable; private ParameterExpression _counterVariable; + private ParameterExpression _targetVariable; public EnumerablePopulationBuilder(ObjectMapperData mapperData) { @@ -244,7 +245,17 @@ private static LambdaExpression GetTargetElementIdLambda(ParameterExpression tar public EnumerableTypeHelper TargetTypeHelper { get; } - public ParameterExpression TargetVariable { get; private set; } + public ParameterExpression TargetVariable + { + get => _targetVariable; + set + { + if (_targetVariable == null) + { + _targetVariable = value; + } + } + } public void AssignSourceVariableFromSourceObject() { diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 0c0cf2245..da7c2bd01 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -136,20 +136,36 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr continue; } + if (context.MapperData.TargetMember.IsEnumerable) + { + context.MapperData.LocalVariable = newSourceContext.MapperData.LocalVariable; + context.MapperData.EnumerablePopulationBuilder.TargetVariable = newSourceContext.MapperData.EnumerablePopulationBuilder.TargetVariable; + } + var mapping = memberPopulations.HasOne() ? memberPopulations.First() : Expression.Block(memberPopulations); - if (configuredRootDataSource.IsConditional) + if (!configuredRootDataSource.IsConditional) + { + yield return mapping; + continue; + } + + if (context.MapperData.TargetMember.IsComplex) { - mapping = Expression.IfThen(configuredRootDataSource.Condition, mapping); + yield return Expression.IfThen(configuredRootDataSource.Condition, mapping); + continue; } - yield return mapping; + var fallback = context.MapperData.GetFallbackCollectionValue(); + var assignFallback = context.MapperData.LocalVariable.AssignTo(fallback); + + yield return Expression.IfThenElse(configuredRootDataSource.Condition, mapping, assignFallback); } } - protected static bool HasConfiguredRootDataSources(ObjectMapperData mapperData, out IList dataSources) + protected static bool HasConfiguredRootDataSources(IMemberMapperData mapperData, out IList dataSources) { if (!mapperData.IsRoot || !mapperData.MapperContext.UserConfigurations.HasConfiguredRootDataSources) @@ -161,7 +177,8 @@ protected static bool HasConfiguredRootDataSources(ObjectMapperData mapperData, dataSources = mapperData .MapperContext .UserConfigurations - .GetDataSources(mapperData); + .GetDataSources(mapperData) + .ToArray(); return dataSources.Any(); } @@ -475,7 +492,11 @@ public MappingCreationContext WithDataSource(IDataSource newDataSource) newSourceMappingData.MapperData.SourceObject = newDataSource.Value; newSourceMappingData.MapperData.TargetObject = MapperData.TargetObject; - newSourceMappingData.MapperData.TargetInstance = MapperData.TargetInstance; + + if (MapperData.TargetMember.IsComplex) + { + newSourceMappingData.MapperData.TargetInstance = MapperData.TargetInstance; + } return new MappingCreationContext(newSourceMappingData, mappingExpressions: MappingExpressions); } diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index 9c182b9b5..ead69d5c3 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -382,7 +382,10 @@ private Expression GetTargetInstance() => Context.UseLocalVariable ? LocalVariable : TargetObject; public ParameterExpression LocalVariable - => _instanceVariable ?? (_instanceVariable = CreateInstanceVariable()); + { + get => _instanceVariable ?? (_instanceVariable = CreateInstanceVariable()); + set => _instanceVariable = value; + } private ParameterExpression CreateInstanceVariable() { From b943eccb7f5cad7b829639c9462541201d8c9407 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 14:48:52 +0100 Subject: [PATCH 12/16] Support for multiple configured root enumerable data sources --- .../WhenConfiguringDataSources.cs | 26 +++++++++ .../Configuration/UserConfigurationSet.cs | 7 ++- .../EnumerablePopulationBuilder.cs | 5 ++ .../MappingExpressionFactoryBase.cs | 57 ++++++++++++++----- 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 309139e84..4dd00fcf9 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1159,6 +1159,32 @@ public void ShouldApplyAConfiguredRootSourceEnumerableMember() } } + [Fact] + public void ShouldApplyMultipleConfiguredRootSourceEnumerableMembers() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .To() + .Map((s, t) => s.Value1) + .ToRootTarget() + .And + .Map((s, t) => s.Value2) + .ToRootTarget(); + + var source = new PublicTwoFields + { + Value1 = new[] { 1, 2, 3 }, + Value2 = new[] { 1L, 2L, 3L } + }; + + var result = mapper.Map(source).ToANew(); + + result.Length.ShouldBe(6); + } + } + // ReSharper disable once ClassNeverInstantiated.Local // ReSharper disable UnusedAutoPropertyAccessor.Local private class IdTester diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index d0d6e5be6..275a0741e 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -175,8 +175,11 @@ private List DataSourceFactories public void Add(ConfiguredDataSourceFactory dataSourceFactory) { - ThrowIfConflictingIgnoredMemberExists(dataSourceFactory); - ThrowIfConflictingDataSourceExists(dataSourceFactory, (dsf, cDsf) => dsf.GetConflictMessage(cDsf)); + if (!dataSourceFactory.TargetMember.IsRoot) + { + ThrowIfConflictingIgnoredMemberExists(dataSourceFactory); + ThrowIfConflictingDataSourceExists(dataSourceFactory, (dsf, cDsf) => dsf.GetConflictMessage(cDsf)); + } DataSourceFactories.AddSortFilter(dataSourceFactory); diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index ea8c0ecae..8ed548568 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -308,6 +308,11 @@ private Expression GetSourceOnlyReturnValue(IObjectMappingData mappingData) private void AssignTargetVariableTo(Expression value) { + if (TargetVariable != null) + { + return; + } + TargetVariable = Context.GetTargetParameterFor(value.Type); _populationExpressions.Add(TargetVariable.AssignTo(value)); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index da7c2bd01..46d8b4b5f 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -122,9 +122,9 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr yield break; } - // TODO: Test coverage for mappings with variables - foreach (var configuredRootDataSource in configuredRootDataSources) + for (var i = 0; i < configuredRootDataSources.Count; ++i) { + var configuredRootDataSource = configuredRootDataSources[i]; var newSourceContext = context.WithDataSource(configuredRootDataSource); newSourceContext.InstantiateLocalVariable = false; @@ -136,11 +136,7 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr continue; } - if (context.MapperData.TargetMember.IsEnumerable) - { - context.MapperData.LocalVariable = newSourceContext.MapperData.LocalVariable; - context.MapperData.EnumerablePopulationBuilder.TargetVariable = newSourceContext.MapperData.EnumerablePopulationBuilder.TargetVariable; - } + context.UpdateFrom(newSourceContext); var mapping = memberPopulations.HasOne() ? memberPopulations.First() @@ -152,13 +148,16 @@ private IEnumerable GetConfiguredRootDataSourcePopulations(MappingCr continue; } - if (context.MapperData.TargetMember.IsComplex) + if (context.MapperData.TargetMember.IsComplex || (i > 0)) { yield return Expression.IfThen(configuredRootDataSource.Condition, mapping); continue; } - var fallback = context.MapperData.GetFallbackCollectionValue(); + var fallback = context.MapperData.LocalVariable.Type.GetEmptyInstanceCreation( + context.TargetMember.ElementType, + context.MapperData.EnumerablePopulationBuilder.TargetTypeHelper); + var assignFallback = context.MapperData.LocalVariable.AssignTo(fallback); yield return Expression.IfThenElse(configuredRootDataSource.Condition, mapping, assignFallback); @@ -438,6 +437,8 @@ public virtual void Reset() internal class MappingCreationContext { + private bool _mapperDataHasRootEnumerableVariables; + public MappingCreationContext( IObjectMappingData mappingData, Expression mapToNullCondition = null, @@ -467,6 +468,8 @@ public MappingCreationContext( public ObjectMapperData MapperData => MappingData.MapperData; + public QualifiedMember TargetMember => MapperData.TargetMember; + public bool IsRoot => MappingData.IsRoot; public IObjectMappingData MappingData { get; } @@ -488,17 +491,41 @@ public MappingCreationContext WithDataSource(IDataSource newDataSource) newSourceMappingData.MapperKey = new RootObjectMapperKey( RuleSet, newSourceMappingData.MappingTypes, - new FixedMembersMembersSource(newDataSource.SourceMember, MapperData.TargetMember)); + new FixedMembersMembersSource(newDataSource.SourceMember, TargetMember)); + + var newContext = new MappingCreationContext(newSourceMappingData, mappingExpressions: MappingExpressions); + + newContext.MapperData.SourceObject = newDataSource.Value; + newContext.MapperData.TargetObject = MapperData.TargetObject; + + if (TargetMember.IsComplex) + { + newContext.MapperData.TargetInstance = MapperData.TargetInstance; + } + else if (_mapperDataHasRootEnumerableVariables) + { + UpdateEnumerableVariables(MapperData, newContext.MapperData); + } - newSourceMappingData.MapperData.SourceObject = newDataSource.Value; - newSourceMappingData.MapperData.TargetObject = MapperData.TargetObject; + return newContext; + } - if (MapperData.TargetMember.IsComplex) + public void UpdateFrom(MappingCreationContext childSourceContext) + { + if (TargetMember.IsComplex || _mapperDataHasRootEnumerableVariables) { - newSourceMappingData.MapperData.TargetInstance = MapperData.TargetInstance; + return; } - return new MappingCreationContext(newSourceMappingData, mappingExpressions: MappingExpressions); + _mapperDataHasRootEnumerableVariables = true; + + UpdateEnumerableVariables(childSourceContext.MapperData, MapperData); + } + + private static void UpdateEnumerableVariables(ObjectMapperData sourceMapperData, ObjectMapperData targetMapperData) + { + targetMapperData.LocalVariable = sourceMapperData.LocalVariable; + targetMapperData.EnumerablePopulationBuilder.TargetVariable = sourceMapperData.EnumerablePopulationBuilder.TargetVariable; } } From cce73eaca203c8bc3f20e709e6a620de3f8a6fe6 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 16:30:05 +0100 Subject: [PATCH 13/16] Test coverage for configuring multiple complex type root source members and root source members inline --- .../WhenConfiguringDataSourcesInline.cs | 20 ++++++++++++++ .../WhenConfiguringDataSources.cs | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index a368eb0d8..8cb54af40 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -2,6 +2,7 @@ { using System; using System.Linq.Expressions; + using AgileMapper.Extensions; using AgileMapper.Members; using TestClasses; using Xunit; @@ -327,6 +328,25 @@ public void ShouldHandleANullSourceMember() } } + // See https://github.com/agileobjects/AgileMapper/issues/64 + [Fact] + public void ShouldApplyAConfiguredRootSourceMember() + { + using (var mapper = Mapper.CreateNew()) + { + var source = new { Value1 = 8392, Value = new { Value2 = 5482 } }; + + var result = source + .MapUsing(mapper) + .ToANew>(cfg => cfg + .Map((s, ptf) => s.Value) + .ToRootTarget()); + + result.Value1.ShouldBe(8392); + result.Value2.ShouldBe(5482); + } + } + #region Helper Members private static Expression, PublicField>, object>> SubtractOne => diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 4dd00fcf9..1e70ee7cf 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1159,6 +1159,33 @@ public void ShouldApplyAConfiguredRootSourceEnumerableMember() } } + [Fact] + public void ShouldApplyMultipleConfiguredRootSourceComplexTypeMembers() + { + using (var mapper = Mapper.CreateNew()) + { + var source = new + { + PropertyOne = new { Value1 = "Value 1!" }, + PropertyTwo = new { Value2 = "Value 2!" }, + }; + + mapper.WhenMapping + .From(source) + .To>() + .Map((s, t) => s.PropertyOne) + .ToRootTarget() + .And + .Map((s, t) => s.PropertyTwo) + .ToRootTarget(); + + var result = mapper.Map(source).ToANew>(); + + result.Value1.ShouldBe("Value 1!"); + result.Value2.ShouldBe("Value 2!"); + } + } + [Fact] public void ShouldApplyMultipleConfiguredRootSourceEnumerableMembers() { From 4c46a198c8566e4f2e9262a9ba0568b8afb42a14 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 30 Jun 2018 16:48:24 +0100 Subject: [PATCH 14/16] Support for runtime-typed configured root source members --- .../WhenConfiguringDataSourcesInline.cs | 35 +++++++++++++++++++ .../DataSources/SourceMemberDataSource.cs | 13 +++---- .../MappingExpressionFactoryBase.cs | 2 ++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index 8cb54af40..628fd0feb 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -347,6 +347,41 @@ public void ShouldApplyAConfiguredRootSourceMember() } } + [Fact] + public void ShouldApplyAConfiguredRootSourceObjectMember() + { + using (var mapper = Mapper.CreateNew()) + { + var source1 = new PublicProperty + { + Value = new PublicField { Value = "Hello!" } + }; + + var result1 = mapper + .Map(source1) + .ToANew>(cfg => cfg + .Map((s, t) => s.Value) + .ToRootTarget()); + + result1.Value.ShouldBe("Hello!"); + + var source2 = new PublicProperty + { + Value = new PublicProperty { Value = "Goodbye!" } + }; + + var result2 = mapper + .Map(source2) + .ToANew>(cfg => cfg + .Map((s, t) => s.Value) + .ToRootTarget()); + + result2.Value.ShouldBe("Goodbye!"); + + mapper.InlineContexts().ShouldHaveSingleItem(); + } + } + #region Helper Members private static Expression, PublicField>, object>> SubtractOne => diff --git a/AgileMapper/DataSources/SourceMemberDataSource.cs b/AgileMapper/DataSources/SourceMemberDataSource.cs index e20d1e1fc..08796af3a 100644 --- a/AgileMapper/DataSources/SourceMemberDataSource.cs +++ b/AgileMapper/DataSources/SourceMemberDataSource.cs @@ -28,15 +28,16 @@ private static Expression CreateSourceMemberTypeTest(Expression value, IMemberMa while (parent != mapperData.SourceObject) { - if (parent.NodeType == ExpressionType.Convert) + if (parent.NodeType != ExpressionType.Convert) { - var cast = (UnaryExpression)parent; - parent = cast.Operand; - - typeTests.Insert(0, GetRuntimeTypeCheck(cast, mapperData)); + parent = parent.GetParentOrNull(); + continue; } - parent = parent.GetParentOrNull(); + var cast = (UnaryExpression)parent; + parent = cast.Operand; + + typeTests.Insert(0, GetRuntimeTypeCheck(cast, mapperData)); } var allTests = typeTests.AndTogether(); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 46d8b4b5f..60ea062f6 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -512,6 +512,8 @@ public MappingCreationContext WithDataSource(IDataSource newDataSource) public void UpdateFrom(MappingCreationContext childSourceContext) { + MappingData.MapperKey.AddSourceMemberTypeTesterIfRequired(childSourceContext.MappingData); + if (TargetMember.IsComplex || _mapperDataHasRootEnumerableVariables) { return; From 2fd2d6d3b7b5e13227945d1e9cc2ae0fee44df26 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 1 Jul 2018 07:32:31 +0100 Subject: [PATCH 15/16] Support for mapping configured root data sources to dictionaries --- .../WhenConfiguringTargetDictionaryMapping.cs | 25 +++++++++++++++++++ .../Members/WhenFindingDataSources.cs | 3 +-- .../CustomDataSourceTargetMemberSpecifier.cs | 17 +++++++------ .../Configuration/ConfiguredLambdaInfo.cs | 18 +++++++++++-- .../ConfiguredDataSourceFactory.cs | 9 ++++++- ...iguredDictionaryEntryDataSourceFactory.cs} | 6 ++--- AgileMapper/DataSources/DataSourceBase.cs | 2 +- .../Finders/MetaMemberDataSourceFinder.cs | 2 +- .../Extensions/Internal/TypeExtensions.cs | 2 +- .../Members/MemberMapperDataExtensions.cs | 2 +- .../DictionaryMappingExpressionFactory.cs | 12 ++++++--- .../TypeConversion/ToStringConverter.cs | 2 +- .../Validation/EnumMappingMismatchFinder.cs | 2 +- 13 files changed, 76 insertions(+), 26 deletions(-) rename AgileMapper/DataSources/{ConfiguredDictionaryDataSourceFactory.cs => ConfiguredDictionaryEntryDataSourceFactory.cs} (82%) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index 3ceb29134..11bb7c2d0 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -344,5 +344,30 @@ public void ShouldApplyACustomConfiguredMemberConditionally() noLine2Result["Line2State"].ShouldBe("Missing"); } } + + [Fact] + public void ShouldApplyAConfiguredRootSourceMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDictionaries + .Map((pf, d) => pf.Value) + .ToRootTarget(); + + var source = new PublicField
+ { + Value = new Address { Line1 = "Here!", Line2 = "Here too!" } + }; + + var result = mapper.Map(source).ToANew>(); + + result["Value.Line1"].ShouldBe("Here!"); + result["Value.Line2"].ShouldBe("Here too!"); + result["Line1"].ShouldBe("Here!"); + result["Line2"].ShouldBe("Here too!"); + } + } } } \ No newline at end of file diff --git a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs index fc0b9843f..4d75f546c 100644 --- a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs +++ b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs @@ -47,8 +47,7 @@ private IQualifiedMember GetMatchingSourceMember( var childMapperData = new ChildMemberMapperData(targetMember, rootMapperData); var childMappingContext = rootMappingData.GetChildMappingData(childMapperData); - var matchingSourceMember = SourceMemberMatcher.GetMatchFor(childMappingContext, out var _); - return matchingSourceMember; + return SourceMemberMatcher.GetMatchFor(childMappingContext, out _); } #region Helper Classes diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index a928b0d99..cbe166a28 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -75,7 +75,7 @@ private ConfiguredDataSourceFactory CreateFromLambda(LambdaExpress if (IsDictionaryEntry(targetMemberLambda, out var dictionaryEntryMember)) { - return new ConfiguredDictionaryDataSourceFactory(_configInfo, valueLambdaInfo, dictionaryEntryMember); + return new ConfiguredDictionaryEntryDataSourceFactory(_configInfo, valueLambdaInfo, dictionaryEntryMember); } return new ConfiguredDataSourceFactory(_configInfo, valueLambdaInfo, targetMemberLambda); @@ -105,21 +105,22 @@ private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out Dictiona if (entryKeyExpression.NodeType != ExpressionType.Constant) { throw new MappingConfigurationException( - "Target dictionary keys must be constant string values."); + "Target dictionary entry keys must be constant string values."); } var entryKey = (string)((ConstantExpression)entryKeyExpression).Value; - var memberFactory = _configInfo.MapperContext.QualifiedMemberFactory; - var rootMember = (DictionaryTargetMember)(_configInfo.TargetType == typeof(ExpandoObject) - ? memberFactory.RootTarget() - : memberFactory.RootTarget()); + ? CreateRootTargetQualifiedMember() + : CreateRootTargetQualifiedMember()); entryMember = rootMember.Append(typeof(TSource), entryKey); return true; } + private QualifiedMember CreateRootTargetQualifiedMember() + => _configInfo.MapperContext.QualifiedMemberFactory.RootTarget(); + private ConfiguredLambdaInfo GetValueLambdaInfo() { if (_customValueLambdaInfo != null) @@ -252,7 +253,7 @@ public IMappingConfigContinuation ToRootTarget() return RegisterDataSource(() => new ConfiguredDataSourceFactory( _configInfo, GetValueLambdaInfo(), - QualifiedMember.From(Member.RootTarget(), _configInfo.MapperContext))); + CreateRootTargetQualifiedMember())); } private void ThrowIfSimpleSourceTypeConfigured() @@ -268,7 +269,7 @@ private void ThrowIfSimpleSourceTypeConfigured() if (customValue.NodeType == ExpressionType.MemberAccess) { - var rootSourceMember = QualifiedMember.From(Member.RootSource(), _configInfo.MapperContext); + var rootSourceMember = _configInfo.MapperContext.QualifiedMemberFactory.RootSource(); var configuredMember = Member.RootSource(customValue.ToReadableString(), customValue.Type); var configuredSourceMember = QualifiedMember.From(configuredMember, _configInfo.MapperContext); sourceValue = configuredSourceMember.GetFriendlyMemberPath(rootSourceMember) + " of type "; diff --git a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs index 230df5244..788a36f1e 100644 --- a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs +++ b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs @@ -5,6 +5,7 @@ using System.Linq.Expressions; using Extensions.Internal; using Members; + using Members.Dictionaries; using NetStandardPolyfills; using ObjectPopulation; @@ -12,6 +13,7 @@ internal class ConfiguredLambdaInfo { private readonly LambdaExpression _lambda; private readonly Type[] _contextTypes; + private readonly bool _isForTargetDictionary; private readonly ParametersSwapper _parametersSwapper; private ConfiguredLambdaInfo( @@ -24,6 +26,8 @@ private ConfiguredLambdaInfo( _contextTypes = contextTypes; _parametersSwapper = parametersSwapper; ReturnType = returnType; + + _isForTargetDictionary = contextTypes.Any() && contextTypes[1].IsDictionary(); } #region Factory Methods @@ -150,9 +154,19 @@ public Expression GetBody( CallbackPosition? position = null, QualifiedMember targetMember = null) { + var contextTypes = _contextTypes; + + if (_isForTargetDictionary && + (mapperData.TargetMember is DictionaryTargetMember dictionaryMember) && + (dictionaryMember.HasCompatibleType(contextTypes[1]))) + { + contextTypes = contextTypes.ToArray(); + contextTypes[1] = mapperData.TargetType; + } + return position.IsPriorToObjectCreation(targetMember) - ? _parametersSwapper.Swap(_lambda, _contextTypes, mapperData, ParametersSwapper.UseTargetMember) - : _parametersSwapper.Swap(_lambda, _contextTypes, mapperData, ParametersSwapper.UseTargetInstance); + ? _parametersSwapper.Swap(_lambda, contextTypes, mapperData, ParametersSwapper.UseTargetMember) + : _parametersSwapper.Swap(_lambda, contextTypes, mapperData, ParametersSwapper.UseTargetInstance); } } } \ No newline at end of file diff --git a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs b/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs index fb2c8284a..e28c69fd9 100644 --- a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs +++ b/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs @@ -77,7 +77,14 @@ public string GetConflictMessage(ConfiguredDataSourceFactory conflictingDataSour } public override bool AppliesTo(IBasicMapperData mapperData) - => base.AppliesTo(mapperData) && _dataSourceLambda.Supports(mapperData.RuleSet); + { + if (TargetMember.IsRoot && !mapperData.IsRoot) + { + return false; + } + + return base.AppliesTo(mapperData) && _dataSourceLambda.Supports(mapperData.RuleSet); + } public IConfiguredDataSource Create(IMemberMapperData mapperData) { diff --git a/AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs b/AgileMapper/DataSources/ConfiguredDictionaryEntryDataSourceFactory.cs similarity index 82% rename from AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs rename to AgileMapper/DataSources/ConfiguredDictionaryEntryDataSourceFactory.cs index 043de073c..04f72efec 100644 --- a/AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/ConfiguredDictionaryEntryDataSourceFactory.cs @@ -4,9 +4,9 @@ using Members; using Members.Dictionaries; - internal class ConfiguredDictionaryDataSourceFactory : ConfiguredDataSourceFactory + internal class ConfiguredDictionaryEntryDataSourceFactory : ConfiguredDataSourceFactory { - public ConfiguredDictionaryDataSourceFactory( + public ConfiguredDictionaryEntryDataSourceFactory( MappingConfigInfo configInfo, ConfiguredLambdaInfo dataSourceLambda, DictionaryTargetMember targetDictionaryEntryMember) @@ -32,7 +32,7 @@ public override bool AppliesTo(IBasicMapperData mapperData) protected override bool MembersConflict(UserConfiguredItemBase otherItem) { - return otherItem is ConfiguredDictionaryDataSourceFactory otherDictionaryItem && + return otherItem is ConfiguredDictionaryEntryDataSourceFactory otherDictionaryItem && TargetDictionaryEntryMember.LeafMember.Equals(otherDictionaryItem.TargetDictionaryEntryMember.LeafMember); } } diff --git a/AgileMapper/DataSources/DataSourceBase.cs b/AgileMapper/DataSources/DataSourceBase.cs index eeae978be..7a8240d4a 100644 --- a/AgileMapper/DataSources/DataSourceBase.cs +++ b/AgileMapper/DataSources/DataSourceBase.cs @@ -166,7 +166,7 @@ private static bool IsNotOptionalEntityMemberId(IMemberMapperData mapperData) .GetTargetMembers(mapperData.TargetType) .FirstOrDefault(m => m.Name == entityMemberName); - return !mapperData.IsEntity(entityMember?.Type, out var _); + return !mapperData.IsEntity(entityMember?.Type, out _); } #endregion diff --git a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs index 81a03c00e..6762dcc2c 100644 --- a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs @@ -210,7 +210,7 @@ private static bool TryGetMetaMember( currentSourceMember = SourceMemberMatcher.GetMatchFor( memberMappingData, - out var _, + out _, searchParentContexts: false); if (currentSourceMember == null) diff --git a/AgileMapper/Extensions/Internal/TypeExtensions.cs b/AgileMapper/Extensions/Internal/TypeExtensions.cs index 92f254978..ca751b860 100644 --- a/AgileMapper/Extensions/Internal/TypeExtensions.cs +++ b/AgileMapper/Extensions/Internal/TypeExtensions.cs @@ -157,7 +157,7 @@ public static bool IsSimple(this Type type) return type.IsValueType() && type.IsFromBcl(); } - public static bool IsDictionary(this Type type) => IsDictionary(type, out var _); + public static bool IsDictionary(this Type type) => IsDictionary(type, out _); public static bool IsDictionary(this Type type, out KeyValuePair keyAndValueTypes) { diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index a2a1301cd..a47c1485b 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -19,7 +19,7 @@ public static bool IsStandalone(this IObjectMappingData mappingData) => mappingData.IsRoot || mappingData.MappingTypes.RuntimeTypesNeeded; public static bool TargetTypeIsEntity(this IMemberMapperData mapperData) - => IsEntity(mapperData, mapperData.TargetType, out var _); + => IsEntity(mapperData, mapperData.TargetType, out _); public static bool IsEntity(this IMemberMapperData mapperData, Type type, out Member idMember) { diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 63c83292a..bc5b8f934 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -34,7 +34,7 @@ private static IEnumerable GetAllTargetMembers(ObjectMapperData var configuredDataSourceFactories = mapperData.MapperContext .UserConfigurations - .QueryDataSourceFactories() + .QueryDataSourceFactories() .Filter(dsf => dsf.IsFor(mapperData)) .ToArray(); @@ -203,7 +203,7 @@ private static IEnumerable GetNestedFlattenedMembers( } private static DictionaryTargetMember[] GetConfiguredTargetMembers( - IEnumerable configuredDataSourceFactories, + IEnumerable configuredDataSourceFactories, IList targetMembersFromSource) { return configuredDataSourceFactories @@ -292,13 +292,17 @@ protected override IEnumerable GetObjectPopulation(MappingCreationCo assignmentFactory = GetMappedDictionaryAssignment; } - else + else if (context.InstantiateLocalVariable) { assignmentFactory = (dsm, md) => GetParameterlessDictionaryAssignment(md); } + else + { + assignmentFactory = null; + } var population = GetDictionaryPopulation(context.MappingData); - var assignment = assignmentFactory.Invoke(sourceDictionaryMember, context.MappingData); + var assignment = assignmentFactory?.Invoke(sourceDictionaryMember, context.MappingData); yield return assignment; yield return population; diff --git a/AgileMapper/TypeConversion/ToStringConverter.cs b/AgileMapper/TypeConversion/ToStringConverter.cs index 6c294b8e6..930a17c0b 100644 --- a/AgileMapper/TypeConversion/ToStringConverter.cs +++ b/AgileMapper/TypeConversion/ToStringConverter.cs @@ -19,7 +19,7 @@ public static bool HasNativeStringRepresentation(Type nonNullableType) (nonNullableType == typeof(object)) || nonNullableType.IsEnum() || (nonNullableType == typeof(char)) || - HasToStringOperator(nonNullableType, out var _); + HasToStringOperator(nonNullableType, out _); } private static bool HasToStringOperator(Type nonNullableSourceType, out MethodInfo operatorMethod) diff --git a/AgileMapper/Validation/EnumMappingMismatchFinder.cs b/AgileMapper/Validation/EnumMappingMismatchFinder.cs index 0f6177604..4614f0658 100644 --- a/AgileMapper/Validation/EnumMappingMismatchFinder.cs +++ b/AgileMapper/Validation/EnumMappingMismatchFinder.cs @@ -150,7 +150,7 @@ protected override Expression VisitBinary(BinaryExpression binary) return base.VisitBinary(binary); } - private static bool IsEnum(Type type) => IsEnum(type, out var _); + private static bool IsEnum(Type type) => IsEnum(type, out _); private bool TryGetMatch(Expression targetMemberAccess, out TargetMemberData targetMemberData) { From 1997b36bb38d0e643a19ec8fbf45c2735fd3ad00 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 1 Jul 2018 07:48:41 +0100 Subject: [PATCH 16/16] Test coverage for configured root source member mapping to dynamic --- .../WhenConfiguringTargetDynamicMapping.cs | 25 ++++++++++++++++++ .../WhenMappingToNewEnumerableMembers.cs | 26 +++++++++++++++++++ .../ObjectPopulation/ObjectMappingData.cs | 4 ++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index 9591fe386..a304d62dc 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -344,5 +344,30 @@ public void ShouldNotConflictTargetDynamicAndDictionaryConfiguration() targetDynamicDictionary["Value+Line2"].ShouldBe("L2"); } } + + [Fact] + public void ShouldApplyAConfiguredRootSourceMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDynamics + .Map((pf, d) => pf.Value) + .ToRootTarget(); + + var source = new PublicField
+ { + Value = new Address { Line1 = "There!", Line2 = "There too!" } + }; + + var result = mapper.Map(source).ToANew(); + + ((string)result.Value_Line1).ShouldBe("There!"); + ((string)result.Value_Line2).ShouldBe("There too!"); + ((string)result.Line1).ShouldBe("There!"); + ((string)result.Line2).ShouldBe("There too!"); + } + } } } diff --git a/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs b/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs index e47ee0706..ba2583826 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs @@ -439,6 +439,32 @@ public void ShouldPopulateANonNullReadOnlyNestedIList() } } + [Fact] + public void ShouldHandleMultipleRuntimeTypedMembers() + { + var arraysSource = new PublicTwoFields + { + Value1 = new[] { 1, 2, 3 }, + Value2 = new[] { "4", "5", "6" } + }; + + var arraysResult = Mapper.Map(arraysSource).ToANew>(); + + arraysResult.Value1.ShouldBe(1L, 2L, 3L); + arraysResult.Value2.ShouldBe(4L, 5L, 6L); + + var listsSource = new PublicTwoFields + { + Value1 = new List { 7, 8, 9 }, + Value2 = new List { "10", "11", "12" } + }; + + var listsResult = Mapper.Map(listsSource).ToANew>(); + + listsResult.Value1.ShouldBe(7L, 8L, 9L); + listsResult.Value2.ShouldBe(10L, 11L, 12L); + } + [Fact] public void ShouldCreateAnEmptyCollectionByDefault() { diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index d69911451..9bb347041 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -58,8 +58,10 @@ private ObjectMappingData( if (parent != null) { Parent = parent; + return; } - else if (createMapper) + + if (createMapper) { _mapper = MapperContext.ObjectMapperFactory.GetOrCreateRoot(this); }