diff --git a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj index 70e14fb2b..c014778c5 100644 --- a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj +++ b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj @@ -66,8 +66,599 @@ - - %(RecursiveDir)%(Filename)%(Extension) + + Caching\WhenCachingWithHashCodes.cs + + + Configuration\AssemblyScanningTestClassBase.cs + + + Configuration\Inline\InlineMappingExtensions.cs + + + Configuration\Inline\WhenConfiguringCallbacksInline.cs + + + Configuration\Inline\WhenConfiguringConstructorDataSourcesInline.cs + + + Configuration\Inline\WhenConfiguringDataSourcesInline.cs + + + Configuration\Inline\WhenConfiguringDataSourcesInlineIncorrectly.cs + + + Configuration\Inline\WhenConfiguringDerivedTypesInline.cs + + + Configuration\Inline\WhenConfiguringEntityMappingInline.cs + + + Configuration\Inline\WhenConfiguringEnumMappingInline.cs + + + Configuration\Inline\WhenConfiguringNameMatchingInline.cs + + + Configuration\Inline\WhenConfiguringObjectCreationInline.cs + + + Configuration\Inline\WhenConfiguringObjectTrackingInline.cs + + + Configuration\Inline\WhenConfiguringStringFormattingInline.cs + + + Configuration\Inline\WhenConfiguringTypeIdentifiersInline.cs + + + Configuration\Inline\WhenIgnoringMembersInline.cs + + + Configuration\Inline\WhenIgnoringMembersInlineIncorrectly.cs + + + Configuration\Inline\WhenMappingToNullInline.cs + + + Configuration\Inline\WhenValidatingMappingsInline.cs + + + Configuration\Inline\WhenViewingMappingPlans.cs + + + Configuration\WhenApplyingMapperConfigurations.cs + + + Configuration\WhenApplyingMapperConfigurationsIncorrectly.cs + + + Configuration\WhenConfiguringConstructorDataSources.cs + + + Configuration\WhenConfiguringDataSources.cs + + + Configuration\WhenConfiguringDataSourcesIncorrectly.cs + + + Configuration\WhenConfiguringDerivedTypes.cs + + + Configuration\WhenConfiguringDerivedTypesIncorrectly.cs + + + Configuration\WhenConfiguringEntityMapping.cs + + + Configuration\WhenConfiguringEnumMapping.cs + + + Configuration\WhenConfiguringExceptionHandling.cs + + + Configuration\WhenConfiguringMappingCallbacks.cs + + + Configuration\WhenConfiguringNameMatching.cs + + + Configuration\WhenConfiguringObjectCreation.cs + + + Configuration\WhenConfiguringObjectCreationCallbacks.cs + + + Configuration\WhenConfiguringObjectTracking.cs + + + Configuration\WhenConfiguringObjectTrackingIncorrectly.cs + + + Configuration\WhenConfiguringReverseDataSources.cs + + + Configuration\WhenConfiguringReverseDataSourcesIncorrectly.cs + + + Configuration\WhenConfiguringStringFormatting.cs + + + Configuration\WhenConfiguringTypeIdentifiers.cs + + + Configuration\WhenIgnoringMembers.cs + + + Configuration\WhenIgnoringMembersByFilter.cs + + + Configuration\WhenIgnoringMembersByGlobalFilter.cs + + + Configuration\WhenIgnoringMembersIncorrectly.cs + + + Configuration\WhenMappingToNull.cs + + + Configuration\WhenResolvingServices.cs + + + Configuration\WhenViewingMappingPlans.cs + + + Dictionaries\Configuration\WhenConfiguringDictionaryMappingIncorrectly.cs + + + Dictionaries\Configuration\WhenConfiguringNestedDictionaryMapping.cs + + + Dictionaries\Configuration\WhenConfiguringSourceDictionaryMapping.cs + + + Dictionaries\Configuration\WhenConfiguringTargetDictionaryMapping.cs + + + Dictionaries\WhenCreatingRootDictionaryMembers.cs + + + Dictionaries\WhenFlatteningToDictionaries.cs + + + Dictionaries\WhenMappingFromDictionariesOnToComplexTypes.cs + + + Dictionaries\WhenMappingFromDictionariesOnToEnumerableMembers.cs + + + Dictionaries\WhenMappingFromDictionariesOverComplexTypes.cs + + + Dictionaries\WhenMappingFromDictionariesToNewComplexTypeMembers.cs + + + Dictionaries\WhenMappingFromDictionariesToNewComplexTypes.cs + + + Dictionaries\WhenMappingFromDictionariesToNewEnumerableMembers.cs + + + Dictionaries\WhenMappingFromDictionariesToNewEnumerables.cs + + + Dictionaries\WhenMappingFromDictionaryMembers.cs + + + Dictionaries\WhenMappingOnToDictionaries.cs + + + Dictionaries\WhenMappingOnToDictionaryMembers.cs + + + Dictionaries\WhenMappingOverDictionaries.cs + + + Dictionaries\WhenMappingOverDictionaryMembers.cs + + + Dictionaries\WhenMappingToNewDictionaries.cs + + + Dictionaries\WhenMappingToNewDictionaryMembers.cs + + + Dictionaries\WhenUnflatteningFromDictionaries.cs + + + Dictionaries\WhenViewingDictionaryMappingPlans.cs + + + Extensions\Internal\WhenEquatingExpressions.cs + + + Extensions\Internal\WhenGeneratingVariableNames.cs + + + Extensions\WhenFlatteningToQueryStringViaExtensionMethods.cs + + + Extensions\WhenFlatteningViaExtensionMethods.cs + + + Extensions\WhenMappingViaExtensionMethods.cs + + + Extensions\WhenUnflatteningFromQueryStringsViaExtensionMethods.cs + + + Extensions\WhenUnflatteningViaExtensionMethods.cs + + + MapperCloning\WhenCloningConstructorDataSources.cs + + + MapperCloning\WhenCloningDataSources.cs + + + MapperCloning\WhenCloningDictionarySettings.cs + + + MapperCloning\WhenCloningMemberIgnores.cs + + + MapperCloning\WhenCloningObjectFactories.cs + + + MapperCloning\WhenCloningStringFormatting.cs + + + Members\MemberTestsBase.cs + + + Members\WhenCreatingTargetMembersFromExpressions.cs + + + Members\WhenDeterminingATypeIdentifier.cs + + + Members\WhenDeterminingRecursion.cs + + + Members\WhenFindingDataSources.cs + + + Members\WhenFindingSourceMembers.cs + + + Members\WhenFindingTargetMembers.cs + + + Members\WhenMatchingSourceToTargetMembers.cs + + + Properties\AssemblyInfo.cs + + + Reflection\WhenAccessingTypeInformation.cs + + + SimpleTypeConversion\WhenConvertingToBools.cs + + + SimpleTypeConversion\WhenConvertingToBytes.cs + + + SimpleTypeConversion\WhenConvertingToCharacters.cs + + + SimpleTypeConversion\WhenConvertingToDateTimes.cs + + + SimpleTypeConversion\WhenConvertingToDecimals.cs + + + SimpleTypeConversion\WhenConvertingToDoubles.cs + + + SimpleTypeConversion\WhenConvertingToEnums.cs + + + SimpleTypeConversion\WhenConvertingToFlagsEnums.cs + + + SimpleTypeConversion\WhenConvertingToGuids.cs + + + SimpleTypeConversion\WhenConvertingToInts.cs + + + SimpleTypeConversion\WhenConvertingToLongs.cs + + + SimpleTypeConversion\WhenConvertingToShorts.cs + + + SimpleTypeConversion\WhenConvertingToStrings.cs + + + Structs\Configuration\WhenConfiguringStructCreationCallbacks.cs + + + Structs\Configuration\WhenConfiguringStructDataSources.cs + + + Structs\Configuration\WhenConfiguringStructMappingCallbacks.cs + + + Structs\Dictionaries\WhenMappingFromDictionariesToStructs.cs + + + Structs\WhenMappingOnToStructMembers.cs + + + Structs\WhenMappingOnToStructs.cs + + + Structs\WhenMappingOverStructMembers.cs + + + Structs\WhenMappingOverStructs.cs + + + Structs\WhenMappingToNewStructMembers.cs + + + Structs\WhenMappingToNewStructs.cs + + + Structs\WhenMappingToStructEnumerables.cs + + + Structs\WhenMappingToUnmappableStructMembers.cs + + + TestClasses\Address.cs + + + TestClasses\CategoryDto.cs + + + TestClasses\CategoryEntity.cs + + + TestClasses\Child.cs + + + TestClasses\Customer.cs + + + TestClasses\CustomerViewModel.cs + + + TestClasses\DtoBase.cs + + + TestClasses\Earthworm.cs + + + TestClasses\EntityBase.cs + + + TestClasses\FacebookUser.cs + + + TestClasses\InternalField.cs + + + TestClasses\IPublicInterface.cs + + + TestClasses\MegaProduct.cs + + + TestClasses\MysteryCustomer.cs + + + TestClasses\MysteryCustomerViewModel.cs + + + TestClasses\Order.cs + + + TestClasses\OrderDto.cs + + + TestClasses\OrderEntity.cs + + + TestClasses\OrderItem.cs + + + TestClasses\OrderItemDto.cs + + + TestClasses\OrderItemEntity.cs + + + TestClasses\OrderUk.cs + + + TestClasses\OrderUs.cs + + + TestClasses\Parent.cs + + + TestClasses\PaymentTypeUk.cs + + + TestClasses\PaymentTypeUs.cs + + + TestClasses\Person.cs + + + TestClasses\PersonViewModel.cs + + + TestClasses\Product.cs + + + TestClasses\ProductDto.cs + + + TestClasses\ProductDtoMega.cs + + + TestClasses\ProductEntity.cs + + + TestClasses\PublicCtor.cs + + + TestClasses\PublicCtorStruct.cs + + + TestClasses\PublicEnumerable.cs + + + TestClasses\PublicField.cs + + + TestClasses\PublicGetMethod.cs + + + TestClasses\PublicImplementation.cs + + + TestClasses\PublicIndex.cs + + + TestClasses\PublicProperty.cs + + + TestClasses\PublicPropertyStruct.cs + + + TestClasses\PublicReadOnlyField.cs + + + TestClasses\PublicReadOnlyProperty.cs + + + TestClasses\PublicSealed.cs + + + TestClasses\PublicSetMethod.cs + + + TestClasses\PublicTwoFields.cs + + + TestClasses\PublicTwoFieldsStruct.cs + + + TestClasses\PublicTwoParamCtor.cs + + + TestClasses\PublicUnconstructable.cs + + + TestClasses\PublicWriteOnlyProperty.cs + + + TestClasses\SaveOrderItemRequest.cs + + + TestClasses\SaveOrderRequest.cs + + + TestClasses\Status.cs + + + TestClasses\StringKeyedDictionary.cs + + + TestClasses\Title.cs + + + TestClasses\TitleShortlist.cs + + + TestClasses\Wedding.cs + + + TestClasses\WeddingDto.cs + + + WhenAnalysingCollections.cs + + + WhenMappingCircularReferences.cs + + + WhenMappingDerivedTypes.cs + + + WhenMappingEntities.cs + + + WhenMappingOnToComplexTypeMembers.cs + + + WhenMappingOnToComplexTypes.cs + + + WhenMappingOnToEnumerableMembers.cs + + + WhenMappingOnToEnumerables.cs + + + WhenMappingOverComplexTypeMembers.cs + + + WhenMappingOverComplexTypes.cs + + + WhenMappingOverEnumerableMembers.cs + + + WhenMappingOverEnumerables.cs + + + WhenMappingToConstructors.cs + + + WhenMappingToMetaMembers.cs + + + WhenMappingToNewComplexTypeMembers.cs + + + WhenMappingToNewComplexTypes.cs + + + WhenMappingToNewEnumerableMembers.cs + + + WhenMappingToNewEnumerables.cs + + + WhenUnflatteningFromQueryStrings.cs + + + WhenUsingFactoryMethods.cs + + + WhenValidatingMappings.cs + + + WhenViewingMappingPlans.cs + + + WhenWorkingWithQueryStrings.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 28d4c8701..39082fdd9 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -220,6 +220,7 @@ + diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index b03c7d7c2..e9e20dc12 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -177,7 +177,7 @@ public void ShouldAllowConditionTypeTestsWhenMappingFromAnInterface() // See https://github.com/agileobjects/AgileMapper/issues/111 [Fact] - public void ShouldConditionallyApplyAToTargetConfiguredSimpleTypeConstant() + public void ShouldConditionallyApplyAToTargetSimpleTypeConstant() { using (var mapper = Mapper.CreateNew()) { @@ -195,7 +195,7 @@ public void ShouldConditionallyApplyAToTargetConfiguredSimpleTypeConstant() } [Fact] - public void ShouldConditionallyApplyAToTargetConfiguredSimpleType() + public void ShouldConditionallyApplyAToTargetSimpleType() { using (var mapper = Mapper.CreateNew()) { @@ -212,7 +212,7 @@ public void ShouldConditionallyApplyAToTargetConfiguredSimpleType() } [Fact] - public void ShouldConditionallyApplyAToTargetConfiguredNestedSimpleTypeExpression() + public void ShouldConditionallyApplyAToTargetNestedSimpleTypeExpression() { using (var mapper = Mapper.CreateNew()) { @@ -236,7 +236,7 @@ public void ShouldConditionallyApplyAToTargetConfiguredNestedSimpleTypeExpressio } [Fact] - public void ShouldConditionallyApplyAToTargetConfiguredSimpleTypeExpressionInAComplexTypeList() + public void ShouldConditionallyApplyAToTargetSimpleTypeExpressionToAComplexTypeListMember() { using (var mapper = Mapper.CreateNew()) { @@ -536,7 +536,7 @@ public void ShouldApplyAConfiguredExpressionToAnArray() .From>() .To>() #if NETCOREAPP2_0 - .Map(ctx => ctx.Source.Value.Split(':', System.StringSplitOptions.None)) + .Map(ctx => ctx.Source.Value.Split(':', StringSplitOptions.None)) #else .Map(ctx => ctx.Source.Value.Split(':')) #endif @@ -1213,7 +1213,7 @@ public void ShouldApplyAConfiguredSourceInterfaceMember() // See https://github.com/agileobjects/AgileMapper/issues/64 [Fact] - public void ShouldApplyAConfiguredRootSource() + public void ShouldApplyAConfiguredToTargetDataSource() { using (var mapper = Mapper.CreateNew()) { @@ -1235,7 +1235,7 @@ public void ShouldApplyAConfiguredRootSource() } [Fact] - public void ShouldApplyANestedOverwriteConfiguredRootSource() + public void ShouldApplyANestedOverwriteConfiguredToTargetDataSource() { using (var mapper = Mapper.CreateNew()) { @@ -1395,7 +1395,7 @@ public void ShouldApplyAConfiguredRootSourceToANestedMember() } [Fact] - public void ShouldApplyAConfiguredRootSourceToAnEnumerableElement() + public void ShouldApplyAToTargetComplexTypeToAComplexTypeEnumerableElement() { using (var mapper = Mapper.CreateNew()) { @@ -1426,7 +1426,7 @@ public void ShouldApplyAConfiguredRootSourceToAnEnumerableElement() } [Fact] - public void ShouldApplyAConfiguredEnumerableRootSource() + public void ShouldApplyAToTargetComplexTypeEnumerable() { using (var mapper = Mapper.CreateNew()) { @@ -1463,7 +1463,7 @@ public void ShouldApplyAConfiguredEnumerableRootSource() } [Fact] - public void ShouldApplyMultipleConfiguredComplexTypeRootSources() + public void ShouldApplyMultipleToTargetComplexTypes() { using (var mapper = Mapper.CreateNew()) { @@ -1490,7 +1490,7 @@ public void ShouldApplyMultipleConfiguredComplexTypeRootSources() } [Fact] - public void ShouldApplyMultipleConfiguredEnumerableRootSources() + public void ShouldApplyMultipleToTargetSimpleTypeEnumerables() { using (var mapper = Mapper.CreateNew()) { @@ -1619,6 +1619,40 @@ public void ShouldHandleDeepNestedRuntimeTypedMembersWithACachedMappingPlan() } } + [Fact] + public void ShouldApplyAToTargetSimpleTypeToANestedComplexTypeMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To>() + .Map(ctx => PublicEnumerable.Parse(ctx.Source)).ToTarget(); + + mapper.GetPlanFor>().ToANew>>(); + + var source = new PublicField { Value = "1,2,3" }; + var result = mapper.Map(source).ToANew>>(); + + result.ShouldNotBeNull(); + result.Value.ShouldNotBeNull(); + result.Value.ShouldBe(1, 2, 3); + } + } + + [Fact] + public void ShouldConditionallyApplyAToTargetSimpleTypeToANestedComplexTypeMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To>() + .If(cxt => cxt.Source.Contains(',')) + .Map(ctx => PublicEnumerable.Parse(ctx.Source)).ToTarget(); + + mapper.GetPlanFor>().ToANew>>(); + } + } + #region Helper Classes internal class IdTester diff --git a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs index 0057b8f72..c2f20b8d1 100644 --- a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs +++ b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs @@ -53,7 +53,7 @@ private IQualifiedMember GetMatchingSourceMember( var childMapperData = new ChildMemberMapperData(targetMember, rootMapperData); var childMappingContext = rootMappingData.GetChildMappingData(childMapperData); - return SourceMemberMatcher.GetMatchFor(childMappingContext, out _); + return SourceMemberMatcher.GetMatchFor(childMappingContext).SourceMember; } #region Helper Classes diff --git a/AgileMapper.UnitTests/TestClasses/PublicEnumerable.cs b/AgileMapper.UnitTests/TestClasses/PublicEnumerable.cs new file mode 100644 index 000000000..f8d385787 --- /dev/null +++ b/AgileMapper.UnitTests/TestClasses/PublicEnumerable.cs @@ -0,0 +1,34 @@ +namespace AgileObjects.AgileMapper.UnitTests.TestClasses +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + internal class PublicEnumerable : IEnumerable + { + private readonly List _items; + + public PublicEnumerable() + : this(new List()) + { + } + + private PublicEnumerable(List items) + { + _items = items; + } + + public static PublicEnumerable Parse(string values) + { + return new PublicEnumerable(values + .Split(',') + .Select(v => (T)Convert.ChangeType(v, typeof(T))) + .ToList()); + } + + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index aba1e77f6..4eb2ed3b5 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -116,16 +116,16 @@ private void ThrowIfRedundantSourceMember(ConfiguredLambdaInfo valueLambdaInfo, var targetMemberMapperData = new ChildMemberMapperData(targetMember, mappingData.MapperData); var targetMemberMappingData = mappingData.GetChildMappingData(targetMemberMapperData); - var bestMatchingSourceMember = SourceMemberMatcher.GetMatchFor(targetMemberMappingData, out _); + var bestSourceMemberMatch = SourceMemberMatcher.GetMatchFor(targetMemberMappingData); - if (bestMatchingSourceMember == null) + if (!bestSourceMemberMatch.IsUseable) { return; } var sourceMember = sourceMemberLambda.ToSourceMember(MapperContext); - if (!bestMatchingSourceMember.Matches(sourceMember)) + if (!bestSourceMemberMatch.SourceMember.Matches(sourceMember)) { return; } diff --git a/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs b/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs index b87004884..2b880d6a2 100644 --- a/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs +++ b/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs @@ -3,14 +3,10 @@ using System; using System.Collections; using System.Collections.Generic; -#if NET35 - using System.Linq; -#endif using System.Linq.Expressions; using Api.Configuration; using Api.Configuration.Projection; using Caching; - using Extensions; #if NET35 using Extensions.Internal; #endif diff --git a/AgileMapper/Configuration/ParametersSwapper.cs b/AgileMapper/Configuration/ParametersSwapper.cs index 88e1c8507..45d657d4b 100644 --- a/AgileMapper/Configuration/ParametersSwapper.cs +++ b/AgileMapper/Configuration/ParametersSwapper.cs @@ -3,16 +3,16 @@ namespace AgileObjects.AgileMapper.Configuration using System; using System.Collections.Generic; using System.Linq; - using Extensions; - using Extensions.Internal; - using Members; - using NetStandardPolyfills; - using ObjectPopulation; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions; + using Extensions.Internal; + using Members; + using NetStandardPolyfills; + using ObjectPopulation; using static Members.Member; internal class ParametersSwapper @@ -170,7 +170,7 @@ private static Expression ReplaceParameters( private static MappingContextInfo GetAppropriateMappingContext(SwapArgs swapArgs) { - if (swapArgs.ContextTypes.All(t => t.IsSimple())) + if (swapArgs.SourceType.IsSimple()) { return GetSimpleTypesMappingContextInfo(swapArgs); } @@ -298,8 +298,8 @@ public MappingContextInfo(SwapArgs swapArgs, Expression contextAccess) : this( swapArgs, contextAccess, - GetValueAccess(swapArgs.GetSourceAccess(contextAccess), swapArgs.ContextTypes[0]), - GetValueAccess(swapArgs.GetTargetAccess(contextAccess), swapArgs.ContextTypes[1])) + GetValueAccess(swapArgs.GetSourceAccess(contextAccess), swapArgs.SourceType), + GetValueAccess(swapArgs.GetTargetAccess(contextAccess), swapArgs.TargetType)) { } @@ -370,6 +370,10 @@ public SwapArgs( public Type[] ContextTypes { get; } + public Type SourceType => ContextTypes[0]; + + public Type TargetType => ContextTypes[1]; + public IMemberMapperData MapperData { get; } public Func TargetValueFactory { get; } @@ -386,10 +390,10 @@ public Expression GetTypedContextAccess(Expression contextAccess) => MapperData.GetTypedContextAccess(contextAccess, ContextTypes); public Expression GetSourceAccess(Expression contextAccess) - => MapperData.GetSourceAccess(contextAccess, ContextTypes[0]); + => MapperData.GetSourceAccess(contextAccess, SourceType); public Expression GetTargetAccess(Expression contextAccess) - => TargetValueFactory.Invoke(MapperData, contextAccess, ContextTypes[1]); + => TargetValueFactory.Invoke(MapperData, contextAccess, TargetType); } #endregion diff --git a/AgileMapper/DataSources/AdHocDataSource.cs b/AgileMapper/DataSources/AdHocDataSource.cs index ecdcd95ef..65089290f 100644 --- a/AgileMapper/DataSources/AdHocDataSource.cs +++ b/AgileMapper/DataSources/AdHocDataSource.cs @@ -1,12 +1,12 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Collections.Generic; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Members; internal class AdHocDataSource : DataSourceBase { diff --git a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs index 27c0a0a01..5103d28cd 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs @@ -26,18 +26,15 @@ public DataSourceFindContext(IChildMemberMappingData childMappingData) GetConfiguredDataSources(originalChildMapperData)); } - private IList GetConfiguredDataSources(IMemberMapperData mapperData) - { - return MapperData - .MapperContext - .UserConfigurations - .GetDataSources(mapperData); - } + private IList GetConfiguredDataSources(IMemberMapperData mapperData) + => MapperContext.UserConfigurations.GetDataSources(mapperData); public IChildMemberMappingData ChildMappingData { get; } public IMemberMapperData MapperData => ChildMappingData.MapperData; + public MapperContext MapperContext => MapperData.MapperContext; + public int DataSourceIndex { get; set; } public bool StopFind { get; set; } @@ -59,7 +56,7 @@ public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberM return new ComplexTypeMappingDataSource(foundDataSource, DataSourceIndex, mappingData); } - if (childTargetMember.IsEnumerable) + if (childTargetMember.IsEnumerable && foundDataSource.SourceMember.IsEnumerable) { return new EnumerableMappingDataSource(foundDataSource, DataSourceIndex, mappingData); } diff --git a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs index e581cf8d3..8d0421445 100644 --- a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs @@ -214,16 +214,17 @@ private static bool TryGetMetaMember( var memberMappingData = currentMappingData.GetChildMappingData(childMemberMapperData); - currentSourceMember = SourceMemberMatcher.GetMatchFor( + var currentSourceMemberMatch = SourceMemberMatcher.GetMatchFor( memberMappingData, - out _, searchParentContexts: false); - if (currentSourceMember == null) + if (!currentSourceMemberMatch.IsUseable) { return false; } + currentSourceMember = currentSourceMemberMatch.SourceMember; + currentMemberPart = new SourceMemberMetaMemberPart( currentSourceMember, currentMapperData, diff --git a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs index 18aec53a3..0ec0b02da 100644 --- a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs @@ -1,8 +1,8 @@ namespace AgileObjects.AgileMapper.DataSources.Finders { + using System.Collections.Generic; using Extensions.Internal; using Members; - using System.Collections.Generic; internal struct SourceMemberDataSourceFinder : IDataSourceFinder { @@ -13,11 +13,11 @@ public IEnumerable FindFor(DataSourceFindContext context) yield break; } - var matchingSourceMemberDataSource = GetSourceMemberDataSourceOrNull(context); + var matchingSourceMemberDataSource = GetSourceMemberDataSource(context, out var hasUseableSourceMember); var configuredDataSources = context.ConfiguredDataSources; var targetMember = context.MapperData.TargetMember; - if ((matchingSourceMemberDataSource == null) || + if (!hasUseableSourceMember || configuredDataSources.Any(cds => cds.IsSameAs(matchingSourceMemberDataSource))) { if (context.DataSourceIndex == 0) @@ -32,7 +32,10 @@ public IEnumerable FindFor(DataSourceFindContext context) yield return context.GetFallbackDataSource(); } - yield break; + if (matchingSourceMemberDataSource.SourceMember == null) + { + yield break; + } } if (matchingSourceMemberDataSource.SourceMember.IsSimple && @@ -65,21 +68,23 @@ public IEnumerable FindFor(DataSourceFindContext context) } } - private static IDataSource GetSourceMemberDataSourceOrNull(DataSourceFindContext context) + private static IDataSource GetSourceMemberDataSource( + DataSourceFindContext context, + out bool hasUseableSourceMember) { - var bestMatchingSourceMember = SourceMemberMatcher.GetMatchFor( - context.ChildMappingData, - out var contextMappingData); + var bestSourceMemberMatch = SourceMemberMatcher.GetMatchFor(context.ChildMappingData); + hasUseableSourceMember = bestSourceMemberMatch.IsUseable; - if (bestMatchingSourceMember == null) + if (hasUseableSourceMember) { - return null; + return context.GetFinalDataSource( + bestSourceMemberMatch.CreateDataSource(), + bestSourceMemberMatch.ContextMappingData); } - var sourceMemberDataSource = SourceMemberDataSource - .For(bestMatchingSourceMember, contextMappingData.MapperData); - - return context.GetFinalDataSource(sourceMemberDataSource, contextMappingData); + return new AdHocDataSource( + bestSourceMemberMatch.SourceMember, + Constants.EmptyExpression); } private static bool UseFallbackComplexTypeMappingDataSource(QualifiedMember targetMember) diff --git a/AgileMapper/DataSources/IDataSource.cs b/AgileMapper/DataSources/IDataSource.cs index f343b67ee..218c1249b 100644 --- a/AgileMapper/DataSources/IDataSource.cs +++ b/AgileMapper/DataSources/IDataSource.cs @@ -1,13 +1,13 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Collections.Generic; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; internal interface IDataSource : IConditionallyChainable { diff --git a/AgileMapper/DataSources/SourceMemberDataSource.cs b/AgileMapper/DataSources/SourceMemberDataSource.cs index 823e734ad..7d0cefc3d 100644 --- a/AgileMapper/DataSources/SourceMemberDataSource.cs +++ b/AgileMapper/DataSources/SourceMemberDataSource.cs @@ -13,7 +13,7 @@ internal class SourceMemberDataSource : DataSourceBase { - private SourceMemberDataSource( + public SourceMemberDataSource( IQualifiedMember sourceMember, Expression sourceMemberValue, IMemberMapperData mapperData) @@ -63,18 +63,5 @@ private static Expression GetRuntimeTypeCheck(UnaryExpression cast, IMemberMappe return memberHasRuntimeType; } - - public static SourceMemberDataSource For(IQualifiedMember sourceMember, IMemberMapperData mapperData) - { - sourceMember = sourceMember.RelativeTo(mapperData.SourceMember); - - var sourceMemberValue = sourceMember - .GetQualifiedAccess(mapperData) - .GetConversionTo(sourceMember.Type); - - var sourceMemberDataSource = new SourceMemberDataSource(sourceMember, sourceMemberValue, mapperData); - - return sourceMemberDataSource; - } } } \ No newline at end of file diff --git a/AgileMapper/Members/SourceMemberMatch.cs b/AgileMapper/Members/SourceMemberMatch.cs new file mode 100644 index 000000000..29d387ff9 --- /dev/null +++ b/AgileMapper/Members/SourceMemberMatch.cs @@ -0,0 +1,62 @@ +namespace AgileObjects.AgileMapper.Members +{ +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using DataSources; + using Extensions.Internal; + + internal class SourceMemberMatch + { + public static readonly SourceMemberMatch Null = new SourceMemberMatch(); + + private SourceMemberMatch() + { + } + + public SourceMemberMatch( + IQualifiedMember sourceMember, + IChildMemberMappingData contextMappingData, + bool isUseable = true) + { + SourceMember = GetFinalSourceMember(sourceMember, contextMappingData.MapperData); + ContextMappingData = contextMappingData; + IsUseable = isUseable; + } + + private static IQualifiedMember GetFinalSourceMember( + IQualifiedMember sourceMember, + IMemberMapperData targetMapperData) + { + return targetMapperData + .MapperContext + .QualifiedMemberFactory + .GetFinalSourceMember(sourceMember, targetMapperData.TargetMember); + } + + public IQualifiedMember SourceMember { get; } + + public IChildMemberMappingData ContextMappingData { get; } + + public bool IsUseable { get; } + + public IDataSource CreateDataSource() + { + var mapperData = ContextMappingData.MapperData; + var sourceMember = SourceMember.RelativeTo(mapperData.SourceMember); + + var sourceMemberValue = sourceMember + .GetQualifiedAccess(mapperData) + .GetConversionTo(sourceMember.Type); + + var sourceMemberDataSource = new SourceMemberDataSource( + sourceMember, + sourceMemberValue, + mapperData); + + return sourceMemberDataSource; + } + } +} \ No newline at end of file diff --git a/AgileMapper/Members/SourceMemberMatcher.cs b/AgileMapper/Members/SourceMemberMatcher.cs index fa5e0a1d2..617953892 100644 --- a/AgileMapper/Members/SourceMemberMatcher.cs +++ b/AgileMapper/Members/SourceMemberMatcher.cs @@ -4,90 +4,65 @@ using System.Collections.Generic; using System.Linq; using Extensions; + using NetStandardPolyfills; internal static class SourceMemberMatcher { - public static IQualifiedMember GetMatchFor( - IChildMemberMappingData targetData, - out IChildMemberMappingData contextMappingData, + public static SourceMemberMatch GetMatchFor( + IChildMemberMappingData targetMappingData, bool searchParentContexts = true) { - var parentSourceMember = targetData.MapperData.SourceMember; + var parentSourceMember = targetMappingData.MapperData.SourceMember; if (parentSourceMember.IsSimple) { - contextMappingData = null; - return null; - } - - if (ExactMatchingSourceMemberExists(parentSourceMember, targetData, out var matchingMember)) - { - contextMappingData = targetData; - return GetFinalSourceMember(matchingMember, targetData); + return SourceMemberMatch.Null; } - matchingMember = EnumerateSourceMembers(parentSourceMember, targetData) - .FirstOrDefault(sm => IsMatchingMember(sm, targetData.MapperData)); - - if (matchingMember != null) + if (ExactMatchingMemberExists(parentSourceMember, targetMappingData, out var matchingMember) && + TypesAreCompatible(matchingMember.Type, targetMappingData.MapperData)) { - contextMappingData = targetData; - return GetFinalSourceMember(matchingMember, targetData); + return new SourceMemberMatch(matchingMember, targetMappingData); } - if (searchParentContexts) + if (TryFindSourceMemberMatch( + targetMappingData, + parentSourceMember, + ref matchingMember, + out var sourceMemberMatch)) { - return GetParentContextMatchOrNull(targetData, out contextMappingData); + return sourceMemberMatch; } - contextMappingData = null; - return null; - } - - private static IQualifiedMember GetParentContextMatchOrNull( - IChildMemberMappingData targetData, - out IChildMemberMappingData contextMappingData) - { - var mappingData = targetData.Parent; - - while (mappingData.Parent != null) + if (searchParentContexts && + TryFindParentContextSourceMemberMatch(targetMappingData, ref matchingMember, out sourceMemberMatch)) { - if (mappingData.MapperData.IsEntryPoint || - mappingData.MapperData.TargetMemberIsEnumerableElement()) - { - break; - } - - mappingData = mappingData.Parent; - - var childMapperData = new ChildMemberMapperData(targetData.MapperData.TargetMember, mappingData.MapperData); - contextMappingData = mappingData.GetChildMappingData(childMapperData); - - var matchingMember = EnumerateSourceMembers(mappingData.MapperData.SourceMember, contextMappingData) - .FirstOrDefault(sm => IsMatchingMember(sm, targetData.MapperData)); - - if (matchingMember != null) - { - return GetFinalSourceMember(matchingMember, targetData); - } + return sourceMemberMatch; } - contextMappingData = null; - return null; + return (matchingMember != null) + ? new SourceMemberMatch(matchingMember, targetMappingData, isUseable: false) + : SourceMemberMatch.Null; } - private static bool ExactMatchingSourceMemberExists( + private static bool ExactMatchingMemberExists( IQualifiedMember parentSourceMember, IChildMemberMappingData targetData, out IQualifiedMember matchingMember) { + var mapperData = targetData.MapperData; + + var matcher = mapperData.TargetType.IsAssignableTo(mapperData.SourceType) + ? (Func)MembersAreTheSame + : MembersMatch; + var sourceMember = QuerySourceMembers( parentSourceMember, targetData, - MembersMatch) + matcher) .FirstOrDefault(); - if ((sourceMember == null) || !TypesAreCompatible(sourceMember.Type, targetData.MapperData)) + if (sourceMember == null) { matchingMember = null; return false; @@ -97,9 +72,12 @@ private static bool ExactMatchingSourceMemberExists( return true; } + private static bool MembersAreTheSame(IChildMemberMappingData mappingData, Member sourceMember) + => mappingData.MapperData.TargetMember.LeafMember.Equals(sourceMember); + private static bool MembersMatch(IChildMemberMappingData mappingData, Member sourceMember) { - if (mappingData.MapperData.TargetMember.LeafMember.Equals(sourceMember)) + if (MembersAreTheSame(mappingData, sourceMember)) { return true; } @@ -127,15 +105,82 @@ private static IEnumerable QuerySourceMembers( : members.Filter(m => m.MemberType != MemberType.GetMethod); } - private static IQualifiedMember GetFinalSourceMember( - IQualifiedMember sourceMember, - IChildMemberMappingData targetData) + private static bool TryFindParentContextSourceMemberMatch( + IChildMemberMappingData targetMappingData, + ref IQualifiedMember matchingMember, + out SourceMemberMatch sourceMemberMatch) { - return targetData - .MapperData - .MapperContext - .QualifiedMemberFactory - .GetFinalSourceMember(sourceMember, targetData.MapperData.TargetMember); + var targetMember = targetMappingData.MapperData.TargetMember; + var mappingData = targetMappingData.Parent; + + while (mappingData.Parent != null) + { + if (mappingData.MapperData.IsEntryPoint || + mappingData.MapperData.TargetMemberIsEnumerableElement()) + { + break; + } + + mappingData = mappingData.Parent; + + var childMapperData = new ChildMemberMapperData(targetMember, mappingData.MapperData); + var contextMappingData = mappingData.GetChildMappingData(childMapperData); + + if (TryFindSourceMemberMatch( + targetMappingData, + mappingData.MapperData.SourceMember, + contextMappingData, + ref matchingMember, + out sourceMemberMatch)) + { + return true; + } + } + + sourceMemberMatch = null; + return false; + } + + private static bool TryFindSourceMemberMatch( + IChildMemberMappingData targetMappingData, + IQualifiedMember parentSourceMember, + ref IQualifiedMember matchingMember, + out SourceMemberMatch sourceMemberMatch) + { + return TryFindSourceMemberMatch( + targetMappingData, + parentSourceMember, + targetMappingData, + ref matchingMember, + out sourceMemberMatch); + } + + private static bool TryFindSourceMemberMatch( + IChildMemberMappingData targetMappingData, + IQualifiedMember parentSourceMember, + IChildMemberMappingData contextMappingData, + ref IQualifiedMember matchingMember, + out SourceMemberMatch sourceMemberMatch) + { + var candidateSourceMembers = EnumerateSourceMembers(parentSourceMember, contextMappingData) + .Where(targetMappingData.MapperData.TargetMember.Matches); + + foreach (var sourceMember in candidateSourceMembers) + { + if (TypesAreCompatible(sourceMember.Type, targetMappingData.MapperData)) + { + sourceMemberMatch = new SourceMemberMatch(sourceMember, contextMappingData); + return true; + } + + if (matchingMember == null) + { + matchingMember = sourceMember; + } + } + + sourceMemberMatch = null; + return false; } private static IEnumerable EnumerateSourceMembers( @@ -201,9 +246,6 @@ private static bool MembersHaveCompatibleTypes(IChildMemberMappingData rootData, return targetMember.Type == typeof(object); } - private static bool IsMatchingMember(IQualifiedMember sourceMember, IMemberMapperData mapperData) - => mapperData.TargetMember.Matches(sourceMember) && TypesAreCompatible(sourceMember.Type, mapperData); - private static bool TypesAreCompatible(Type sourceType, IMemberMapperData mapperData) => mapperData.CanConvert(sourceType, mapperData.TargetMember.Type); } diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index b046f0954..fc755e267 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -5,6 +5,11 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Globalization; using System.Linq; using System.Reflection; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif using DataSources; using Enumerables; using Extensions; @@ -14,11 +19,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using Members.Sources; using NetStandardPolyfills; using ReadableExpressions.Extensions; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif using static Members.Member; internal class ObjectMapperData : BasicMapperData, IMemberMapperData