From e4a0a6c33596297845ffc4120a0130e0443c99e3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 6 Dec 2017 19:17:59 +0000 Subject: [PATCH 01/74] Support for flattening single-member nested complex types to an untyped dictionary --- .../Dictionaries/WhenMappingToNewDictionaries.cs | 14 ++++++++++++++ AgileMapper/Members/DictionaryTargetMember.cs | 11 ++++++----- .../MappingExpressionFactoryBase.cs | 8 ++++---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs index 5eb63865c..28f48f790 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs @@ -58,10 +58,24 @@ public void ShouldMapNestedSimpleTypeMembersToATypedDictionary() var result = Mapper.Map(source).ToANew>(); result["Name"].ShouldBe("Eddie"); + result.ContainsKey("Address").ShouldBeFalse(); result["Address.Line1"].ShouldBe("Customer house"); result["Address.Line2"].ShouldBeNull(); } + [Fact] + public void ShouldMapNestedSimpleTypeMembersToAnUntypedDictionary() + { + var source = new PublicProperty> + { + Value = new PublicField { Value = 12345 } + }; + var result = Mapper.Map(source).ToANew>(); + + result.ContainsKey("Value").ShouldBeFalse(); + result["Value.Value"].ShouldBe(12345); + } + [Fact] public void ShouldMapASimpleTypeArrayToAnUntypedDictionary() { diff --git a/AgileMapper/Members/DictionaryTargetMember.cs b/AgileMapper/Members/DictionaryTargetMember.cs index 75273f46b..de20588f5 100644 --- a/AgileMapper/Members/DictionaryTargetMember.cs +++ b/AgileMapper/Members/DictionaryTargetMember.cs @@ -256,7 +256,7 @@ public override Expression GetPopulation(Expression value, IMemberMapperData map return keyedAssignment; } - private bool ValueIsFlattening(Expression value, out BlockExpression flattening) + private bool ValueIsFlattening(Expression value, out Expression flattening) { if (!(HasObjectEntries || HasSimpleEntries)) { @@ -268,9 +268,10 @@ private bool ValueIsFlattening(Expression value, out BlockExpression flattening) if (value.NodeType == ExpressionType.Block) { - flattening = (BlockExpression)value; - blockParameters = flattening.Variables; - value = flattening.Expressions[0]; + flattening = value; + var flatteningBlock = (BlockExpression)flattening; + blockParameters = flatteningBlock.Variables; + value = flatteningBlock.Expressions[0]; } else { @@ -289,7 +290,7 @@ private bool ValueIsFlattening(Expression value, out BlockExpression flattening) flattening = blockParameters.Any() ? Expression.Block(blockParameters, flatteningExpressions) : flatteningExpressions.HasOne() - ? (BlockExpression)flatteningExpressions[0] + ? flatteningExpressions[0] : Expression.Block(flatteningExpressions); return true; diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 42ab0db78..42796fee5 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -145,12 +145,12 @@ private Expression GetMappingBlock(IList mappingExpressions, Mapping goto CreateFullMappingBlock; } - var localVariableAssignment = mappingExpressions.First(exp => exp.NodeType == ExpressionType.Assign); + var firstAssignment = (BinaryExpression)mappingExpressions.First(exp => exp.NodeType == ExpressionType.Assign); - if (mappingExpressions.Last() == localVariableAssignment) + if ((firstAssignment.Left.NodeType == ExpressionType.Parameter) && + (mappingExpressions.Last() == firstAssignment)) { - var assignedValue = ((BinaryExpression)localVariableAssignment).Right; - returnExpression = GetReturnExpression(assignedValue, mappingExtras); + returnExpression = GetReturnExpression(firstAssignment.Right, mappingExtras); if (mappingExpressions.HasOne()) { From 680f0ee9093e7c08b6e8f9692e881fb55a59ac86 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 6 Dec 2017 19:45:07 +0000 Subject: [PATCH 02/74] Support for mapping from non-generic IDictionary<,> implementations, e.g. ExpandoObject --- .../AgileMapper.UnitTests.csproj | 7 ++- ...WhenMappingFromDictionariesOverObjects.cs} | 2 +- ...henMappingFromDictionariesToNewObjects.cs} | 62 +++++++++++-------- ...WhenMergingFromDictionariesOnToObjects.cs} | 2 +- .../Dynamics/WhenMappingFromDynamics.cs | 20 ++++++ .../DictionaryEntryVariablePair.cs | 5 +- .../Extensions/ExpressionExtensions.cs | 7 ++- AgileMapper/Extensions/TypeExtensions.cs | 27 ++++---- 8 files changed, 87 insertions(+), 45 deletions(-) rename AgileMapper.UnitTests/Dictionaries/{WhenOverwritingObjectsFromDictionaries.cs => WhenMappingFromDictionariesOverObjects.cs} (97%) rename AgileMapper.UnitTests/Dictionaries/{WhenMappingNewObjectsFromDictionaries.cs => WhenMappingFromDictionariesToNewObjects.cs} (87%) rename AgileMapper.UnitTests/Dictionaries/{WhenMergingObjectsFromDictionaries.cs => WhenMergingFromDictionariesOnToObjects.cs} (98%) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 9c8402f51..58be40a98 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -96,8 +96,9 @@ - - + + + @@ -180,7 +181,7 @@ - + diff --git a/AgileMapper.UnitTests/Dictionaries/WhenOverwritingObjectsFromDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverObjects.cs similarity index 97% rename from AgileMapper.UnitTests/Dictionaries/WhenOverwritingObjectsFromDictionaries.cs rename to AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverObjects.cs index dcd790f0a..07cefea41 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenOverwritingObjectsFromDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverObjects.cs @@ -6,7 +6,7 @@ using TestClasses; using Xunit; - public class WhenOverwritingObjectsFromDictionaries + public class WhenMappingFromDictionariesOverObjects { [Fact] public void ShouldPopulateADateTimeMemberFromAnUntypedEntry() diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingNewObjectsFromDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs similarity index 87% rename from AgileMapper.UnitTests/Dictionaries/WhenMappingNewObjectsFromDictionaries.cs rename to AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs index 02d691afc..913529995 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingNewObjectsFromDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs @@ -8,10 +8,10 @@ using TestClasses; using Xunit; - public class WhenMappingNewObjectsFromDictionaries + public class WhenMappingFromDictionariesToNewObjects { [Fact] - public void ShouldPopulateAnIntMemberFromATypedEntry() + public void ShouldMapToAnIntMemberFromATypedEntry() { var source = new Dictionary { ["Value"] = 123 }; var result = Mapper.Map(source).ToANew>(); @@ -21,7 +21,17 @@ public void ShouldPopulateAnIntMemberFromATypedEntry() } [Fact] - public void ShouldPopulateAStringMemberFromATypedEntryCaseInsensitively() + public void ShouldMapToAnIntMemberFromADictionaryImplementationTypedEntry() + { + var source = new StringKeyedDictionary { ["Value"] = 999 }; + var result = Mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Value.ShouldBe(999L); + } + + [Fact] + public void ShouldMapToAStringMemberFromATypedEntryCaseInsensitively() { var source = new Dictionary { ["value"] = "Hello" }; var result = Mapper.Map(source).ToANew>(); @@ -30,7 +40,7 @@ public void ShouldPopulateAStringMemberFromATypedEntryCaseInsensitively() } [Fact] - public void ShouldPopulateAStringSetMethodFromATypedEntry() + public void ShouldMapToAStringSetMethodFromATypedEntry() { var source = new Dictionary { ["SetValue"] = "Goodbye" }; var result = Mapper.Map(source).ToANew>(); @@ -39,7 +49,7 @@ public void ShouldPopulateAStringSetMethodFromATypedEntry() } [Fact] - public void ShouldPopulateANestedStringMemberFromTypedDottedEntries() + public void ShouldMapToANestedStringMemberFromTypedDottedEntries() { var source = new Dictionary { ["Value.Value"] = "Over there!" }; var result = Mapper.Map(source).ToANew>>(); @@ -48,7 +58,7 @@ public void ShouldPopulateANestedStringMemberFromTypedDottedEntries() } [Fact] - public void ShouldPopulateANestedBoolMemberFromUntypedDottedEntries() + public void ShouldMapToANestedBoolMemberFromUntypedDottedEntries() { var source = new Dictionary { ["Value.Value"] = "true" }; var result = Mapper.Map(source).ToANew>>(); @@ -77,7 +87,7 @@ public void ShouldConvertASimpleTypeMemberValueFromObject() } [Fact] - public void ShouldPopulateASimpleTypeCollectionFromATypedSourceArray() + public void ShouldMapToASimpleTypeCollectionFromATypedSourceArray() { var source = new Dictionary { ["Value"] = new long[] { 4, 5, 6 } }; var result = Mapper.Map(source).ToANew>>(); @@ -86,7 +96,7 @@ public void ShouldPopulateASimpleTypeCollectionFromATypedSourceArray() } [Fact] - public void ShouldPopulateASimpleTypeListFromNullableTypedSourceEntries() + public void ShouldMapToASimpleTypeListFromNullableTypedSourceEntries() { var source = new Dictionary { @@ -100,7 +110,7 @@ public void ShouldPopulateASimpleTypeListFromNullableTypedSourceEntries() } [Fact] - public void ShouldPopulateASimpleTypeEnumerableFromAnUntypedSourceArray() + public void ShouldMapToASimpleTypeEnumerableFromAnUntypedSourceArray() { var source = new Dictionary { ["Value"] = new[] { 1, 2, 3 } }; var result = Mapper.Map(source).ToANew>>(); @@ -109,7 +119,7 @@ public void ShouldPopulateASimpleTypeEnumerableFromAnUntypedSourceArray() } [Fact] - public void ShouldPopulateASimpleTypeArrayFromAConvertibleTypedSourceEnumerable() + public void ShouldMapToASimpleTypeArrayFromAConvertibleTypedSourceEnumerable() { var source = new Dictionary> { ["Value"] = new[] { 4, 5, 6 } }; var result = Mapper.Map(source).ToANew>(); @@ -118,7 +128,7 @@ public void ShouldPopulateASimpleTypeArrayFromAConvertibleTypedSourceEnumerable( } [Fact] - public void ShouldPopulateAComplexTypeArrayFromAConvertibleTypedSourceEnumerable() + public void ShouldMapToAComplexTypeArrayFromAConvertibleTypedSourceEnumerable() { var source = new Dictionary> { @@ -137,7 +147,7 @@ public void ShouldPopulateAComplexTypeArrayFromAConvertibleTypedSourceEnumerable } [Fact] - public void ShouldPopulateARootSimpleTypeEnumerableFromTypedEntries() + public void ShouldMapToARootSimpleTypeEnumerableFromTypedEntries() { var source = new Dictionary { @@ -151,7 +161,7 @@ public void ShouldPopulateARootSimpleTypeEnumerableFromTypedEntries() } [Fact] - public void ShouldPopulateANestedSimpleTypeListFromTypedEntries() + public void ShouldMapToANestedSimpleTypeListFromTypedEntries() { var source = new Dictionary { @@ -165,7 +175,7 @@ public void ShouldPopulateANestedSimpleTypeListFromTypedEntries() } [Fact] - public void ShouldPopulateANestedSimpleTypeCollectionFromConvertibleTypedEntries() + public void ShouldMapToANestedSimpleTypeCollectionFromConvertibleTypedEntries() { var now = DateTime.Now; @@ -184,7 +194,7 @@ public void ShouldPopulateANestedSimpleTypeCollectionFromConvertibleTypedEntries } [Fact] - public void ShouldPopulateARootSimpleTypeArrayFromConvertibleTypedEntries() + public void ShouldMapToARootSimpleTypeArrayFromConvertibleTypedEntries() { var source = new Dictionary { @@ -201,7 +211,7 @@ public void ShouldPopulateARootSimpleTypeArrayFromConvertibleTypedEntries() } [Fact] - public void ShouldPopulateARootComplexTypeCollectionFromTypedEntries() + public void ShouldMapToARootComplexTypeCollectionFromTypedEntries() { var source = new Dictionary { @@ -218,7 +228,7 @@ public void ShouldPopulateARootComplexTypeCollectionFromTypedEntries() } [Fact] - public void ShouldPopulateARootComplexTypeListFromUntypedEntries() + public void ShouldMapToARootComplexTypeListFromUntypedEntries() { var source = new Dictionary { @@ -234,7 +244,7 @@ public void ShouldPopulateARootComplexTypeListFromUntypedEntries() } [Fact] - public void ShouldPopulateARootComplexTypeEnumerableFromTypedDottedEntries() + public void ShouldMapToARootComplexTypeEnumerableFromTypedDottedEntries() { var source = new Dictionary { @@ -249,7 +259,7 @@ public void ShouldPopulateARootComplexTypeEnumerableFromTypedDottedEntries() } [Fact] - public void ShouldPopulateARootParameterisedConstructorComplexTypeEnumerableFromTypedDottedEntries() + public void ShouldMapToARootParameterisedConstructorComplexTypeEnumerableFromTypedDottedEntries() { var source = new Dictionary { @@ -266,7 +276,7 @@ public void ShouldPopulateARootParameterisedConstructorComplexTypeEnumerableFrom } [Fact] - public void ShouldPopulateARootComplexTypeCollectionFromUntypedDottedEntries() + public void ShouldMapToARootComplexTypeCollectionFromUntypedDottedEntries() { var source = new Dictionary { @@ -285,7 +295,7 @@ public void ShouldPopulateARootComplexTypeCollectionFromUntypedDottedEntries() } [Fact] - public void ShouldPopulateANestedComplexTypeArrayFromUntypedEntries() + public void ShouldMapToANestedComplexTypeArrayFromUntypedEntries() { var source = new Dictionary { @@ -301,7 +311,7 @@ public void ShouldPopulateANestedComplexTypeArrayFromUntypedEntries() } [Fact] - public void ShouldPopulateANestedComplexTypeCollectionFromTypedDottedEntries() + public void ShouldMapToANestedComplexTypeCollectionFromTypedDottedEntries() { var source = new Dictionary { @@ -318,7 +328,7 @@ public void ShouldPopulateANestedComplexTypeCollectionFromTypedDottedEntries() } [Fact] - public void ShouldPopulateANestedComplexTypeArrayFromUntypedDottedEntries() + public void ShouldMapToANestedComplexTypeArrayFromUntypedDottedEntries() { var source = new Dictionary { @@ -343,7 +353,7 @@ public void ShouldPopulateANestedComplexTypeArrayFromUntypedDottedEntries() } [Fact] - public void ShouldPopulateSimpleTypeConstructorParameterFromUntypedEntry() + public void ShouldMapToSimpleTypeConstructorParameterFromUntypedEntry() { var guid = Guid.NewGuid(); var source = new Dictionary { ["Value"] = guid.ToString() }; @@ -353,7 +363,7 @@ public void ShouldPopulateSimpleTypeConstructorParameterFromUntypedEntry() } [Fact] - public void ShouldPopulateComplexTypeAndSimpleTypeArrayConstructorParametersFromUntypedDottedEntries() + public void ShouldMapToComplexTypeAndSimpleTypeArrayConstructorParametersFromUntypedDottedEntries() { var now = DateTime.Now; var nowString = now.ToCurrentCultureString(); @@ -379,7 +389,7 @@ public void ShouldPopulateComplexTypeAndSimpleTypeArrayConstructorParametersFrom } [Fact] - public void ShouldPopulateDeepNestedComplexTypeMembersFromUntypedDottedEntries() + public void ShouldMapToDeepNestedComplexTypeMembersFromUntypedDottedEntries() { var source = new Dictionary { diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMergingObjectsFromDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMergingFromDictionariesOnToObjects.cs similarity index 98% rename from AgileMapper.UnitTests/Dictionaries/WhenMergingObjectsFromDictionaries.cs rename to AgileMapper.UnitTests/Dictionaries/WhenMergingFromDictionariesOnToObjects.cs index 5f10961f7..01f822fa0 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMergingObjectsFromDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMergingFromDictionariesOnToObjects.cs @@ -7,7 +7,7 @@ using TestClasses; using Xunit; - public class WhenMergingObjectsFromDictionaries + public class WhenMergingFromDictionariesOnToObjects { [Fact] public void ShouldPopulateAStringMemberFromANullableTypedEntry() diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs new file mode 100644 index 000000000..1ba3561ee --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs @@ -0,0 +1,20 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamics + { + [Fact] + public void ShouldMapToASimpleTypeMember() + { + dynamic source = new ExpandoObject(); + source.value = 123; + + var result = (PublicField)Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(123); + } + } +} diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index 6326d345d..3531b843b 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -165,7 +165,10 @@ private Expression GetKeyMatchingQuery( var keyMatchesLambda = Expression.Lambda>(keyMatcher, keyParameter); - var dictionaryKeys = Expression.Property(MapperData.SourceObject, "Keys"); + var dictionaryType = MapperData.SourceObject.Type.GetDictionaryType(); + var sourceObject = MapperData.SourceObject.GetConversionTo(dictionaryType); + + var dictionaryKeys = Expression.Property(sourceObject, "Keys"); var keyMatchesQuery = Expression.Call(queryMethod, dictionaryKeys, keyMatchesLambda); return keyMatchesQuery; diff --git a/AgileMapper/Extensions/ExpressionExtensions.cs b/AgileMapper/Extensions/ExpressionExtensions.cs index 970fbe900..49ed77ed2 100644 --- a/AgileMapper/Extensions/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/ExpressionExtensions.cs @@ -136,8 +136,11 @@ public static Expression GetIndexAccess(this Expression indexedExpression, Expre return Expression.ArrayIndex(indexedExpression, indexValue); } - var indexer = indexedExpression.Type - .GetPublicInstanceProperties() + var relevantTypes = new[] { indexedExpression.Type } + .Concat(indexedExpression.Type.GetAllInterfaces()); + + var indexer = relevantTypes + .SelectMany(t => t.GetPublicInstanceProperties()) .First(p => p.GetIndexParameters().HasOne() && (p.GetIndexParameters()[0].ParameterType == indexValue.Type)); diff --git a/AgileMapper/Extensions/TypeExtensions.cs b/AgileMapper/Extensions/TypeExtensions.cs index 54e62fc60..69a38541f 100644 --- a/AgileMapper/Extensions/TypeExtensions.cs +++ b/AgileMapper/Extensions/TypeExtensions.cs @@ -208,27 +208,32 @@ public static bool IsDictionary(this Type type, out KeyValuePair key public static KeyValuePair GetDictionaryTypes(this Type type) { - if (!type.IsGenericType()) - { - return default(KeyValuePair); - } + var dictionaryType = GetDictionaryType(type); - var typeDefinition = type.GetGenericTypeDefinition(); + return (dictionaryType != null) + ? GetDictionaryTypesFrom(dictionaryType) + : default(KeyValuePair); + } - if ((typeDefinition == typeof(Dictionary<,>)) || (typeDefinition == typeof(IDictionary<,>))) + public static Type GetDictionaryType(this Type type) + { + if (type.IsGenericType()) { - return GetDictionaryTypesFrom(type); + var typeDefinition = type.GetGenericTypeDefinition(); + + if ((typeDefinition == typeof(Dictionary<,>)) || (typeDefinition == typeof(IDictionary<,>))) + { + return type; + } } var interfaceType = type .GetAllInterfaces() .FirstOrDefault(t => t.IsGenericType() && - (t.GetGenericTypeDefinition() == typeof(IDictionary<,>))); + (t.GetGenericTypeDefinition() == typeof(IDictionary<,>))); - return (interfaceType != null) - ? GetDictionaryTypesFrom(interfaceType) - : default(KeyValuePair); + return interfaceType; } private static KeyValuePair GetDictionaryTypesFrom(Type type) From c9794b56d5100b730dbd6ad86c2366b4421f6dbe Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 6 Dec 2017 19:46:39 +0000 Subject: [PATCH 03/74] Test coverage for conversion of dynamic object simple-type member values during mapping --- .../Dynamics/WhenMappingFromDynamics.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs index 1ba3561ee..aa38aad4a 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs @@ -16,5 +16,16 @@ public void ShouldMapToASimpleTypeMember() result.Value.ShouldBe(123); } + + [Fact] + public void ShouldConvertASimpleTypeMemberValue() + { + dynamic source = new ExpandoObject(); + source.Value = "728"; + + var result = (PublicField)Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(728L); + } } } From 07e987fe410c3e9868769c14a6d14b9762990b36 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 6 Dec 2017 19:58:23 +0000 Subject: [PATCH 04/74] Organising source Dictionary mapping tests --- .../AgileMapper.UnitTests.csproj | 11 +- ...MappingFromDictionariesOnToComplexTypes.cs | 22 + ...gFromDictionariesOnToEnumerableMembers.cs} | 17 +- ...appingFromDictionariesOverComplexTypes.cs} | 2 +- ...appingFromDictionariesToNewComplexTypes.cs | 212 ++++++++ ...gFromDictionariesToNewEnumerableMembers.cs | 113 ++++ ...MappingFromDictionariesToNewEnumerables.cs | 213 ++++++++ ...WhenMappingFromDictionariesToNewObjects.cs | 514 ------------------ ...enMappingFromDynamicsToNewComplexTypes.cs} | 2 +- 9 files changed, 571 insertions(+), 535 deletions(-) create mode 100644 AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToComplexTypes.cs rename AgileMapper.UnitTests/Dictionaries/{WhenMergingFromDictionariesOnToObjects.cs => WhenMappingFromDictionariesOnToEnumerableMembers.cs} (82%) rename AgileMapper.UnitTests/Dictionaries/{WhenMappingFromDictionariesOverObjects.cs => WhenMappingFromDictionariesOverComplexTypes.cs} (97%) create mode 100644 AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs create mode 100644 AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs create mode 100644 AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerables.cs delete mode 100644 AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs rename AgileMapper.UnitTests/Dynamics/{WhenMappingFromDynamics.cs => WhenMappingFromDynamicsToNewComplexTypes.cs} (92%) diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 58be40a98..2f30f6399 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -96,9 +96,12 @@ - - - + + + + + + @@ -181,7 +184,7 @@ - + diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToComplexTypes.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToComplexTypes.cs new file mode 100644 index 000000000..7a2c2e7bf --- /dev/null +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToComplexTypes.cs @@ -0,0 +1,22 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dictionaries +{ + using System; + using System.Collections.Generic; + using TestClasses; + using Xunit; + + public class WhenMappingFromDictionariesOnToComplexTypes + { + [Fact] + public void ShouldPopulateAStringMemberFromANullableTypedEntry() + { + var guid = Guid.NewGuid(); + + var source = new Dictionary { ["Value"] = guid }; + var target = new PublicProperty(); + var result = Mapper.Map(source).OnTo(target); + + result.Value.ShouldBe(guid.ToString()); + } + } +} \ No newline at end of file diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMergingFromDictionariesOnToObjects.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToEnumerableMembers.cs similarity index 82% rename from AgileMapper.UnitTests/Dictionaries/WhenMergingFromDictionariesOnToObjects.cs rename to AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToEnumerableMembers.cs index 01f822fa0..7d443330b 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMergingFromDictionariesOnToObjects.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOnToEnumerableMembers.cs @@ -1,26 +1,13 @@ namespace AgileObjects.AgileMapper.UnitTests.Dictionaries { - using System; using System.Collections.Generic; using System.Linq; using Shouldly; using TestClasses; using Xunit; - public class WhenMergingFromDictionariesOnToObjects + public class WhenMappingFromDictionariesOnToEnumerableMembers { - [Fact] - public void ShouldPopulateAStringMemberFromANullableTypedEntry() - { - var guid = Guid.NewGuid(); - - var source = new Dictionary { ["Value"] = guid }; - var target = new PublicProperty(); - var result = Mapper.Map(source).OnTo(target); - - result.Value.ShouldBe(guid.ToString()); - } - [Fact] public void ShouldMergeSimpleTypeListFromSimpleTypeDictionaryImplementationEntries() { @@ -59,7 +46,7 @@ public void ShouldMergeSimpleTypeCollectionFromSimpleTypeDictionaryImplementatio } [Fact] - public void ShouldMergeANestedComplexTypeArrayFromUntypedDictionaryImplementationEntries() + public void ShouldMergeAComplexTypeArrayFromUntypedDictionaryImplementationEntries() { var source = new StringKeyedDictionary { diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverObjects.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverComplexTypes.cs similarity index 97% rename from AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverObjects.cs rename to AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverComplexTypes.cs index 07cefea41..4a24a4d89 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverObjects.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesOverComplexTypes.cs @@ -6,7 +6,7 @@ using TestClasses; using Xunit; - public class WhenMappingFromDictionariesOverObjects + public class WhenMappingFromDictionariesOverComplexTypes { [Fact] public void ShouldPopulateADateTimeMemberFromAnUntypedEntry() diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs new file mode 100644 index 000000000..c01eb6f8c --- /dev/null +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs @@ -0,0 +1,212 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dictionaries +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDictionariesToNewComplexTypes + { + [Fact] + public void ShouldMapToAnIntMemberFromATypedEntry() + { + var source = new Dictionary { ["Value"] = 123 }; + var result = Mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Value.ShouldBe(123); + } + + [Fact] + public void ShouldMapToAnIntMemberFromADictionaryImplementationTypedEntry() + { + var source = new StringKeyedDictionary { ["Value"] = 999 }; + var result = Mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Value.ShouldBe(999L); + } + + [Fact] + public void ShouldMapToAStringMemberFromATypedEntryCaseInsensitively() + { + var source = new Dictionary { ["value"] = "Hello" }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe("Hello"); + } + + [Fact] + public void ShouldMapToAStringSetMethodFromATypedEntry() + { + var source = new Dictionary { ["SetValue"] = "Goodbye" }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe("Goodbye"); + } + + [Fact] + public void ShouldMapToANestedStringMemberFromTypedDottedEntries() + { + var source = new Dictionary { ["Value.Value"] = "Over there!" }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.Value.ShouldBe("Over there!"); + } + + [Fact] + public void ShouldMapToANestedBoolMemberFromUntypedDottedEntries() + { + var source = new Dictionary { ["Value.Value"] = "true" }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.Value.ShouldBeTrue(); + } + + [Fact] + public void ShouldConvertASimpleTypeMemberValue() + { + var source = new Dictionary { ["setvalue"] = "123" }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(123); + } + + [Fact] + public void ShouldConvertASimpleTypeMemberValueFromObject() + { + var idGuid = Guid.NewGuid(); + var source = new Dictionary { ["Id"] = idGuid.ToString() }; + var result = Mapper.Map(source).ToANew(); + + result.ShouldNotBeNull(); + result.Id.ShouldBe(idGuid); + } + + [Fact] + public void ShouldMapToSimpleTypeConstructorParameterFromUntypedEntry() + { + var guid = Guid.NewGuid(); + var source = new Dictionary { ["Value"] = guid.ToString() }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(guid); + } + + [Fact] + public void ShouldMapToDeepNestedComplexTypeMembersFromUntypedDottedEntries() + { + var source = new Dictionary + { + ["Value[0].Value.SetValue[0].Title"] = "Mr", + ["Value[0].Value.SetValue[0].Name"] = "Franks", + ["Value[0].Value.SetValue[0].Address.Line1"] = "Somewhere", + ["Value[0].Value.SetValue[0].Address.Line2"] = "Over the rainbow", + ["Value[0].Value.SetValue[1]"] = new PersonViewModel { Name = "Mike", AddressLine1 = "La la la" }, + ["Value[0].Value.SetValue[2].Title"] = 5, + ["Value[0].Value.SetValue[2].Name"] = "Wilkes", + ["Value[0].Value.SetValue[2].Address.Line1"] = "Over there", + ["Value[1].Value.SetValue[0].Title"] = 737328, + ["Value[1].Value.SetValue[0].Name"] = "Rob", + ["Value[1].Value.SetValue[0].Address.Line1"] = "Some place" + }; + + var result = Mapper + .Map(source) + .ToANew>>>>(); + + result.Value.Count.ShouldBe(2); + + result.Value.First().Value.Value.Length.ShouldBe(3); + result.Value.Second().Value.Value.Length.ShouldBe(1); + + result.Value.First().Value.Value.First().Title.ShouldBe(Title.Mr); + result.Value.First().Value.Value.First().Name.ShouldBe("Franks"); + result.Value.First().Value.Value.First().Address.Line1.ShouldBe("Somewhere"); + result.Value.First().Value.Value.First().Address.Line2.ShouldBe("Over the rainbow"); + + result.Value.First().Value.Value.Second().Title.ShouldBeDefault(); + result.Value.First().Value.Value.Second().Name.ShouldBe("Mike"); + result.Value.First().Value.Value.Second().Address.Line1.ShouldBe("La la la"); + result.Value.First().Value.Value.Second().Address.Line2.ShouldBeDefault(); + + result.Value.First().Value.Value.Third().Title.ShouldBe(Title.Mrs); + result.Value.First().Value.Value.Third().Name.ShouldBe("Wilkes"); + result.Value.First().Value.Value.Third().Address.Line1.ShouldBe("Over there"); + result.Value.First().Value.Value.Third().Address.Line2.ShouldBeDefault(); + + result.Value.Second().Value.Value.First().Title.ShouldBeDefault(); + result.Value.Second().Value.Value.First().Name.ShouldBe("Rob"); + result.Value.Second().Value.Value.First().Address.Line1.ShouldBe("Some place"); + result.Value.Second().Value.Value.First().Address.Line2.ShouldBeDefault(); + } + + [Fact] + public void ShouldIgnoreANonStringKeyedDictionary() + { + var source = new Dictionary { [123] = 456 }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBeDefault(); + } + + [Fact] + public void ShouldHandleAnUnparseableStringValue() + { + var source = new Dictionary { ["Value"] = "jkdekml" }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBeDefault(); + } + + [Fact] + public void ShouldHandleANullObjectValue() + { + var source = new Dictionary { ["Value"] = null }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBeDefault(); + } + + [Fact] + public void ShouldIgnoreADeclaredUnconvertibleValueType() + { + var source = new Dictionary { ["Value"] = new byte[0] }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBeDefault(); + } + + [Fact] + public void ShouldHandleAnUnconvertibleValueForASimpleType() + { + var source = new Dictionary { ["Value"] = new object() }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBeNull(); + } + + [Fact] + public void ShouldHandleAMappingException() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .To
() + .CreateInstancesUsing(ctx => new Address { Line1 = int.Parse("rstgerfed").ToString() }); + + var source = new Dictionary + { + ["Line1"] = "La la la", + ["Line2"] = "La la la" + }; + + var mappingEx = Should.Throw(() => mapper.Map(source).ToANew
()); + + mappingEx.Message.ShouldContain("Dictionary -> Address"); + } + } + } +} diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs new file mode 100644 index 000000000..1deed81d0 --- /dev/null +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs @@ -0,0 +1,113 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dictionaries +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDictionariesToNewEnumerableMembers + { + [Fact] + public void ShouldMapToASimpleTypeListFromTypedEntries() + { + var source = new Dictionary + { + ["Value[0]"] = 9, + ["Value[1]"] = 8, + ["Value[2]"] = 7 + }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBe(9, 8, 7); + } + + [Fact] + public void ShouldMapToASimpleTypeCollectionFromConvertibleTypedEntries() + { + var now = DateTime.Now; + + var source = new Dictionary + { + ["Value[0]"] = now, + ["value[1]"] = now.AddHours(1), + ["Value[2]"] = now.AddHours(2) + }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBe( + now.ToCurrentCultureString(), + now.AddHours(1).ToCurrentCultureString(), + now.AddHours(2).ToCurrentCultureString()); + } + + [Fact] + public void ShouldMapToAComplexTypeArrayFromUntypedEntries() + { + var source = new Dictionary + { + ["Value[0]"] = new Customer { Name = "Mr Pants" }, + ["Value[1]"] = new Person { Name = "Ms Blouse" } + }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.Length.ShouldBe(2); + result.Value.First().ShouldBeOfType(); + result.Value.First().Name.ShouldBe("Mr Pants"); + result.Value.Second().Name.ShouldBe("Ms Blouse"); + } + + [Fact] + public void ShouldMapToAComplexTypeCollectionFromTypedDottedEntries() + { + var source = new Dictionary + { + ["Value[0].ProductId"] = "Spade", + ["Value[0].Price"] = "100.00", + ["Value[0].HowMega"] = "1.01" + }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldHaveSingleItem(); + result.Value.First().ProductId.ShouldBe("Spade"); + result.Value.First().Price.ShouldBe(100.00); + result.Value.First().HowMega.ShouldBe(1.01); + } + + [Fact] + public void ShouldMapToAComplexTypeArrayFromUntypedDottedEntries() + { + var source = new Dictionary + { + ["Value[0].ProductId"] = "Jay", + ["Value[0].Price"] = "100.00", + ["Value[0].HowMega"] = "1.01", + ["Value[1].ProductId"] = "Silent Bob", + ["Value[1].Price"] = "1000.00", + ["Value[1].HowMega"] = ".99" + }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.Length.ShouldBe(2); + + result.Value.First().ProductId.ShouldBe("Jay"); + result.Value.First().Price.ShouldBe(100.00); + result.Value.First().HowMega.ShouldBe(1.01); + + result.Value.Second().ProductId.ShouldBe("Silent Bob"); + result.Value.Second().Price.ShouldBe(1000.00); + result.Value.Second().HowMega.ShouldBe(0.99); + } + + [Fact] + public void ShouldHandleAnUnconvertibleValueForACollection() + { + var source = new Dictionary { ["Value"] = new Person { Name = "Nope" } }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBeEmpty(); + } + } +} \ No newline at end of file diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerables.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerables.cs new file mode 100644 index 000000000..984e760e3 --- /dev/null +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerables.cs @@ -0,0 +1,213 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dictionaries +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDictionariesToNewEnumerables + { + [Fact] + public void ShouldMapToASimpleTypeCollectionFromATypedSourceArray() + { + var source = new Dictionary { ["Value"] = new long[] { 4, 5, 6 } }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBe(4, 5, 6); + } + + [Fact] + public void ShouldMapToASimpleTypeListFromNullableTypedSourceEntries() + { + var source = new Dictionary + { + ["Value[0]"] = 56, + ["Value[1]"] = null, + ["Value[2]"] = 27382 + }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBe(56, null, default(byte?)); + } + + [Fact] + public void ShouldMapToASimpleTypeEnumerableFromAnUntypedSourceArray() + { + var source = new Dictionary { ["Value"] = new[] { 1, 2, 3 } }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBe(1, 2, 3); + } + + [Fact] + public void ShouldMapToASimpleTypeArrayFromAConvertibleTypedSourceEnumerable() + { + var source = new Dictionary> { ["Value"] = new[] { 4, 5, 6 } }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe("4", "5", "6"); + } + + [Fact] + public void ShouldMapToAComplexTypeArrayFromAConvertibleTypedSourceEnumerable() + { + var source = new Dictionary> + { + ["Value"] = new[] + { + new Person { Name = "Mr Pants"}, + new Customer { Name = "Mrs Blouse" } + } + + }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.Length.ShouldBe(2); + result.Value.First().Name.ShouldBe("Mr Pants"); + result.Value.Second().Name.ShouldBe("Mrs Blouse"); + } + + [Fact] + public void ShouldMapToASimpleTypeEnumerableFromTypedEntries() + { + var source = new Dictionary + { + ["[0]"] = 1, + ["[1]"] = 2, + ["[2]"] = 3 + }; + var result = Mapper.Map(source).ToANew>(); + + result.ShouldBe(1, 2, 3); + } + + [Fact] + public void ShouldMapToASimpleTypeArrayFromConvertibleTypedEntries() + { + var source = new Dictionary + { + ["[0]"] = 123, + ["[1]"] = long.MaxValue, + ["[2]"] = 789 + }; + var result = Mapper.Map(source).ToANew(); + + result.Length.ShouldBe(3); + result.First().ShouldBe(123); + result.Second().ShouldBeDefault(); + result.Third().ShouldBe(789); + } + + [Fact] + public void ShouldMapToAComplexTypeCollectionFromTypedEntries() + { + var source = new Dictionary + { + ["[0]"] = new MegaProduct { ProductId = "asdfasdf" }, + ["[1]"] = new MegaProduct { ProductId = "mnbvmnbv" } + }; + var result = Mapper.Map(source).ToANew>(); + + result.Count.ShouldBe(2); + result.First().ShouldNotBeSameAs(source.First()); + result.First().ProductId.ShouldBe("asdfasdf"); + result.Second().ShouldNotBeSameAs(source.Second()); + result.Second().ProductId.ShouldBe("mnbvmnbv"); + } + + [Fact] + public void ShouldMapToAComplexTypeListFromUntypedEntries() + { + var source = new Dictionary + { + ["[0]"] = new Product { ProductId = "Pants" }, + ["[1]"] = new MegaProduct { ProductId = "Blouse" } + }; + var result = Mapper.Map(source).ToANew>(); + + result.Count.ShouldBe(2); + result.First().ProductId.ShouldBe("Pants"); + result.Second().ShouldBeOfType(); + result.Second().ProductId.ShouldBe("Blouse"); + } + + [Fact] + public void ShouldMapToAComplexTypeEnumerableFromTypedDottedEntries() + { + var source = new Dictionary + { + ["[0].ProductId"] = "Hose", + ["[0].Price"] = "1.99" + }; + var result = Mapper.Map(source).ToANew>(); + + result.ShouldHaveSingleItem(); + result.First().ProductId.ShouldBe("Hose"); + result.First().Price.ShouldBe(1.99); + } + + [Fact] + public void ShouldMapToAParameterisedConstructorComplexTypeEnumerableFromTypedDottedEntries() + { + var source = new Dictionary + { + ["[0].Value"] = "123", + ["[1].Value"] = "456", + ["[2].value"] = "789" + }; + var result = Mapper.Map(source).ToANew>>(); + + result.Count().ShouldBe(3); + result.First().Value.ShouldBe(123); + result.Second().Value.ShouldBe(456); + result.Third().Value.ShouldBe(789); + } + + [Fact] + public void ShouldMapToAComplexTypeCollectionFromUntypedDottedEntries() + { + var source = new Dictionary + { + ["[0].ProductId"] = "Blouse", + ["[0].Price"] = "10.99", + ["[1].ProductId"] = "Pants", + ["[1].Price"] = "7.99" + }; + var result = Mapper.Map(source).ToANew>(); + + result.Count.ShouldBe(2); + result.First().ProductId.ShouldBe("Blouse"); + result.First().Price.ShouldBe(10.99); + result.Second().ProductId.ShouldBe("Pants"); + result.Second().Price.ShouldBe(7.99); + } + + [Fact] + public void ShouldMapToComplexTypeAndSimpleTypeArrayConstructorParametersFromUntypedDottedEntries() + { + var now = DateTime.Now; + var nowString = now.ToCurrentCultureString(); + var inTenMinutes = now.AddMinutes(10).ToCurrentCultureString(); + var inTwentyMinutes = now.AddMinutes(20).ToCurrentCultureString(); + + var source = new Dictionary + { + ["Value1.ProductId"] = "Boom", + ["Value1.Price"] = "1.99", + ["Value1.HowMega"] = "1.00", + ["Value2[0]"] = nowString, + ["Value2[1]"] = inTenMinutes, + ["Value2[2]"] = inTwentyMinutes + }; + var result = Mapper.Map(source).ToANew>(); + + result.Value1.ProductId.ShouldBe("Boom"); + result.Value1.Price.ShouldBe(1.99); + result.Value1.HowMega.ShouldBe(1.00); + + result.Value2.ShouldBe(d => d.ToCurrentCultureString(), nowString, inTenMinutes, inTwentyMinutes); + } + } +} \ No newline at end of file diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs deleted file mode 100644 index 913529995..000000000 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewObjects.cs +++ /dev/null @@ -1,514 +0,0 @@ -namespace AgileObjects.AgileMapper.UnitTests.Dictionaries -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using Shouldly; - using TestClasses; - using Xunit; - - public class WhenMappingFromDictionariesToNewObjects - { - [Fact] - public void ShouldMapToAnIntMemberFromATypedEntry() - { - var source = new Dictionary { ["Value"] = 123 }; - var result = Mapper.Map(source).ToANew>(); - - result.ShouldNotBeNull(); - result.Value.ShouldBe(123); - } - - [Fact] - public void ShouldMapToAnIntMemberFromADictionaryImplementationTypedEntry() - { - var source = new StringKeyedDictionary { ["Value"] = 999 }; - var result = Mapper.Map(source).ToANew>(); - - result.ShouldNotBeNull(); - result.Value.ShouldBe(999L); - } - - [Fact] - public void ShouldMapToAStringMemberFromATypedEntryCaseInsensitively() - { - var source = new Dictionary { ["value"] = "Hello" }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBe("Hello"); - } - - [Fact] - public void ShouldMapToAStringSetMethodFromATypedEntry() - { - var source = new Dictionary { ["SetValue"] = "Goodbye" }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBe("Goodbye"); - } - - [Fact] - public void ShouldMapToANestedStringMemberFromTypedDottedEntries() - { - var source = new Dictionary { ["Value.Value"] = "Over there!" }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.Value.ShouldBe("Over there!"); - } - - [Fact] - public void ShouldMapToANestedBoolMemberFromUntypedDottedEntries() - { - var source = new Dictionary { ["Value.Value"] = "true" }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.Value.ShouldBeTrue(); - } - - [Fact] - public void ShouldConvertASimpleTypeMemberValue() - { - var source = new Dictionary { ["setvalue"] = "123" }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBe(123); - } - - [Fact] - public void ShouldConvertASimpleTypeMemberValueFromObject() - { - var idGuid = Guid.NewGuid(); - var source = new Dictionary { ["Id"] = idGuid.ToString() }; - var result = Mapper.Map(source).ToANew(); - - result.ShouldNotBeNull(); - result.Id.ShouldBe(idGuid); - } - - [Fact] - public void ShouldMapToASimpleTypeCollectionFromATypedSourceArray() - { - var source = new Dictionary { ["Value"] = new long[] { 4, 5, 6 } }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldBe(4, 5, 6); - } - - [Fact] - public void ShouldMapToASimpleTypeListFromNullableTypedSourceEntries() - { - var source = new Dictionary - { - ["Value[0]"] = 56, - ["Value[1]"] = null, - ["Value[2]"] = 27382 - }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldBe(56, null, default(byte?)); - } - - [Fact] - public void ShouldMapToASimpleTypeEnumerableFromAnUntypedSourceArray() - { - var source = new Dictionary { ["Value"] = new[] { 1, 2, 3 } }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldBe(1, 2, 3); - } - - [Fact] - public void ShouldMapToASimpleTypeArrayFromAConvertibleTypedSourceEnumerable() - { - var source = new Dictionary> { ["Value"] = new[] { 4, 5, 6 } }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBe("4", "5", "6"); - } - - [Fact] - public void ShouldMapToAComplexTypeArrayFromAConvertibleTypedSourceEnumerable() - { - var source = new Dictionary> - { - ["Value"] = new[] - { - new Person { Name = "Mr Pants"}, - new Customer { Name = "Mrs Blouse" } - } - - }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.Length.ShouldBe(2); - result.Value.First().Name.ShouldBe("Mr Pants"); - result.Value.Second().Name.ShouldBe("Mrs Blouse"); - } - - [Fact] - public void ShouldMapToARootSimpleTypeEnumerableFromTypedEntries() - { - var source = new Dictionary - { - ["[0]"] = 1, - ["[1]"] = 2, - ["[2]"] = 3 - }; - var result = Mapper.Map(source).ToANew>(); - - result.ShouldBe(1, 2, 3); - } - - [Fact] - public void ShouldMapToANestedSimpleTypeListFromTypedEntries() - { - var source = new Dictionary - { - ["Value[0]"] = 9, - ["Value[1]"] = 8, - ["Value[2]"] = 7 - }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldBe(9, 8, 7); - } - - [Fact] - public void ShouldMapToANestedSimpleTypeCollectionFromConvertibleTypedEntries() - { - var now = DateTime.Now; - - var source = new Dictionary - { - ["Value[0]"] = now, - ["value[1]"] = now.AddHours(1), - ["Value[2]"] = now.AddHours(2) - }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldBe( - now.ToCurrentCultureString(), - now.AddHours(1).ToCurrentCultureString(), - now.AddHours(2).ToCurrentCultureString()); - } - - [Fact] - public void ShouldMapToARootSimpleTypeArrayFromConvertibleTypedEntries() - { - var source = new Dictionary - { - ["[0]"] = 123, - ["[1]"] = long.MaxValue, - ["[2]"] = 789 - }; - var result = Mapper.Map(source).ToANew(); - - result.Length.ShouldBe(3); - result.First().ShouldBe(123); - result.Second().ShouldBeDefault(); - result.Third().ShouldBe(789); - } - - [Fact] - public void ShouldMapToARootComplexTypeCollectionFromTypedEntries() - { - var source = new Dictionary - { - ["[0]"] = new MegaProduct { ProductId = "asdfasdf" }, - ["[1]"] = new MegaProduct { ProductId = "mnbvmnbv" } - }; - var result = Mapper.Map(source).ToANew>(); - - result.Count.ShouldBe(2); - result.First().ShouldNotBeSameAs(source.First()); - result.First().ProductId.ShouldBe("asdfasdf"); - result.Second().ShouldNotBeSameAs(source.Second()); - result.Second().ProductId.ShouldBe("mnbvmnbv"); - } - - [Fact] - public void ShouldMapToARootComplexTypeListFromUntypedEntries() - { - var source = new Dictionary - { - ["[0]"] = new Product { ProductId = "Pants" }, - ["[1]"] = new MegaProduct { ProductId = "Blouse" } - }; - var result = Mapper.Map(source).ToANew>(); - - result.Count.ShouldBe(2); - result.First().ProductId.ShouldBe("Pants"); - result.Second().ShouldBeOfType(); - result.Second().ProductId.ShouldBe("Blouse"); - } - - [Fact] - public void ShouldMapToARootComplexTypeEnumerableFromTypedDottedEntries() - { - var source = new Dictionary - { - ["[0].ProductId"] = "Hose", - ["[0].Price"] = "1.99" - }; - var result = Mapper.Map(source).ToANew>(); - - result.ShouldHaveSingleItem(); - result.First().ProductId.ShouldBe("Hose"); - result.First().Price.ShouldBe(1.99); - } - - [Fact] - public void ShouldMapToARootParameterisedConstructorComplexTypeEnumerableFromTypedDottedEntries() - { - var source = new Dictionary - { - ["[0].Value"] = "123", - ["[1].Value"] = "456", - ["[2].value"] = "789" - }; - var result = Mapper.Map(source).ToANew>>(); - - result.Count().ShouldBe(3); - result.First().Value.ShouldBe(123); - result.Second().Value.ShouldBe(456); - result.Third().Value.ShouldBe(789); - } - - [Fact] - public void ShouldMapToARootComplexTypeCollectionFromUntypedDottedEntries() - { - var source = new Dictionary - { - ["[0].ProductId"] = "Blouse", - ["[0].Price"] = "10.99", - ["[1].ProductId"] = "Pants", - ["[1].Price"] = "7.99" - }; - var result = Mapper.Map(source).ToANew>(); - - result.Count.ShouldBe(2); - result.First().ProductId.ShouldBe("Blouse"); - result.First().Price.ShouldBe(10.99); - result.Second().ProductId.ShouldBe("Pants"); - result.Second().Price.ShouldBe(7.99); - } - - [Fact] - public void ShouldMapToANestedComplexTypeArrayFromUntypedEntries() - { - var source = new Dictionary - { - ["Value[0]"] = new Customer { Name = "Mr Pants" }, - ["Value[1]"] = new Person { Name = "Ms Blouse" } - }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.Length.ShouldBe(2); - result.Value.First().ShouldBeOfType(); - result.Value.First().Name.ShouldBe("Mr Pants"); - result.Value.Second().Name.ShouldBe("Ms Blouse"); - } - - [Fact] - public void ShouldMapToANestedComplexTypeCollectionFromTypedDottedEntries() - { - var source = new Dictionary - { - ["Value[0].ProductId"] = "Spade", - ["Value[0].Price"] = "100.00", - ["Value[0].HowMega"] = "1.01" - }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldHaveSingleItem(); - result.Value.First().ProductId.ShouldBe("Spade"); - result.Value.First().Price.ShouldBe(100.00); - result.Value.First().HowMega.ShouldBe(1.01); - } - - [Fact] - public void ShouldMapToANestedComplexTypeArrayFromUntypedDottedEntries() - { - var source = new Dictionary - { - ["Value[0].ProductId"] = "Jay", - ["Value[0].Price"] = "100.00", - ["Value[0].HowMega"] = "1.01", - ["Value[1].ProductId"] = "Silent Bob", - ["Value[1].Price"] = "1000.00", - ["Value[1].HowMega"] = ".99" - }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.Length.ShouldBe(2); - - result.Value.First().ProductId.ShouldBe("Jay"); - result.Value.First().Price.ShouldBe(100.00); - result.Value.First().HowMega.ShouldBe(1.01); - - result.Value.Second().ProductId.ShouldBe("Silent Bob"); - result.Value.Second().Price.ShouldBe(1000.00); - result.Value.Second().HowMega.ShouldBe(0.99); - } - - [Fact] - public void ShouldMapToSimpleTypeConstructorParameterFromUntypedEntry() - { - var guid = Guid.NewGuid(); - var source = new Dictionary { ["Value"] = guid.ToString() }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBe(guid); - } - - [Fact] - public void ShouldMapToComplexTypeAndSimpleTypeArrayConstructorParametersFromUntypedDottedEntries() - { - var now = DateTime.Now; - var nowString = now.ToCurrentCultureString(); - var inTenMinutes = now.AddMinutes(10).ToCurrentCultureString(); - var inTwentyMinutes = now.AddMinutes(20).ToCurrentCultureString(); - - var source = new Dictionary - { - ["Value1.ProductId"] = "Boom", - ["Value1.Price"] = "1.99", - ["Value1.HowMega"] = "1.00", - ["Value2[0]"] = nowString, - ["Value2[1]"] = inTenMinutes, - ["Value2[2]"] = inTwentyMinutes - }; - var result = Mapper.Map(source).ToANew>(); - - result.Value1.ProductId.ShouldBe("Boom"); - result.Value1.Price.ShouldBe(1.99); - result.Value1.HowMega.ShouldBe(1.00); - - result.Value2.ShouldBe(d => d.ToCurrentCultureString(), nowString, inTenMinutes, inTwentyMinutes); - } - - [Fact] - public void ShouldMapToDeepNestedComplexTypeMembersFromUntypedDottedEntries() - { - var source = new Dictionary - { - ["Value[0].Value.SetValue[0].Title"] = "Mr", - ["Value[0].Value.SetValue[0].Name"] = "Franks", - ["Value[0].Value.SetValue[0].Address.Line1"] = "Somewhere", - ["Value[0].Value.SetValue[0].Address.Line2"] = "Over the rainbow", - ["Value[0].Value.SetValue[1]"] = new PersonViewModel { Name = "Mike", AddressLine1 = "La la la" }, - ["Value[0].Value.SetValue[2].Title"] = 5, - ["Value[0].Value.SetValue[2].Name"] = "Wilkes", - ["Value[0].Value.SetValue[2].Address.Line1"] = "Over there", - ["Value[1].Value.SetValue[0].Title"] = 737328, - ["Value[1].Value.SetValue[0].Name"] = "Rob", - ["Value[1].Value.SetValue[0].Address.Line1"] = "Some place" - }; - - var result = Mapper - .Map(source) - .ToANew>>>>(); - - result.Value.Count.ShouldBe(2); - - result.Value.First().Value.Value.Length.ShouldBe(3); - result.Value.Second().Value.Value.Length.ShouldBe(1); - - result.Value.First().Value.Value.First().Title.ShouldBe(Title.Mr); - result.Value.First().Value.Value.First().Name.ShouldBe("Franks"); - result.Value.First().Value.Value.First().Address.Line1.ShouldBe("Somewhere"); - result.Value.First().Value.Value.First().Address.Line2.ShouldBe("Over the rainbow"); - - result.Value.First().Value.Value.Second().Title.ShouldBeDefault(); - result.Value.First().Value.Value.Second().Name.ShouldBe("Mike"); - result.Value.First().Value.Value.Second().Address.Line1.ShouldBe("La la la"); - result.Value.First().Value.Value.Second().Address.Line2.ShouldBeDefault(); - - result.Value.First().Value.Value.Third().Title.ShouldBe(Title.Mrs); - result.Value.First().Value.Value.Third().Name.ShouldBe("Wilkes"); - result.Value.First().Value.Value.Third().Address.Line1.ShouldBe("Over there"); - result.Value.First().Value.Value.Third().Address.Line2.ShouldBeDefault(); - - result.Value.Second().Value.Value.First().Title.ShouldBeDefault(); - result.Value.Second().Value.Value.First().Name.ShouldBe("Rob"); - result.Value.Second().Value.Value.First().Address.Line1.ShouldBe("Some place"); - result.Value.Second().Value.Value.First().Address.Line2.ShouldBeDefault(); - } - - [Fact] - public void ShouldIgnoreANonStringKeyedDictionary() - { - var source = new Dictionary { [123] = 456 }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBeDefault(); - } - - [Fact] - public void ShouldHandleAnUnparseableStringValue() - { - var source = new Dictionary { ["Value"] = "jkdekml" }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBeDefault(); - } - - [Fact] - public void ShouldHandleANullObjectValue() - { - var source = new Dictionary { ["Value"] = null }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBeDefault(); - } - - [Fact] - public void ShouldIgnoreADeclaredUnconvertibleValueType() - { - var source = new Dictionary { ["Value"] = new byte[0] }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBeDefault(); - } - - [Fact] - public void ShouldHandleAnUnconvertibleValueForASimpleType() - { - var source = new Dictionary { ["Value"] = new object() }; - var result = Mapper.Map(source).ToANew>(); - - result.Value.ShouldBeNull(); - } - - [Fact] - public void ShouldHandleAnUnconvertibleValueForACollection() - { - var source = new Dictionary { ["Value"] = new Person { Name = "Nope" } }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.ShouldBeEmpty(); - } - - [Fact] - public void ShouldHandleAMappingException() - { - using (var mapper = Mapper.CreateNew()) - { - mapper.WhenMapping - .To
() - .CreateInstancesUsing(ctx => new Address { Line1 = int.Parse("rstgerfed").ToString() }); - - var source = new Dictionary - { - ["Line1"] = "La la la", - ["Line2"] = "La la la" - }; - - var mappingEx = Should.Throw(() => mapper.Map(source).ToANew
()); - - mappingEx.Message.ShouldContain("Dictionary -> Address"); - } - } - } -} diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs similarity index 92% rename from AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs rename to AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs index aa38aad4a..abf9af07c 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs @@ -4,7 +4,7 @@ using TestClasses; using Xunit; - public class WhenMappingFromDynamics + public class WhenMappingFromDynamicsToNewComplexTypes { [Fact] public void ShouldMapToASimpleTypeMember() From 070faf3f1f544fe0cbce032160f32b3464cbbe47 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 6 Dec 2017 21:31:02 +0000 Subject: [PATCH 05/74] Removing need to cast IDictionary<,> source objects when accessing keys --- .../DataSources/DictionaryEntryVariablePair.cs | 5 +---- AgileMapper/Members/DictionarySourceMember.cs | 11 ++++------- AgileMapper/ObjectPopulation/ObjectMapperData.cs | 7 ++----- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index 3531b843b..6326d345d 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -165,10 +165,7 @@ private Expression GetKeyMatchingQuery( var keyMatchesLambda = Expression.Lambda>(keyMatcher, keyParameter); - var dictionaryType = MapperData.SourceObject.Type.GetDictionaryType(); - var sourceObject = MapperData.SourceObject.GetConversionTo(dictionaryType); - - var dictionaryKeys = Expression.Property(sourceObject, "Keys"); + var dictionaryKeys = Expression.Property(MapperData.SourceObject, "Keys"); var keyMatchesQuery = Expression.Call(queryMethod, dictionaryKeys, keyMatchesLambda); return keyMatchesQuery; diff --git a/AgileMapper/Members/DictionarySourceMember.cs b/AgileMapper/Members/DictionarySourceMember.cs index 1e1173e7a..576bd06c7 100644 --- a/AgileMapper/Members/DictionarySourceMember.cs +++ b/AgileMapper/Members/DictionarySourceMember.cs @@ -16,7 +16,7 @@ public DictionarySourceMember(IMemberMapperData mapperData) public DictionarySourceMember(IQualifiedMember wrappedSourceMember, QualifiedMember matchedTargetMember) : this( - wrappedSourceMember.Type, + wrappedSourceMember.Type.GetDictionaryType(), wrappedSourceMember, matchedTargetMember, wrappedSourceMember.Matches(matchedTargetMember)) @@ -78,12 +78,9 @@ private DictionarySourceMember( public IQualifiedMember GetElementMember() { - if (EntryMember.IsEnumerable) - { - return EntryMember.GetElementMember(); - } - - return EntryMember.GetInstanceElementMember(); + return EntryMember.IsEnumerable + ? EntryMember.GetElementMember() + : EntryMember.GetInstanceElementMember(); } public IQualifiedMember Append(Member childMember) => EntryMember.Append(childMember); diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index 63a8f09b9..d761dbefe 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -145,10 +145,7 @@ private Expression GetMappingDataProperty(Type mappingDataType, string propertyN { var property = mappingDataType.GetPublicInstanceProperty(propertyName); - // ReSharper disable once AssignNullToNotNullAttribute - var propertyAccess = Expression.Property(MappingDataObject, property); - - return propertyAccess; + return Expression.Property(MappingDataObject, property); } private static MethodInfo GetMapMethod(Type mappingDataType, int numberOfArguments) @@ -282,7 +279,7 @@ private static bool TypeHasACompatibleChildMember( #region Factory Method public static ObjectMapperData For(IObjectMappingData mappingData) - { + { var membersSource = mappingData.MapperKey.GetMembersSource(mappingData.Parent); var sourceMember = membersSource.GetSourceMember().WithType(typeof(TSource)); var targetMember = membersSource.GetTargetMember().WithType(typeof(TTarget)); From 519b7fad0c672b07c2a1a0c3ab929425c5c987ec Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 6 Dec 2017 21:32:31 +0000 Subject: [PATCH 06/74] Test coverage for mapping a null string dynamic member --- .../WhenMappingFromDynamicsToNewComplexTypes.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs index abf9af07c..108cf6131 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { using System.Dynamic; + using Shouldly; using TestClasses; using Xunit; @@ -27,5 +28,16 @@ public void ShouldConvertASimpleTypeMemberValue() result.Value.ShouldBe(728L); } + + [Fact] + public void ShouldHandleANullASimpleTypeMemberValue() + { + dynamic source = new ExpandoObject(); + source.Value = default(string); + + var result = (PublicSetMethod)Mapper.Map(source).ToANew>(); + + result.Value.ShouldBeNull(); + } } } From 2fa18bfa86d142d20bcd50140faa7c74bc5aedbe Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 7 Dec 2017 20:29:15 +0000 Subject: [PATCH 07/74] Support for mapping untyped dictionary complex type entries to complex type members / Organising source dictionary tests / Test coverage for mapping from a dynamic to nested members --- .../AgileMapper.UnitTests.csproj | 2 + ...FromDictionariesToNewComplexTypeMembers.cs | 99 +++++++++++++++++++ ...appingFromDictionariesToNewComplexTypes.cs | 67 ------------- ...gFromDictionariesToNewEnumerableMembers.cs | 12 +++ ...pingFromDynamicsToNewComplexTypeMembers.cs | 29 ++++++ AgileMapper/Members/DictionarySourceMember.cs | 3 +- 6 files changed, 144 insertions(+), 68 deletions(-) create mode 100644 AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 2f30f6399..355690779 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -99,8 +99,10 @@ + + diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs new file mode 100644 index 000000000..93c27623f --- /dev/null +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs @@ -0,0 +1,99 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dictionaries +{ + using System.Collections.Generic; + using System.Linq; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDictionariesToNewComplexTypeMembers + { + [Fact] + public void ShouldMapToAStringMemberFromTypedDottedEntries() + { + var source = new Dictionary { ["Value.Value"] = "Over there!" }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.Value.ShouldBe("Over there!"); + } + + [Fact] + public void ShouldMapToABoolMemberFromUntypedDottedEntries() + { + var source = new Dictionary { ["Value.Value"] = "true" }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.Value.ShouldBeTrue(); + } + + [Fact] + public void ShouldMapToNestedMembersFromUntypedEntries() + { + var source = new Dictionary + { + ["Id"] = 123, + ["Name"] = "Captain Customer", + ["Address"] = new Address + { + Line1 = "Line 1", + Line2 = "Line 2" + } + }; + var result = Mapper.Map(source).ToANew(); + + result.Id.ShouldBeDefault(); + result.Name.ShouldBe("Captain Customer"); + result.Address.ShouldNotBeNull(); + result.Address.Line1.ShouldBe("Line 1"); + result.Address.Line2.ShouldBe("Line 2"); + } + + [Fact] + public void ShouldMapToDeepNestedComplexTypeMembersFromUntypedDottedEntries() + { + var source = new Dictionary + { + ["Value[0].Value.SetValue[0].Title"] = "Mr", + ["Value[0].Value.SetValue[0].Name"] = "Franks", + ["Value[0].Value.SetValue[0].Address.Line1"] = "Somewhere", + ["Value[0].Value.SetValue[0].Address.Line2"] = "Over the rainbow", + ["Value[0].Value.SetValue[1]"] = new PersonViewModel { Name = "Mike", AddressLine1 = "La la la" }, + ["Value[0].Value.SetValue[2].Title"] = 5, + ["Value[0].Value.SetValue[2].Name"] = "Wilkes", + ["Value[0].Value.SetValue[2].Address.Line1"] = "Over there", + ["Value[1].Value.SetValue[0].Title"] = 737328, + ["Value[1].Value.SetValue[0].Name"] = "Rob", + ["Value[1].Value.SetValue[0].Address.Line1"] = "Some place" + }; + + var result = Mapper + .Map(source) + .ToANew>>>>(); + + result.Value.Count.ShouldBe(2); + + result.Value.First().Value.Value.Length.ShouldBe(3); + result.Value.Second().Value.Value.Length.ShouldBe(1); + + result.Value.First().Value.Value.First().Title.ShouldBe(Title.Mr); + result.Value.First().Value.Value.First().Name.ShouldBe("Franks"); + result.Value.First().Value.Value.First().Address.Line1.ShouldBe("Somewhere"); + result.Value.First().Value.Value.First().Address.Line2.ShouldBe("Over the rainbow"); + + result.Value.First().Value.Value.Second().Title.ShouldBeDefault(); + result.Value.First().Value.Value.Second().Name.ShouldBe("Mike"); + result.Value.First().Value.Value.Second().Address.Line1.ShouldBe("La la la"); + result.Value.First().Value.Value.Second().Address.Line2.ShouldBeDefault(); + + result.Value.First().Value.Value.Third().Title.ShouldBe(Title.Mrs); + result.Value.First().Value.Value.Third().Name.ShouldBe("Wilkes"); + result.Value.First().Value.Value.Third().Address.Line1.ShouldBe("Over there"); + result.Value.First().Value.Value.Third().Address.Line2.ShouldBeDefault(); + + result.Value.Second().Value.Value.First().Title.ShouldBeDefault(); + result.Value.Second().Value.Value.First().Name.ShouldBe("Rob"); + result.Value.Second().Value.Value.First().Address.Line1.ShouldBe("Some place"); + result.Value.Second().Value.Value.First().Address.Line2.ShouldBeDefault(); + } + } +} \ No newline at end of file diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs index c01eb6f8c..c3dc6561f 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypes.cs @@ -2,7 +2,6 @@ { using System; using System.Collections.Generic; - using System.Linq; using Shouldly; using TestClasses; using Xunit; @@ -47,24 +46,6 @@ public void ShouldMapToAStringSetMethodFromATypedEntry() result.Value.ShouldBe("Goodbye"); } - [Fact] - public void ShouldMapToANestedStringMemberFromTypedDottedEntries() - { - var source = new Dictionary { ["Value.Value"] = "Over there!" }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.Value.ShouldBe("Over there!"); - } - - [Fact] - public void ShouldMapToANestedBoolMemberFromUntypedDottedEntries() - { - var source = new Dictionary { ["Value.Value"] = "true" }; - var result = Mapper.Map(source).ToANew>>(); - - result.Value.Value.ShouldBeTrue(); - } - [Fact] public void ShouldConvertASimpleTypeMemberValue() { @@ -95,54 +76,6 @@ public void ShouldMapToSimpleTypeConstructorParameterFromUntypedEntry() result.Value.ShouldBe(guid); } - [Fact] - public void ShouldMapToDeepNestedComplexTypeMembersFromUntypedDottedEntries() - { - var source = new Dictionary - { - ["Value[0].Value.SetValue[0].Title"] = "Mr", - ["Value[0].Value.SetValue[0].Name"] = "Franks", - ["Value[0].Value.SetValue[0].Address.Line1"] = "Somewhere", - ["Value[0].Value.SetValue[0].Address.Line2"] = "Over the rainbow", - ["Value[0].Value.SetValue[1]"] = new PersonViewModel { Name = "Mike", AddressLine1 = "La la la" }, - ["Value[0].Value.SetValue[2].Title"] = 5, - ["Value[0].Value.SetValue[2].Name"] = "Wilkes", - ["Value[0].Value.SetValue[2].Address.Line1"] = "Over there", - ["Value[1].Value.SetValue[0].Title"] = 737328, - ["Value[1].Value.SetValue[0].Name"] = "Rob", - ["Value[1].Value.SetValue[0].Address.Line1"] = "Some place" - }; - - var result = Mapper - .Map(source) - .ToANew>>>>(); - - result.Value.Count.ShouldBe(2); - - result.Value.First().Value.Value.Length.ShouldBe(3); - result.Value.Second().Value.Value.Length.ShouldBe(1); - - result.Value.First().Value.Value.First().Title.ShouldBe(Title.Mr); - result.Value.First().Value.Value.First().Name.ShouldBe("Franks"); - result.Value.First().Value.Value.First().Address.Line1.ShouldBe("Somewhere"); - result.Value.First().Value.Value.First().Address.Line2.ShouldBe("Over the rainbow"); - - result.Value.First().Value.Value.Second().Title.ShouldBeDefault(); - result.Value.First().Value.Value.Second().Name.ShouldBe("Mike"); - result.Value.First().Value.Value.Second().Address.Line1.ShouldBe("La la la"); - result.Value.First().Value.Value.Second().Address.Line2.ShouldBeDefault(); - - result.Value.First().Value.Value.Third().Title.ShouldBe(Title.Mrs); - result.Value.First().Value.Value.Third().Name.ShouldBe("Wilkes"); - result.Value.First().Value.Value.Third().Address.Line1.ShouldBe("Over there"); - result.Value.First().Value.Value.Third().Address.Line2.ShouldBeDefault(); - - result.Value.Second().Value.Value.First().Title.ShouldBeDefault(); - result.Value.Second().Value.Value.First().Name.ShouldBe("Rob"); - result.Value.Second().Value.Value.First().Address.Line1.ShouldBe("Some place"); - result.Value.Second().Value.Value.First().Address.Line2.ShouldBeDefault(); - } - [Fact] public void ShouldIgnoreANonStringKeyedDictionary() { diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs index 1deed81d0..7c68efe73 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewEnumerableMembers.cs @@ -43,6 +43,18 @@ public void ShouldMapToASimpleTypeCollectionFromConvertibleTypedEntries() now.AddHours(2).ToCurrentCultureString()); } + [Fact] + public void ShouldMapToASimpleTypeCollectionFromAnUntypedEntry() + { + var source = new Dictionary + { + ["Value"] = new[] { '1', '2', '3' } + }; + var result = Mapper.Map(source).ToANew>>(); + + result.Value.ShouldBe(1, 2, 3); + } + [Fact] public void ShouldMapToAComplexTypeArrayFromUntypedEntries() { diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs new file mode 100644 index 000000000..1a4008707 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs @@ -0,0 +1,29 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsToNewComplexTypeMembers + { + [Fact] + public void ShouldMapToNestedSimpleTypeMembers() + { + dynamic source = new ExpandoObject(); + source.Name = "Dynamic Customer!"; + source.Address = new Address + { + Line1 = "Dynamic Line 1!", + Line2 = "Dynamic Line 2!", + }; + + var result = (Customer)Mapper.Map(source).ToANew(); + + result.Name = "Dynamic Customer!"; + result.Address.ShouldNotBeNull(); + result.Address.Line1.ShouldBe("Dynamic Line 1!"); + result.Address.Line2.ShouldBe("Dynamic Line 2!"); + } + } +} \ No newline at end of file diff --git a/AgileMapper/Members/DictionarySourceMember.cs b/AgileMapper/Members/DictionarySourceMember.cs index 576bd06c7..528375928 100644 --- a/AgileMapper/Members/DictionarySourceMember.cs +++ b/AgileMapper/Members/DictionarySourceMember.cs @@ -96,7 +96,8 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) ValueType); } - public IQualifiedMember WithType(Type runtimeType) => this; + public IQualifiedMember WithType(Type runtimeType) + => (runtimeType != _wrappedSourceMember.Type) ? EntryMember.WithType(runtimeType) : this; public bool CouldMatch(QualifiedMember otherMember) => _wrappedSourceMember.CouldMatch(otherMember); From 43011e66ac86b7df6fd91e9941717352e5e6ffa3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 7 Dec 2017 20:47:04 +0000 Subject: [PATCH 08/74] Renaming dictionary key matchers for clarity / Using efficient string.FirstOrDefault() extension method instead of string.Left(numberOfCharacters) when converting strings to character --- .../DataSources/DictionaryEntryVariablePair.cs | 14 +++++++------- .../Extensions/StringExpressionExtensions.cs | 7 +++---- AgileMapper/Extensions/StringExtensions.cs | 6 +++--- AgileMapper/TypeConversion/ToCharacterConverter.cs | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index 6326d345d..62ce7fab6 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -106,8 +106,8 @@ public Expression GetMatchingKeyAssignment(Expression targetMemberKey) var firstMatchingKeyOrNull = GetKeyMatchingQuery( HasConstantTargetMemberKey ? targetMemberKey : Key, - Expression.Equal, (keyParameter, targetKey) => keyParameter.GetCaseInsensitiveEquals(targetKey), + Expression.Equal, _linqFirstOrDefaultMethod); var keyVariableAssignment = GetKeyAssignment(firstMatchingKeyOrNull); @@ -125,8 +125,8 @@ public Expression GetNoKeysWithMatchingStartQuery(Expression targetMemberKey) var noKeysStartWithTarget = GetKeyMatchingQuery( targetMemberKey, - (keyParameter, targetKey) => GetKeyStartsWithCall(keyParameter, targetKey, StringComparison.Ordinal), GetKeyStartsWithIgnoreCaseCall, + (keyParameter, targetKey) => GetKeyStartsWithCall(keyParameter, targetKey, StringComparison.Ordinal), EnumerableExtensions.EnumerableNoneMethod); return noKeysStartWithTarget; @@ -153,15 +153,15 @@ private static Expression GetKeyStartsWithCall( private Expression GetKeyMatchingQuery( Expression targetMemberKey, - Func rootKeyMatcherFactory, - Func nestedKeyMatcherFactory, + Func keyMatcherFactory, + Func elementKeyMatcherFactory, MethodInfo queryMethod) { var keyParameter = Expression.Parameter(typeof(string), "key"); - var keyMatcher = MapperData.IsRoot - ? rootKeyMatcherFactory.Invoke(keyParameter, targetMemberKey) - : nestedKeyMatcherFactory.Invoke(keyParameter, targetMemberKey); + var keyMatcher = MapperData.TargetMemberIsEnumerableElement() + ? elementKeyMatcherFactory.Invoke(keyParameter, targetMemberKey) + : keyMatcherFactory.Invoke(keyParameter, targetMemberKey); var keyMatchesLambda = Expression.Lambda>(keyMatcher, keyParameter); diff --git a/AgileMapper/Extensions/StringExpressionExtensions.cs b/AgileMapper/Extensions/StringExpressionExtensions.cs index 6067d569a..84593a4f9 100644 --- a/AgileMapper/Extensions/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/StringExpressionExtensions.cs @@ -85,12 +85,11 @@ private static void OptimiseForStringConcat(IList expressions) expressions.Insert(0, currentNamePart.ToConstantExpression()); } - public static Expression GetLeftCall(this Expression stringAccess, int numberOfCharacters) + public static Expression GetFirstOrDefaultCall(this Expression stringAccess) { return Expression.Call( - typeof(StringExtensions).GetPublicStaticMethod("Left"), - stringAccess, - numberOfCharacters.ToConstantExpression()); + typeof(StringExtensions).GetPublicStaticMethod("FirstOrDefault"), + stringAccess); } } } \ No newline at end of file diff --git a/AgileMapper/Extensions/StringExtensions.cs b/AgileMapper/Extensions/StringExtensions.cs index 3b856c7d0..8ddd2aaff 100644 --- a/AgileMapper/Extensions/StringExtensions.cs +++ b/AgileMapper/Extensions/StringExtensions.cs @@ -8,14 +8,14 @@ public static string ToPascalCase(this string value) public static string ToCamelCase(this string value) => char.ToLowerInvariant(value[0]) + value.Substring(1); - public static string Left(this string value, int numberOfCharacters) + public static string FirstOrDefault(this string value) { - if (string.IsNullOrEmpty(value) || (value.Length <= numberOfCharacters)) + if (string.IsNullOrEmpty(value) || (value.Length <= 1)) { return value; } - return value.Substring(0, numberOfCharacters); + return value[0].ToString(); } } } \ No newline at end of file diff --git a/AgileMapper/TypeConversion/ToCharacterConverter.cs b/AgileMapper/TypeConversion/ToCharacterConverter.cs index 0670679dc..5badd3e61 100644 --- a/AgileMapper/TypeConversion/ToCharacterConverter.cs +++ b/AgileMapper/TypeConversion/ToCharacterConverter.cs @@ -76,7 +76,7 @@ private Expression GetFromNumericConversion(Expression sourceValue, Type targetT if (!isWholeNumberNumeric) { - stringValue = stringValue.GetLeftCall(numberOfCharacters: 1); + stringValue = stringValue.GetFirstOrDefaultCall(); } var convertedStringValue = GetFromStringConversion(stringValue, targetType); From 3aa65d10f76bda176d14d9b162f417ac458c393d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 7 Dec 2017 21:09:37 +0000 Subject: [PATCH 09/74] Supporting mapping from dynamic members with flattened names to nested target members --- .../WhenViewingDictionaryMappingPlans.cs | 2 +- ...pingFromDynamicsToNewComplexTypeMembers.cs | 16 ++++++++++++++ .../DictionaryEntryVariablePair.cs | 2 +- .../Extensions/StringExpressionExtensions.cs | 8 +++++++ AgileMapper/Extensions/StringExtensions.cs | 22 +++++++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/WhenViewingDictionaryMappingPlans.cs b/AgileMapper.UnitTests/Dictionaries/WhenViewingDictionaryMappingPlans.cs index b75b50fe2..f6f91557b 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenViewingDictionaryMappingPlans.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenViewingDictionaryMappingPlans.cs @@ -16,7 +16,7 @@ public void ShouldShowATargetObjectMappingPlan() .ToANew(); plan.ShouldContain("Dictionary sourceDictionary_String_String"); - plan.ShouldContain("idKey = sourceDictionary_String_String.Keys.FirstOrDefault(key => string.Equals(key, \"Id\""); + plan.ShouldContain("idKey = sourceDictionary_String_String.Keys.FirstOrDefault(key => key.MatchesKey(\"Id\""); plan.ShouldContain("id = sourceDictionary_String_String[idKey]"); plan.ShouldContain("customerViewModel.Id ="); plan.ShouldContain("Guid.TryParse(id"); diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs index 1a4008707..02060a664 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs @@ -25,5 +25,21 @@ public void ShouldMapToNestedSimpleTypeMembers() result.Address.Line1.ShouldBe("Dynamic Line 1!"); result.Address.Line2.ShouldBe("Dynamic Line 2!"); } + + [Fact] + public void ShouldMapFlattenedPropertiesToNestedSimpleTypeMembers() + { + dynamic source = new ExpandoObject(); + source.name = "Dynamic Person"; + source.addressLine1 = "Dynamic Line 1"; + source.addressLine2 = "Dynamic Line 2"; + + var result = (Person)Mapper.Map(source).ToANew(); + + result.Name = "Dynamic Person"; + result.Address.ShouldNotBeNull(); + result.Address.Line1.ShouldBe("Dynamic Line 1"); + result.Address.Line2.ShouldBe("Dynamic Line 2"); + } } } \ No newline at end of file diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index 62ce7fab6..d8e09bc83 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -106,7 +106,7 @@ public Expression GetMatchingKeyAssignment(Expression targetMemberKey) var firstMatchingKeyOrNull = GetKeyMatchingQuery( HasConstantTargetMemberKey ? targetMemberKey : Key, - (keyParameter, targetKey) => keyParameter.GetCaseInsensitiveEquals(targetKey), + (keyParameter, targetKey) => keyParameter.GetMatchesKeyCall(targetKey), Expression.Equal, _linqFirstOrDefaultMethod); diff --git a/AgileMapper/Extensions/StringExpressionExtensions.cs b/AgileMapper/Extensions/StringExpressionExtensions.cs index 84593a4f9..721557277 100644 --- a/AgileMapper/Extensions/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/StringExpressionExtensions.cs @@ -91,5 +91,13 @@ public static Expression GetFirstOrDefaultCall(this Expression stringAccess) typeof(StringExtensions).GetPublicStaticMethod("FirstOrDefault"), stringAccess); } + + public static Expression GetMatchesKeyCall(this Expression stringAccess, Expression keyValue) + { + return Expression.Call( + typeof(StringExtensions).GetPublicStaticMethod("MatchesKey"), + stringAccess, + keyValue); + } } } \ No newline at end of file diff --git a/AgileMapper/Extensions/StringExtensions.cs b/AgileMapper/Extensions/StringExtensions.cs index 8ddd2aaff..be0ab8a63 100644 --- a/AgileMapper/Extensions/StringExtensions.cs +++ b/AgileMapper/Extensions/StringExtensions.cs @@ -1,5 +1,7 @@ namespace AgileObjects.AgileMapper.Extensions { + using System; + internal static class StringExtensions { public static string ToPascalCase(this string value) @@ -17,5 +19,25 @@ public static string FirstOrDefault(this string value) return value[0].ToString(); } + + public static bool MatchesKey(this string subjectKey, string queryKey) + { + if (queryKey == null) + { + // This can happen when mapping to types with multiple, nested + // recursive relationships, e.g: + // Dictionary<,> -> Order -> OrderItems -> Order -> OrderItems + // ...it's basically not supported + return false; + } + + if (subjectKey.Equals(queryKey, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return (queryKey.IndexOf('.') != -1) && + subjectKey.Equals(queryKey.Replace(".", null), StringComparison.OrdinalIgnoreCase); + } } } \ No newline at end of file From 2b2f47b7689ae14a76c6524a5c21afcb48e95afa Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 7 Dec 2017 21:23:28 +0000 Subject: [PATCH 10/74] Supporting flattened source Dictionary keys by default, without configuration --- ...ConfiguringDictionaryMappingIncorrectly.cs | 29 ++++--------------- .../WhenConfiguringSourceDictionaryMapping.cs | 28 ------------------ .../WhenConfiguringTargetDictionaryMapping.cs | 2 +- ...FromDictionariesToNewComplexTypeMembers.cs | 18 ++++++++++++ .../Dictionaries/DictionaryConfigurator.cs | 2 +- .../Dictionaries/IGlobalDictionarySettings.cs | 8 ++--- .../ISourceDictionaryConfigSettings.cs | 11 ------- .../SourceDictionaryMappingConfigurator.cs | 6 ---- 8 files changed, 29 insertions(+), 75 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs index 2bff412ef..b8ca5d475 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs @@ -167,7 +167,7 @@ public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedGlobally() { mapper.WhenMapping .Dictionaries - .UseFlattenedMemberNames() + .UseFlattenedTargetMemberNames() .UseMemberNameSeparator("+"); } }); @@ -186,7 +186,7 @@ public void ShouldErrorIfMemberNamesAreSeparatedAndFlattenedGlobally() mapper.WhenMapping .Dictionaries .UseMemberNameSeparator("+") - .UseFlattenedMemberNames(); + .UseFlattenedTargetMemberNames(); } }); @@ -195,7 +195,7 @@ public void ShouldErrorIfMemberNamesAreSeparatedAndFlattenedGlobally() } [Fact] - public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedForASpecificTargetType() + public void ShouldErrorIfDifferentSeparatorsSpecifiedForASpecificTargetType() { var configEx = Should.Throw(() => { @@ -204,32 +204,13 @@ public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedForASpecificTargetTy mapper.WhenMapping .Dictionaries .To>>() - .UseFlattenedMemberNames() + .UseMemberNameSeparator("-") .UseMemberNameSeparator("_"); } }); configEx.Message.ShouldContain("PublicField>"); - configEx.Message.ShouldContain("flattened"); - } - - [Fact] - public void ShouldErrorIfMemberNamesAreSeparatedAndFlattenedForASpecificTargetType() - { - var configEx = Should.Throw(() => - { - using (var mapper = Mapper.CreateNew()) - { - mapper.WhenMapping - .Dictionaries - .To>>() - .UseMemberNameSeparator("+") - .UseFlattenedMemberNames(); - } - }); - - configEx.Message.ShouldContain("PublicProperty>"); - configEx.Message.ShouldContain("separated with '+'"); + configEx.Message.ShouldContain("separated with '-'"); } [Fact] diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs index b7214e9f2..7b5905eb9 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs @@ -169,31 +169,6 @@ public void ShouldApplyNonDictionarySpecificConfiguration() } } - [Fact] - public void ShouldApplyFlattenedMemberNamesGlobally() - { - using (var mapper = Mapper.CreateNew()) - { - mapper.WhenMapping - .Dictionaries - .UseFlattenedMemberNames(); - - var source = new Dictionary - { - ["Name"] = "Bob", - ["Discount"] = "0.1", - ["AddressLine1"] = "Bob's House", - ["AddressLine2"] = "Bob's Street" - }; - var result = mapper.Map(source).ToANew(); - - result.Name.ShouldBe("Bob"); - result.Discount.ShouldBe(0.1); - result.Address.Line1.ShouldBe("Bob's House"); - result.Address.Line2.ShouldBe("Bob's Street"); - } - } - [Fact] public void ShouldApplyFlattenedMemberNamesToASpecificTargetType() { @@ -202,8 +177,6 @@ public void ShouldApplyFlattenedMemberNamesToASpecificTargetType() mapper.WhenMapping .Dictionaries .To() - .UseFlattenedMemberNames() - .And .MapMemberNameKey("OrderCode") .To(o => o.OrderId); @@ -322,7 +295,6 @@ public void ShouldApplyACustomEnumerableElementPatternGlobally() { mapper.WhenMapping .Dictionaries - .UseFlattenedMemberNames() .UseElementKeyPattern("_i_") .AndWhenMapping .To>() diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index 610501e53..298c99bfe 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -17,7 +17,7 @@ public void ShouldApplyFlattenedMemberNamesGlobally() { mapper.WhenMapping .Dictionaries - .UseFlattenedMemberNames(); + .UseFlattenedTargetMemberNames(); var source = new MysteryCustomer { diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs index 93c27623f..8c7057fad 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingFromDictionariesToNewComplexTypeMembers.cs @@ -26,6 +26,24 @@ public void ShouldMapToABoolMemberFromUntypedDottedEntries() result.Value.Value.ShouldBeTrue(); } + [Fact] + public void ShouldMapToMemberFromFlattenedNameEntries() + { + var source = new Dictionary + { + ["Name"] = "Bob", + ["Discount"] = "0.1", + ["AddressLine1"] = "Bob's House", + ["AddressLine2"] = "Bob's Street" + }; + var result = Mapper.Map(source).ToANew(); + + result.Name.ShouldBe("Bob"); + result.Discount.ShouldBe(0.1); + result.Address.Line1.ShouldBe("Bob's House"); + result.Address.Line2.ShouldBe("Bob's Street"); + } + [Fact] public void ShouldMapToNestedMembersFromUntypedEntries() { diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs index b760f9946..318d82cb1 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs @@ -25,7 +25,7 @@ internal DictionaryConfigurator(MappingConfigInfo configInfo) /// Person.Address.StreetName member would be populated using the dictionary entry with key /// 'AddressStreetName' when mapping to a root Person object. /// - public IGlobalDictionarySettings UseFlattenedMemberNames() + public IGlobalDictionarySettings UseFlattenedTargetMemberNames() { var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(GlobalConfigInfo); diff --git a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs index 23e17a49a..860a3a5ee 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs @@ -9,15 +9,15 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries public interface IGlobalDictionarySettings { /// - /// Construct dictionary keys for nested members using flattened member names. For example, a - /// Person.Address.StreetName member would be populated using the dictionary entry with key - /// 'AddressStreetName' when mapping to a root Person object. + /// Construct keys for target dictionary members using flattened member names. For example, a + /// Person.Address.StreetName member would be mapped to a dictionary entry with the key + /// 'AddressStreetName'. /// /// /// An with which to globally configure other /// dictionary mapping aspects. /// - IGlobalDictionarySettings UseFlattenedMemberNames(); + IGlobalDictionarySettings UseFlattenedTargetMemberNames(); /// /// Use the given to separate member names when mapping to nested diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs index f7a7f2231..13c2d9935 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs @@ -9,17 +9,6 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries /// The target type to which the configuration should apply. public interface ISourceDictionaryConfigSettings { - /// - /// Construct dictionary keys for nested members using flattened member names. For example, a - /// Person.Address.StreetName member would be populated using the dictionary entry with key - /// 'AddressStreetName' when mapping to a root Person object. - /// - /// - /// An ISourceDictionaryConfigSettings to enable further configuration of mappings from dictionaries - /// to the target type being configured. - /// - ISourceDictionaryConfigSettings UseFlattenedMemberNames(); - /// /// Use the given to separate member names when mapping to nested /// complex type members. For example, calling UseMemberName("-") will require a dictionary entry diff --git a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs index 9e30121d2..24269ce80 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs @@ -15,12 +15,6 @@ public SourceDictionaryMappingConfigurator(MappingConfigInfo configInfo) #region ISourceDictionaryConfigSettings Members - public ISourceDictionaryConfigSettings UseFlattenedMemberNames() - { - SetupFlattenedMemberNames(); - return this; - } - public ISourceDictionaryConfigSettings UseMemberNameSeparator(string separator) { SetupMemberNameSeparator(separator); From a15496ca0c89b4aa9d3657bb9a5f4640e16762bd Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 8 Dec 2017 17:03:51 +0000 Subject: [PATCH 11/74] Test coverage for mapping from a dynamic to a simple type enumerable member --- .../AgileMapper.UnitTests.csproj | 1 + ...henMappingFromDynamicsToNewComplexTypes.cs | 2 +- ...ppingFromDynamicsToNewEnumerableMembers.cs | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 355690779..4e0a18fca 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -104,6 +104,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs index 108cf6131..78acadd7a 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs @@ -32,7 +32,7 @@ public void ShouldConvertASimpleTypeMemberValue() [Fact] public void ShouldHandleANullASimpleTypeMemberValue() { - dynamic source = new ExpandoObject(); + dynamic source = new ExpandoObject{}; source.Value = default(string); var result = (PublicSetMethod)Mapper.Map(source).ToANew>(); diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs new file mode 100644 index 000000000..66a695fe4 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs @@ -0,0 +1,20 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsToNewEnumerableMembers + { + [Fact] + public void ShouldMapToASimpleTypeCollectionMember() + { + dynamic source = new ExpandoObject(); + source.Value = new[] { "a", "b", "c" }; + + var result = (PublicField)Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe('a', 'b', 'c'); + } + } +} From dbc5ff5524980557d4494f60c72abaecaee905c9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 8 Dec 2017 17:28:47 +0000 Subject: [PATCH 12/74] Support for mapping dynamic non-identifiable complex type collection members to enumerable collection members / Removing double .Map() call from Dictionary entry -> complex type enumerable mapping / Adding Type.IsClosedTypeOf extension method --- ...ppingFromDynamicsToNewEnumerableMembers.cs | 20 +++++++++++++++++++ AgileMapper/Extensions/TypeExtensions.cs | 7 ++++--- AgileMapper/Members/MemberExtensions.cs | 4 +--- .../Enumerables/EnumerableTypeHelper.cs | 3 +-- ...ourceObjectDictionaryPopulationLoopData.cs | 8 ++++---- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs index 66a695fe4..c41c53558 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { + using System.Collections.Generic; using System.Dynamic; using TestClasses; using Xunit; @@ -16,5 +17,24 @@ public void ShouldMapToASimpleTypeCollectionMember() result.Value.ShouldBe('a', 'b', 'c'); } + + [Fact] + public void ShouldMapToAComplexTypeEnumerableMember() + { + dynamic source = new ExpandoObject(); + + source.Value = new[] + { + new PublicField { Value = "1" }, + new PublicField { Value = "2" }, + new PublicField { Value = "3" } + }; + + var result = (PublicProperty>>)Mapper + .Map(source) + .ToANew>>>(); + + result.Value.ShouldBe(pf => pf.Value, 1, 2, 3); + } } } diff --git a/AgileMapper/Extensions/TypeExtensions.cs b/AgileMapper/Extensions/TypeExtensions.cs index 69a38541f..8ddf302e0 100644 --- a/AgileMapper/Extensions/TypeExtensions.cs +++ b/AgileMapper/Extensions/TypeExtensions.cs @@ -229,13 +229,14 @@ public static Type GetDictionaryType(this Type type) var interfaceType = type .GetAllInterfaces() - .FirstOrDefault(t => - t.IsGenericType() && - (t.GetGenericTypeDefinition() == typeof(IDictionary<,>))); + .FirstOrDefault(t => t.IsClosedTypeOf(typeof(IDictionary<,>))); return interfaceType; } + public static bool IsClosedTypeOf(this Type type, Type genericTypeDefinition) + => type.IsGenericType() && (type.GetGenericTypeDefinition() == genericTypeDefinition); + private static KeyValuePair GetDictionaryTypesFrom(Type type) { var types = type.GetGenericTypeArguments(); diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 6b78867d4..ab4b8f92a 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -89,9 +89,7 @@ public static bool IsUnmappable(this QualifiedMember member, out string reason) return true; } - if (member.IsEnumerable && - member.Type.IsGenericType() && - (member.Type.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>))) + if (member.IsEnumerable && member.Type.IsClosedTypeOf(typeof(ReadOnlyCollection<>))) { reason = "readonly " + member.Type.GetFriendlyName(); return true; diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs index d10b51a8f..0b3d991a9 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs @@ -48,8 +48,7 @@ public bool IsDeclaredReadOnly private bool IsReadOnlyCollectionInterface() { #if NET_STANDARD - return EnumerableType.IsGenericType() && - (EnumerableType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)); + return EnumerableType.IsClosedTypeOf(typeof(IReadOnlyCollection<>)); #else return EnumerableType.IsInterface() && (EnumerableType.Name == "IReadOnlyCollection`1") && diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs index 7354e97b4..f3b5ee40b 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs @@ -50,10 +50,10 @@ private Expression GetCompositeLoopExitCheck() public Expression GetElementMapping(IObjectMappingData enumerableMappingData) { - var convertedEnumeratorValue = _enumerableLoopData.GetElementMapping(enumerableMappingData); - var convertedElementValue = _elementsDictionaryLoopData.GetElementMapping(enumerableMappingData); - - return Expression.Condition(_sourceEnumerableFound, convertedEnumeratorValue, convertedElementValue); + return Expression.Condition( + _sourceEnumerableFound, + _enumerableLoopData.SourceElement, + _elementsDictionaryLoopData.GetElementMapping(enumerableMappingData)); } public Expression Adapt(LoopExpression loop) From ca0445f7135cb421e702fefac8d0d674988d9358 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 8 Dec 2017 22:22:50 +0000 Subject: [PATCH 13/74] Support for mapping from dynamic, underscored-index enumerable element members to complex type enumerable members --- ...ppingFromDynamicsToNewEnumerableMembers.cs | 16 +++++++ .../Configuration/DictionarySettings.cs | 1 + .../Configuration/ElementKeyPartFactory.cs | 14 +++++- .../Configuration/MappingConfigInfo.cs | 39 ++------------- .../Configuration/UserConfiguredItemBase.cs | 2 +- AgileMapper/Constants.cs | 1 + AgileMapper/Members/BasicMapperData.cs | 3 ++ AgileMapper/Members/ChildMemberMapperData.cs | 2 + AgileMapper/Members/ConfiguredSourceMember.cs | 2 + .../Members/DictionaryEntrySourceMember.cs | 2 + AgileMapper/Members/DictionarySourceMember.cs | 2 + AgileMapper/Members/IBasicMapperData.cs | 10 ++-- AgileMapper/Members/IQualifiedMember.cs | 2 + AgileMapper/Members/ITypePair.cs | 11 +++++ AgileMapper/Members/QualifiedMember.cs | 3 ++ AgileMapper/Members/TypePairExtensions.cs | 47 +++++++++++++++++++ .../MappingCallbackFactory.cs | 2 +- .../ObjectPopulation/ObjectMapperData.cs | 16 +++++-- 18 files changed, 126 insertions(+), 49 deletions(-) create mode 100644 AgileMapper/Members/ITypePair.cs create mode 100644 AgileMapper/Members/TypePairExtensions.cs diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs index c41c53558..aabcdb643 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs @@ -36,5 +36,21 @@ public void ShouldMapToAComplexTypeEnumerableMember() result.Value.ShouldBe(pf => pf.Value, 1, 2, 3); } + + [Fact] + public void ShouldMapToAComplexTypeEnumerableMemberFromComplexTypeEntries() + { + dynamic source = new ExpandoObject(); + + source.Value_0_ = new PublicProperty { Value = '9' }; + source.Value_1_ = new PublicProperty { Value = '8' }; + source.Value_2_ = new PublicProperty { Value = '7' }; + + var result = (PublicField>>)Mapper + .Map(source) + .ToANew>>>(); + + result.Value.ShouldBe(pf => pf.Value, 9, 8, 7); + } } } diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/DictionarySettings.cs index 6a646ffd1..c5e1bee1b 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/DictionarySettings.cs @@ -26,6 +26,7 @@ public DictionarySettings(MapperContext mapperContext) _elementKeyPartFactories = new List { + ElementKeyPartFactory.UnderscoredIndexForDynamics(mapperContext), ElementKeyPartFactory.SquareBracketedIndex(mapperContext) }; } diff --git a/AgileMapper/Configuration/ElementKeyPartFactory.cs b/AgileMapper/Configuration/ElementKeyPartFactory.cs index c2bfcfae0..005ff98b7 100644 --- a/AgileMapper/Configuration/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/ElementKeyPartFactory.cs @@ -1,9 +1,11 @@ namespace AgileObjects.AgileMapper.Configuration { using System.Collections.Generic; + using System.Dynamic; using System.Linq.Expressions; using System.Text.RegularExpressions; using Extensions; + using Members; internal class ElementKeyPartFactory : UserConfiguredItemBase { @@ -29,6 +31,16 @@ private ElementKeyPartFactory( #region Factory Methods + public static ElementKeyPartFactory UnderscoredIndexForDynamics(MapperContext mapperContext) + { + var sourceExpandoObject = new MappingConfigInfo(mapperContext) + .ForAllRuleSets() + .ForSourceType() + .ForAllTargetTypes(); + + return new ElementKeyPartFactory("_", "_", sourceExpandoObject); + } + public static ElementKeyPartFactory SquareBracketedIndex(MapperContext mapperContext) => new ElementKeyPartFactory("[", "]", MappingConfigInfo.AllRuleSetsSourceTypesAndTargetTypes(mapperContext)); @@ -55,7 +67,7 @@ public static ElementKeyPartFactory For(string pattern, MappingConfigInfo config var prefix = patternMatch.Groups["Prefix"].Value; var suffix = patternMatch.Groups["Suffix"].Value; - if (!configInfo.IsForAllSourceTypes && !configInfo.SourceType.IsEnumerable()) + if (!configInfo.IsForAllSourceTypes() && !configInfo.SourceType.IsEnumerable()) { configInfo = configInfo .Clone() diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index cbaae73f7..b13f35237 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -5,13 +5,11 @@ using System.Linq.Expressions; using Extensions; using Members; - using NetStandardPolyfills; using ObjectPopulation; using ReadableExpressions; - internal class MappingConfigInfo + internal class MappingConfigInfo : ITypePair { - private static readonly Type _allSourceTypes = typeof(MappingConfigInfo); private static readonly MappingRuleSet _allRuleSets = new MappingRuleSet("*", true, null, null, null); private ConfiguredLambdaInfo _conditionLambda; @@ -36,7 +34,7 @@ public static MappingConfigInfo AllRuleSetsSourceTypesAndTargetTypes(MapperConte public Type SourceType { get; private set; } - public MappingConfigInfo ForAllSourceTypes() => ForSourceType(_allSourceTypes); + public MappingConfigInfo ForAllSourceTypes() => ForSourceType(Constants.AllTypes); public MappingConfigInfo ForSourceType() => ForSourceType(typeof(TSource)); @@ -48,13 +46,6 @@ public MappingConfigInfo ForSourceType(Type sourceType) public bool HasSameSourceTypeAs(MappingConfigInfo otherConfigInfo) => otherConfigInfo.SourceType == SourceType; - public bool IsForSourceType(MappingConfigInfo otherConfigInfo) => IsForSourceType(otherConfigInfo.SourceType); - - private bool IsForSourceType(Type sourceType) - => IsForAllSourceTypes || sourceType.IsAssignableTo(SourceType); - - public bool IsForAllSourceTypes => SourceType == _allSourceTypes; - public Type TargetType { get; private set; } public MappingConfigInfo ForAllTargetTypes() => ForTargetType(); @@ -67,34 +58,10 @@ public MappingConfigInfo ForTargetType(Type targetType) return this; } - public bool IsForTargetType(MappingConfigInfo otherConfigInfo) - => otherConfigInfo.TargetType.IsAssignableTo(TargetType); - public bool HasSameTargetTypeAs(MappingConfigInfo otherConfigInfo) => TargetType == otherConfigInfo.TargetType; public bool HasCompatibleTypes(MappingConfigInfo otherConfigInfo) - => HasCompatibleTypes(otherConfigInfo.SourceType, otherConfigInfo.TargetType); - - public bool HasCompatibleTypes(IBasicMapperData mapperData) - => HasCompatibleTypes(mapperData.SourceType, mapperData.TargetType); - - public bool HasCompatibleTypes(Type sourceType, Type targetType) - { - if (IsForSourceType(sourceType)) - { - if (targetType.IsAssignableTo(TargetType)) - { - return true; - } - - if (targetType.IsInterface()) - { - return Array.IndexOf(TargetType.GetAllInterfaces(), targetType) != -1; - } - } - - return false; - } + => ((ITypePair)this).HasCompatibleTypes(otherConfigInfo); public MappingRuleSet RuleSet { get; private set; } diff --git a/AgileMapper/Configuration/UserConfiguredItemBase.cs b/AgileMapper/Configuration/UserConfiguredItemBase.cs index 550fec4ef..b34e77214 100644 --- a/AgileMapper/Configuration/UserConfiguredItemBase.cs +++ b/AgileMapper/Configuration/UserConfiguredItemBase.cs @@ -136,7 +136,7 @@ protected bool MemberPathHasMatchingSourceAndTargetTypes(IBasicMapperData mapper { while (mapperData != null) { - if (ConfigInfo.HasCompatibleTypes(mapperData)) + if (mapperData.HasCompatibleTypes(ConfigInfo)) { return true; } diff --git a/AgileMapper/Constants.cs b/AgileMapper/Constants.cs index d3e4f3b29..7c740920d 100644 --- a/AgileMapper/Constants.cs +++ b/AgileMapper/Constants.cs @@ -15,6 +15,7 @@ internal static class Constants public static readonly string EnumerableElementName = "[i]"; public static readonly Type[] NoTypeArguments = Enumerable.EmptyArray; + public static readonly Type AllTypes = typeof(Constants); public static readonly Expression EmptyExpression = Expression.Empty(); diff --git a/AgileMapper/Members/BasicMapperData.cs b/AgileMapper/Members/BasicMapperData.cs index 663060fc0..2f919aee2 100644 --- a/AgileMapper/Members/BasicMapperData.cs +++ b/AgileMapper/Members/BasicMapperData.cs @@ -32,5 +32,8 @@ public BasicMapperData( public Type TargetType { get; } public QualifiedMember TargetMember { get; } + + public virtual bool HasCompatibleTypes(ITypePair typePair) + => typePair.HasCompatibleTypes(this); } } \ No newline at end of file diff --git a/AgileMapper/Members/ChildMemberMapperData.cs b/AgileMapper/Members/ChildMemberMapperData.cs index 348b0b150..e8991d2d7 100644 --- a/AgileMapper/Members/ChildMemberMapperData.cs +++ b/AgileMapper/Members/ChildMemberMapperData.cs @@ -35,5 +35,7 @@ public ChildMemberMapperData(QualifiedMember targetMember, ObjectMapperData pare public Expression TargetInstance => Parent.TargetInstance; public ExpressionInfoFinder ExpressionInfoFinder => Parent.ExpressionInfoFinder; + + public override bool HasCompatibleTypes(ITypePair typePair) => Parent.HasCompatibleTypes(typePair); } } \ No newline at end of file diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index fc37c7c29..f297766a9 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -82,6 +82,8 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) relativeMemberChain); } + public bool HasCompatibleType(Type type) => false; + public bool CouldMatch(QualifiedMember otherMember) => _matchedTargetMemberJoinedNames.CouldMatch(otherMember.JoinedNames); diff --git a/AgileMapper/Members/DictionaryEntrySourceMember.cs b/AgileMapper/Members/DictionaryEntrySourceMember.cs index 6d6ea04a6..716d6af48 100644 --- a/AgileMapper/Members/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/DictionaryEntrySourceMember.cs @@ -98,6 +98,8 @@ public IQualifiedMember WithType(Type runtimeType) childMembers); } + public bool HasCompatibleType(Type type) => false; + public bool CouldMatch(QualifiedMember otherMember) => _matchedTargetMember.CouldMatch(otherMember); public bool Matches(IQualifiedMember otherMember) diff --git a/AgileMapper/Members/DictionarySourceMember.cs b/AgileMapper/Members/DictionarySourceMember.cs index 528375928..e447992f5 100644 --- a/AgileMapper/Members/DictionarySourceMember.cs +++ b/AgileMapper/Members/DictionarySourceMember.cs @@ -99,6 +99,8 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) public IQualifiedMember WithType(Type runtimeType) => (runtimeType != _wrappedSourceMember.Type) ? EntryMember.WithType(runtimeType) : this; + public bool HasCompatibleType(Type type) => _wrappedSourceMember.HasCompatibleType(type); + public bool CouldMatch(QualifiedMember otherMember) => _wrappedSourceMember.CouldMatch(otherMember); public bool Matches(IQualifiedMember otherMember) => _wrappedSourceMember.Matches(otherMember); diff --git a/AgileMapper/Members/IBasicMapperData.cs b/AgileMapper/Members/IBasicMapperData.cs index 92e473d81..c71cd0add 100644 --- a/AgileMapper/Members/IBasicMapperData.cs +++ b/AgileMapper/Members/IBasicMapperData.cs @@ -1,8 +1,6 @@ namespace AgileObjects.AgileMapper.Members { - using System; - - internal interface IBasicMapperData + internal interface IBasicMapperData : ITypePair { MappingRuleSet RuleSet { get; } @@ -10,10 +8,8 @@ internal interface IBasicMapperData IBasicMapperData Parent { get; } - Type SourceType { get; } - - Type TargetType { get; } - QualifiedMember TargetMember { get; } + + bool HasCompatibleTypes(ITypePair typePair); } } \ No newline at end of file diff --git a/AgileMapper/Members/IQualifiedMember.cs b/AgileMapper/Members/IQualifiedMember.cs index 17a866ce2..cc6723ab6 100644 --- a/AgileMapper/Members/IQualifiedMember.cs +++ b/AgileMapper/Members/IQualifiedMember.cs @@ -21,6 +21,8 @@ internal interface IQualifiedMember IQualifiedMember WithType(Type runtimeType); + bool HasCompatibleType(Type type); + bool CouldMatch(QualifiedMember otherMember); bool Matches(IQualifiedMember otherMember); diff --git a/AgileMapper/Members/ITypePair.cs b/AgileMapper/Members/ITypePair.cs new file mode 100644 index 000000000..726fd1a64 --- /dev/null +++ b/AgileMapper/Members/ITypePair.cs @@ -0,0 +1,11 @@ +namespace AgileObjects.AgileMapper.Members +{ + using System; + + internal interface ITypePair + { + Type SourceType { get; } + + Type TargetType { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index 8125a4d4f..a9eb50e89 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -7,6 +7,7 @@ namespace AgileObjects.AgileMapper.Members using System.Linq.Expressions; using Caching; using Extensions; + using NetStandardPolyfills; using ReadableExpressions.Extensions; internal class QualifiedMember : IQualifiedMember @@ -243,6 +244,8 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) IQualifiedMember IQualifiedMember.WithType(Type runtimeType) => WithType(runtimeType); + public bool HasCompatibleType(Type type) => Type.IsAssignableTo(type); + public QualifiedMember WithType(Type runtimeType) { if (runtimeType == Type) diff --git a/AgileMapper/Members/TypePairExtensions.cs b/AgileMapper/Members/TypePairExtensions.cs new file mode 100644 index 000000000..8c5bfb665 --- /dev/null +++ b/AgileMapper/Members/TypePairExtensions.cs @@ -0,0 +1,47 @@ +namespace AgileObjects.AgileMapper.Members +{ + using System; + using NetStandardPolyfills; + + internal static class TypePairExtensions + { + public static bool IsForAllSourceTypes(this ITypePair typePair) + => typePair.SourceType == Constants.AllTypes; + + public static bool IsForSourceType(this ITypePair typePair, ITypePair otherTypePair) + => typePair.IsForSourceType(otherTypePair.SourceType); + + private static bool IsForSourceType(this ITypePair typePair, Type sourceType) + => IsForAllSourceTypes(typePair) || sourceType.IsAssignableTo(typePair.SourceType); + + public static bool IsForTargetType(this ITypePair typePair, ITypePair otherTypePair) + => otherTypePair.TargetType.IsAssignableTo(typePair.TargetType); + + public static bool HasCompatibleTypes( + this ITypePair typePair, + ITypePair otherTypePair, + Func sourceTypeMatcher = null) + { + var sourceTypesMatch = + typePair.IsForSourceType(otherTypePair.SourceType) || + (sourceTypeMatcher?.Invoke() == true); + + if (!sourceTypesMatch) + { + return false; + } + + if (otherTypePair.TargetType.IsAssignableTo(typePair.TargetType)) + { + return true; + } + + if (otherTypePair.TargetType.IsInterface()) + { + return Array.IndexOf(typePair.TargetType.GetAllInterfaces(), otherTypePair.TargetType) != -1; + } + + return false; + } + } +} \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/MappingCallbackFactory.cs b/AgileMapper/ObjectPopulation/MappingCallbackFactory.cs index bc879e596..018a13d1d 100644 --- a/AgileMapper/ObjectPopulation/MappingCallbackFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingCallbackFactory.cs @@ -25,7 +25,7 @@ public virtual bool AppliesTo(CallbackPosition callbackPosition, IBasicMapperDat => (CallbackPosition == callbackPosition) && base.AppliesTo(mapperData); protected override bool MemberPathMatches(IBasicMapperData mapperData) - => ConfigInfo.HasCompatibleTypes(mapperData); + => mapperData.HasCompatibleTypes(ConfigInfo); public Expression Create(IMemberMapperData mapperData) { diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index d761dbefe..2cd1f74fa 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -51,8 +51,8 @@ private ObjectMapperData( var mappingDataType = typeof(IMappingData<,>).MakeGenericType(SourceType, TargetType); SourceObject = GetMappingDataProperty(mappingDataType, "Source"); - TargetObject = Expression.Property(MappingDataObject, "Target"); - CreatedObject = Expression.Property(MappingDataObject, "CreatedObject"); + TargetObject = GetMappingDataProperty("Target"); + CreatedObject = GetMappingDataProperty("CreatedObject"); var isPartOfDerivedTypeMapping = declaredTypeMapperData != null; @@ -64,7 +64,7 @@ private ObjectMapperData( else { EnumerableIndex = GetMappingDataProperty(mappingDataType, "EnumerableIndex"); - ParentObject = Expression.Property(MappingDataObject, "Parent"); + ParentObject = GetMappingDataProperty("Parent"); } ExpressionInfoFinder = new ExpressionInfoFinder(MappingDataObject); @@ -148,6 +148,9 @@ private Expression GetMappingDataProperty(Type mappingDataType, string propertyN return Expression.Property(MappingDataObject, property); } + private Expression GetMappingDataProperty(string propertyName) + => Expression.Property(MappingDataObject, propertyName); + private static MethodInfo GetMapMethod(Type mappingDataType, int numberOfArguments) { return mappingDataType @@ -324,6 +327,13 @@ public static ObjectMapperData For(IObjectMappingData mappingD public MapperDataContext Context { get; } + public override bool HasCompatibleTypes(ITypePair typePair) + { + return typePair.HasCompatibleTypes( + this, + () => SourceMember.HasCompatibleType(typePair.SourceType)); + } + public IQualifiedMember GetSourceMemberFor(string targetMemberRegistrationName, int dataSourceIndex) => _dataSourcesByTargetMemberName[targetMemberRegistrationName][dataSourceIndex].SourceMember; From abb306bf3eb0405f286168ede4a2590246476dd3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 8 Dec 2017 22:26:45 +0000 Subject: [PATCH 14/74] Organising Dictionary classes --- .../Configuration/CustomDataSourceTargetMemberSpecifier.cs | 1 + AgileMapper/Configuration/JoiningNameFactory.cs | 1 + .../DataSources/ConfiguredDictionaryDataSourceFactory.cs | 1 + AgileMapper/DataSources/DictionaryDataSourceFactory.cs | 1 + AgileMapper/DataSources/DictionaryEntryVariablePair.cs | 1 + AgileMapper/DataSources/SourceMemberDataSource.cs | 1 - .../Members/{ => Dictionaries}/DictionaryEntrySourceMember.cs | 2 +- .../DictionaryMemberMapperDataExtensions.cs | 2 +- .../Members/{ => Dictionaries}/DictionarySourceMember.cs | 2 +- .../Members/{ => Dictionaries}/DictionaryTargetMember.cs | 2 +- AgileMapper/Members/Member.cs | 1 + AgileMapper/Members/MemberMapperDataExtensions.cs | 1 + AgileMapper/Members/QualifiedMemberFactory.cs | 1 + .../ObjectPopulation/DictionaryMappingExpressionFactory.cs | 1 + .../Enumerables/Dictionaries/DictionaryPopulationBuilder.cs | 1 + .../Dictionaries/DictionaryToDictionaryPopulationLoopData.cs | 2 +- .../{ => Dictionaries}/SourceElementsDictionaryAdapter.cs | 4 ++-- .../SourceElementsDictionaryPopulationLoopData.cs | 4 ++-- .../{ => Dictionaries}/SourceInstanceDictionaryAdapter.cs | 4 ++-- .../{ => Dictionaries}/SourceObjectDictionaryAdapter.cs | 4 ++-- .../SourceObjectDictionaryPopulationLoopData.cs | 2 +- .../Enumerables/SourceEnumerableAdapterFactory.cs | 1 + .../ExistingOrDefaultValueDataSourceFactory.cs | 1 + AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs | 1 - 24 files changed, 26 insertions(+), 16 deletions(-) rename AgileMapper/Members/{ => Dictionaries}/DictionaryEntrySourceMember.cs (98%) rename AgileMapper/Members/{ => Dictionaries}/DictionaryMemberMapperDataExtensions.cs (99%) rename AgileMapper/Members/{ => Dictionaries}/DictionarySourceMember.cs (98%) rename AgileMapper/Members/{ => Dictionaries}/DictionaryTargetMember.cs (99%) rename AgileMapper/ObjectPopulation/Enumerables/{ => Dictionaries}/SourceElementsDictionaryAdapter.cs (97%) rename AgileMapper/ObjectPopulation/Enumerables/{ => Dictionaries}/SourceElementsDictionaryPopulationLoopData.cs (98%) rename AgileMapper/ObjectPopulation/Enumerables/{ => Dictionaries}/SourceInstanceDictionaryAdapter.cs (95%) rename AgileMapper/ObjectPopulation/Enumerables/{ => Dictionaries}/SourceObjectDictionaryAdapter.cs (98%) rename AgileMapper/ObjectPopulation/Enumerables/{ => Dictionaries}/SourceObjectDictionaryPopulationLoopData.cs (97%) diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index dfe3d1df0..2e2f7ae41 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -9,6 +9,7 @@ using DataSources; using Extensions; using Members; + using Members.Dictionaries; using NetStandardPolyfills; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/JoiningNameFactory.cs index b3165185a..358d6b0b1 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/JoiningNameFactory.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using Extensions; using Members; + using Members.Dictionaries; using ReadableExpressions.Extensions; internal class JoiningNameFactory : UserConfiguredItemBase diff --git a/AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs b/AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs index 1a09dc0d5..043de073c 100644 --- a/AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/ConfiguredDictionaryDataSourceFactory.cs @@ -2,6 +2,7 @@ { using Configuration; using Members; + using Members.Dictionaries; internal class ConfiguredDictionaryDataSourceFactory : ConfiguredDataSourceFactory { diff --git a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs index c4acacced..30637377e 100644 --- a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Extensions; using Members; + using Members.Dictionaries; internal class DictionaryDataSourceFactory : IMaptimeDataSourceFactory { diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index d8e09bc83..e3f30323a 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -7,6 +7,7 @@ namespace AgileObjects.AgileMapper.DataSources using System.Reflection; using Extensions; using Members; + using Members.Dictionaries; using NetStandardPolyfills; internal class DictionaryEntryVariablePair diff --git a/AgileMapper/DataSources/SourceMemberDataSource.cs b/AgileMapper/DataSources/SourceMemberDataSource.cs index c1bef5433..aa6191fea 100644 --- a/AgileMapper/DataSources/SourceMemberDataSource.cs +++ b/AgileMapper/DataSources/SourceMemberDataSource.cs @@ -3,7 +3,6 @@ using System.Linq.Expressions; using System.Collections.Generic; #if NET_STANDARD - using System.Reflection; #endif using Extensions; using Members; diff --git a/AgileMapper/Members/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs similarity index 98% rename from AgileMapper/Members/DictionaryEntrySourceMember.cs rename to AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index 716d6af48..93d77340b 100644 --- a/AgileMapper/Members/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Members +namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Linq.Expressions; diff --git a/AgileMapper/Members/DictionaryMemberMapperDataExtensions.cs b/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs similarity index 99% rename from AgileMapper/Members/DictionaryMemberMapperDataExtensions.cs rename to AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs index c0ee05936..830ae57aa 100644 --- a/AgileMapper/Members/DictionaryMemberMapperDataExtensions.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Members +namespace AgileObjects.AgileMapper.Members.Dictionaries { using System.Collections.Generic; using System.Linq; diff --git a/AgileMapper/Members/DictionarySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs similarity index 98% rename from AgileMapper/Members/DictionarySourceMember.cs rename to AgileMapper/Members/Dictionaries/DictionarySourceMember.cs index e447992f5..cd427ff90 100644 --- a/AgileMapper/Members/DictionarySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Members +namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Linq.Expressions; diff --git a/AgileMapper/Members/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs similarity index 99% rename from AgileMapper/Members/DictionaryTargetMember.cs rename to AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index de20588f5..5b66f587f 100644 --- a/AgileMapper/Members/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Members +namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Collections.Generic; diff --git a/AgileMapper/Members/Member.cs b/AgileMapper/Members/Member.cs index 21fa9d888..e5a8a366b 100644 --- a/AgileMapper/Members/Member.cs +++ b/AgileMapper/Members/Member.cs @@ -6,6 +6,7 @@ namespace AgileObjects.AgileMapper.Members #endif using System.Linq.Expressions; using System.Reflection; + using Dictionaries; using Extensions; using NetStandardPolyfills; using ObjectPopulation; diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 101570238..5127ed114 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -7,6 +7,7 @@ namespace AgileObjects.AgileMapper.Members using System.Linq.Expressions; using System.Reflection; using DataSources; + using Dictionaries; using Extensions; using NetStandardPolyfills; using ObjectPopulation; diff --git a/AgileMapper/Members/QualifiedMemberFactory.cs b/AgileMapper/Members/QualifiedMemberFactory.cs index 2a21ab84f..fa63cbeb2 100644 --- a/AgileMapper/Members/QualifiedMemberFactory.cs +++ b/AgileMapper/Members/QualifiedMemberFactory.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Members { using Caching; + using Dictionaries; using Extensions; internal class QualifiedMemberFactory diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 8cd084403..a15adfecd 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -10,6 +10,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using Enumerables.Dictionaries; using Extensions; using Members; + using Members.Dictionaries; using NetStandardPolyfills; using ReadableExpressions; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 96661cb50..3f19836a3 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -6,6 +6,7 @@ using DataSources; using Extensions; using Members; + using Members.Dictionaries; using Members.Population; internal class DictionaryPopulationBuilder diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryToDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryToDictionaryPopulationLoopData.cs index 3e0a71c4c..e33314747 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryToDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryToDictionaryPopulationLoopData.cs @@ -2,7 +2,7 @@ { using System.Collections.Generic; using System.Linq.Expressions; - using Members; + using Members.Dictionaries; internal class DictionaryToDictionaryPopulationLoopData : EnumerableSourcePopulationLoopData { diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceElementsDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs similarity index 97% rename from AgileMapper/ObjectPopulation/Enumerables/SourceElementsDictionaryAdapter.cs rename to AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs index f9766a31a..f709a234c 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceElementsDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs @@ -1,11 +1,11 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables +namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using DataSources; using Extensions; - using Members; + using Members.Dictionaries; using NetStandardPolyfills; internal class SourceElementsDictionaryAdapter : SourceEnumerableAdapterBase, ISourceEnumerableAdapter diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceElementsDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs similarity index 98% rename from AgileMapper/ObjectPopulation/Enumerables/SourceElementsDictionaryPopulationLoopData.cs rename to AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs index 5d20f769b..a5eb764ea 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceElementsDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs @@ -1,11 +1,11 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables +namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System; using System.Collections.Generic; using System.Linq.Expressions; using DataSources; using Extensions; - using Members; + using Members.Dictionaries; using NetStandardPolyfills; internal class SourceElementsDictionaryPopulationLoopData : IPopulationLoopData diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceInstanceDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs similarity index 95% rename from AgileMapper/ObjectPopulation/Enumerables/SourceInstanceDictionaryAdapter.cs rename to AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs index 5a3fb03d3..b9442c10f 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceInstanceDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs @@ -1,9 +1,9 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables +namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System.Linq.Expressions; using DataSources; using Extensions; - using Members; + using Members.Dictionaries; internal class SourceInstanceDictionaryAdapter : SourceEnumerableAdapterBase, ISourceEnumerableAdapter { diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs similarity index 98% rename from AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryAdapter.cs rename to AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs index 4814f3e50..8e55cf60b 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs @@ -1,10 +1,10 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables +namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System.Collections; using System.Linq; using System.Linq.Expressions; using Extensions; - using Members; + using Members.Dictionaries; using NetStandardPolyfills; internal class SourceObjectDictionaryAdapter : SourceEnumerableAdapterBase, ISourceEnumerableAdapter diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs similarity index 97% rename from AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs rename to AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs index f3b5ee40b..aee3bef61 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceObjectDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables +namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System.Linq.Expressions; using DataSources; diff --git a/AgileMapper/ObjectPopulation/Enumerables/SourceEnumerableAdapterFactory.cs b/AgileMapper/ObjectPopulation/Enumerables/SourceEnumerableAdapterFactory.cs index 41079a74a..558adf1f5 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/SourceEnumerableAdapterFactory.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/SourceEnumerableAdapterFactory.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables { + using Dictionaries; using Members; internal static class SourceEnumerableAdapterFactory diff --git a/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs b/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs index b3a675ec7..9e6166654 100644 --- a/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs +++ b/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs @@ -4,6 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using DataSources; using Extensions; using Members; + using Members.Dictionaries; internal class ExistingOrDefaultValueDataSourceFactory : IDataSourceFactory { diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 42796fee5..222c42b88 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -6,7 +6,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Linq.Expressions; using Extensions; #if NET_STANDARD - using System.Reflection; #endif using Members; using NetStandardPolyfills; From 70b3f140799c7b49b796c5102470af17ab40914d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 9 Dec 2017 20:16:21 +0000 Subject: [PATCH 15/74] Test coverage for mapping from flattened dynamic members to nested complex type enumerable members --- .../WhenCreatingRootDictionaryMembers.cs | 1 + ...nMappingFromDynamicsToNewEnumerableMembers.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/AgileMapper.UnitTests/Dictionaries/WhenCreatingRootDictionaryMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenCreatingRootDictionaryMembers.cs index c2034d3d1..e23137b83 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenCreatingRootDictionaryMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenCreatingRootDictionaryMembers.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using AgileMapper.Members; + using AgileMapper.Members.Dictionaries; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs index aabcdb643..c7322c573 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerableMembers.cs @@ -52,5 +52,21 @@ public void ShouldMapToAComplexTypeEnumerableMemberFromComplexTypeEntries() result.Value.ShouldBe(pf => pf.Value, 9, 8, 7); } + + [Fact] + public void ShouldMapToAComplexTypeEnumerableMemberFromFlattenedEntries() + { + dynamic source = new ExpandoObject(); + + source.Value_0_SetValue = '4'; + source.Value_1_SetValue = '5'; + source.Value_2_SetValue = '6'; + + var result = (PublicField>>)Mapper + .Map(source) + .ToANew>>>(); + + result.Value.ShouldBe(pf => pf.Value, 4, 5, 6); + } } } From 0ade1344f9987b5bff39e2cde3706d9ddb8b0237 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 9 Dec 2017 20:30:30 +0000 Subject: [PATCH 16/74] Test coverage for mapping from dynamics to new root enumerables --- .../AgileMapper.UnitTests.csproj | 1 + ...WhenMappingFromDynamicsToNewEnumerables.cs | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerables.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 4e0a18fca..1aaa039d3 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -105,6 +105,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerables.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerables.cs new file mode 100644 index 000000000..7af48ff15 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewEnumerables.cs @@ -0,0 +1,64 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Dynamic; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsToNewEnumerables + { + [Fact] + public void ShouldMapToASimpleTypeArray() + { + dynamic source = new ExpandoObject(); + + source._0_ = 'a'; + source._1_ = 'b'; + source._2_ = 'c'; + + var result = (string[])Mapper.Map(source).ToANew(); + + result.ShouldBe("a", "b", "c"); + } + + [Fact] + public void ShouldMapToAComplexTypeCollectionFromComplexTypeEntries() + { + dynamic source = new ExpandoObject(); + + source._0_ = new ProductDto { ProductId = "prod-one" }; + source._1_ = new ProductDto { ProductId = "prod-two" }; + source._2_ = new ProductDto { ProductId = "prod-three" }; + + var result = (Collection)Mapper.Map(source).ToANew>(); + + result.ShouldBe(p => p.ProductId, "prod-one", "prod-two", "prod-three"); + } + + [Fact] + public void ShouldMapToAComplexTypeListFromFlattenedEntries() + { + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + var guid3 = Guid.NewGuid(); + + dynamic source = new ExpandoObject(); + + source._0_Value1 = guid1; + source._0_Value2 = 123; + source._1_Value1 = guid2; + source._1_Value2 = 456; + source._2_Value1 = guid3; + source._2_Value2 = 789; + + var result = (IList>)Mapper + .Map(source) + .ToANew>>(); + + result.ShouldBe(p => p.Value1, guid1.ToString(), guid2.ToString(), guid3.ToString()); + result.ShouldBe(p => p.Value2, "123", "456", "789"); + } + } +} From 949279e9af8efc369b8ed553cdba0e6d094752d5 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 9 Dec 2017 22:38:07 +0000 Subject: [PATCH 17/74] Short-circuiting mappings from dictionaries to recursive members / Including dictionary implementation type name in MappingExceptions instead of interface name / Support for id-aware mapping from: - IDictionary<,>-implementation identifiable complex type collection entries ...over + onto: - Identifiable complex type collections --- .../AgileMapper.UnitTests.csproj | 1 + ...appingFromDynamicsOverEnumerableMembers.cs | 60 ++++++++++++++++ AgileMapper/Members/ConfiguredSourceMember.cs | 2 + .../DictionaryEntrySourceMember.cs | 2 + .../Dictionaries/DictionarySourceMember.cs | 2 + AgileMapper/Members/IQualifiedMember.cs | 2 + AgileMapper/Members/MemberExtensions.cs | 2 +- .../Members/MemberMapperDataExtensions.cs | 6 +- AgileMapper/Members/QualifiedMember.cs | 2 + .../DefaultSourceEnumerableAdapter.cs | 4 +- .../SourceElementsDictionaryAdapter.cs | 2 + .../SourceInstanceDictionaryAdapter.cs | 2 + .../SourceObjectDictionaryAdapter.cs | 70 ++++++++++--------- ...ourceObjectDictionaryPopulationLoopData.cs | 11 ++- .../EnumerablePopulationBuilder.cs | 4 ++ .../Enumerables/ISourceEnumerableAdapter.cs | 2 + .../ObjectPopulation/MappingFactory.cs | 7 +- 17 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 1aaa039d3..b9b50a7d4 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -102,6 +102,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs new file mode 100644 index 000000000..1bbfe1cef --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs @@ -0,0 +1,60 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Collections.Generic; + using System.Dynamic; + using System.Linq; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsOverEnumerableMembers + { + [Fact] + public void ShouldOverwriteASimpleTypeCollection() + { + dynamic source = new ExpandoObject(); + + source.Value = new List { 10, 20, 30 }; + + var target = new PublicField> + { + Value = new List { "40" } + }; + + Mapper.Map(source).Over(target); + + target.Value.ShouldBe("10", "20", "30"); + } + + [Fact] + public void ShouldOverwriteAnIndentifableComplexTypeCollection() + { + dynamic source = new ExpandoObject(); + + source.Value = new List + { + new ProductDto { ProductId = "prod-1", Price = 12.99m }, + new ProductDto { ProductId = "prod-2", Price = 15.00m }, + }; + + var target = new PublicField> + { + Value = new List + { + new Product { ProductId = "prod-2", Price = 20.00 }, + new Product { ProductId = "prod-3", Price = 1.99 }, + } + }; + + var preMappingProd2 = target.Value.First(); + + Mapper.Map(source).Over(target); + + target.Value.Count.ShouldBe(2); + + target.Value.First().ShouldBeSameAs(preMappingProd2); + target.Value.ShouldBe(p => p.ProductId, "prod-2", "prod-1"); + target.Value.ShouldBe(p => p.Price, 15.00, 12.99); + } + } +} diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index f297766a9..8c2ff2841 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -57,6 +57,8 @@ private ConfiguredSourceMember( public Type Type { get; } + public string GetFriendlyTypeName() => Type.GetFriendlyName(); + public bool IsEnumerable { get; } public string Name { get; } diff --git a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index 93d77340b..c03530562 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -52,6 +52,8 @@ private DictionaryEntrySourceMember( public Type Type { get; } + public string GetFriendlyTypeName() => Type.GetFriendlyName(); + public Type ElementType => _childMembers.First().ElementType; public bool IsEnumerable { get; } diff --git a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs index cd427ff90..a298cc301 100644 --- a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs @@ -58,6 +58,8 @@ private DictionarySourceMember( public Type Type { get; } + public string GetFriendlyTypeName() => _wrappedSourceMember.GetFriendlyTypeName(); + public Type KeyType { get; } public Type ValueType { get; } diff --git a/AgileMapper/Members/IQualifiedMember.cs b/AgileMapper/Members/IQualifiedMember.cs index cc6723ab6..8eb7290ea 100644 --- a/AgileMapper/Members/IQualifiedMember.cs +++ b/AgileMapper/Members/IQualifiedMember.cs @@ -7,6 +7,8 @@ internal interface IQualifiedMember { Type Type { get; } + string GetFriendlyTypeName(); + bool IsEnumerable { get; } string Name { get; } diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index ab4b8f92a..77c709f41 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -25,7 +25,7 @@ public static string GetFriendlyTargetPath(this IQualifiedMember targetMember, I private static string GetMemberPath(IQualifiedMember member, IQualifiedMember rootMember) { - var rootTypeName = rootMember.Type.GetFriendlyName(); + var rootTypeName = rootMember.GetFriendlyTypeName(); var memberPath = member.GetPath(); if (memberPath == rootMember.Name) diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 5127ed114..89aafdb2e 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -81,10 +81,8 @@ public static ExpressionInfoFinder.ExpressionInfo GetExpressionInfoFor( return mapperData.ExpressionInfoFinder.FindIn(value, targetCanBeNull); } - public static bool SourceIsNotFlatObject(this IMemberMapperData mapperData) - { - return !mapperData.SourceType.IsDictionary(); - } + public static bool SourceIsFlatObject(this IMemberMapperData mapperData) + => mapperData.SourceType.IsDictionary(); public static bool SourceMemberIsStringKeyedDictionary( this IMemberMapperData mapperData, diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index a9eb50e89..cecd5c33c 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -142,6 +142,8 @@ public static QualifiedMember From(Member[] memberChain, MapperContext mapperCon public Type Type => LeafMember?.Type; + public string GetFriendlyTypeName() => Type.GetFriendlyName(); + public Type ElementType => LeafMember?.ElementType; public virtual Type GetElementType(Type sourceElementType) => ElementType; diff --git a/AgileMapper/ObjectPopulation/Enumerables/DefaultSourceEnumerableAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/DefaultSourceEnumerableAdapter.cs index 3cfd7192a..5060a5087 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/DefaultSourceEnumerableAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/DefaultSourceEnumerableAdapter.cs @@ -17,13 +17,15 @@ public Expression GetSourceCountAccess() return Expression.ArrayLength(SourceValue); } - var countPropertyInfo = + var countPropertyInfo = SourceValue.Type.GetPublicInstanceProperty("Count") ?? SourceTypeHelper.CollectionInterfaceType.GetPublicInstanceProperty("Count"); return Expression.Property(SourceValue, countPropertyInfo); } + public Expression GetMappingShortCircuitOrNull() => null; + public IPopulationLoopData GetPopulationLoopData() { if (SourceTypeHelper.HasListInterface) diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs index f709a234c..dfe25935d 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs @@ -84,6 +84,8 @@ public Expression GetSourceCountAccess() public override bool UseReadOnlyTargetWrapper => base.UseReadOnlyTargetWrapper && Builder.Context.ElementTypesAreSimple; + public Expression GetMappingShortCircuitOrNull() => null; + public IPopulationLoopData GetPopulationLoopData() { if (Builder.ElementTypesAreSimple) diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs index b9442c10f..38cb83f56 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs @@ -55,6 +55,8 @@ private Expression GetNullValueEntryShortCircuit(Expression shortCircuitReturn) public Expression GetSourceCountAccess() => _defaultAdapter.GetSourceCountAccess(); + public Expression GetMappingShortCircuitOrNull() => null; + public IPopulationLoopData GetPopulationLoopData() => _defaultAdapter.GetPopulationLoopData(); } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs index 8e55cf60b..91c40f4f1 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs @@ -35,27 +35,17 @@ public override Expression GetSourceValues() var enumerableIsNull = untypedEnumerableVariable.GetIsDefaultComparison(); var ifNotEnumerableReturnEmpty = Expression.IfThen(enumerableIsNull, returnEmpty); - var typedEnumerableAssignment = - GetTypedEnumerableAssignment(untypedEnumerableVariable, out var typedEnumerableVariable); - - var enumerableIsTyped = typedEnumerableVariable.GetIsNotDefaultComparison(); - var returnTypedEnumerable = Expression.Return(returnLabel, typedEnumerableVariable); - var ifTypedEnumerableReturn = Expression.IfThen(enumerableIsTyped, returnTypedEnumerable); - var returnProjectionResult = GetEntryValueProjection(untypedEnumerableVariable, returnLabel); var sourceValueBlock = Expression.Block( new[] { _instanceDictionaryAdapter.DictionaryVariables.Key, - untypedEnumerableVariable, - typedEnumerableVariable + untypedEnumerableVariable }, ifKeyNotFoundShortCircuit, enumerableAssignment, ifNotEnumerableReturnEmpty, - typedEnumerableAssignment, - ifTypedEnumerableReturn, returnProjectionResult); return sourceValueBlock; @@ -71,30 +61,26 @@ private Expression GetUntypedEnumerableAssignment(out ParameterExpression enumer return enumerableAssignment; } - private Expression GetTypedEnumerableAssignment( - Expression untypedEnumerableVariable, - out ParameterExpression typedEnumerableVariable) - { - var targetEnumerableType = _emptyTarget.Type; - var enumerableAsTyped = Expression.TypeAs(untypedEnumerableVariable, targetEnumerableType); - var typedEnumerableVariableName = targetEnumerableType.GetVariableNameInCamelCase(); - typedEnumerableVariable = Expression.Variable(targetEnumerableType, typedEnumerableVariableName); - var typedEnumerableAssignment = typedEnumerableVariable.AssignTo(enumerableAsTyped); - - return typedEnumerableAssignment; - } - private Expression GetEntryValueProjection( Expression untypedEnumerableVariable, LabelTarget returnLabel) { - var linqCastMethod = typeof(Enumerable).GetPublicStaticMethod("Cast"); - var typedCastMethod = linqCastMethod.MakeGenericMethod(typeof(object)); - var linqCastCall = Expression.Call(null, typedCastMethod, untypedEnumerableVariable); - - var sourceItemsProjection = Builder.Context.ElementTypesAreSimple - ? Builder.GetSourceItemsProjection(linqCastCall, GetSourceElementConversion) - : Builder.GetSourceItemsProjection(linqCastCall, GetSourceElementMapping); + Expression sourceItemsProjection; + + if (Builder.Context.ElementTypesAreSimple) + { + var linqCastMethod = typeof(Enumerable).GetPublicStaticMethod("Cast"); + var typedCastMethod = linqCastMethod.MakeGenericMethod(typeof(object)); + var linqCastCall = Expression.Call(null, typedCastMethod, untypedEnumerableVariable); + sourceItemsProjection = Builder.GetSourceItemsProjection(linqCastCall, GetSourceElementConversion); + } + else + { + sourceItemsProjection = Builder.MapperData.Parent.GetMapCall( + untypedEnumerableVariable, + Builder.MapperData.TargetMember, + dataSourceIndex: 0); + } var returnProjectionResult = Expression.Label(returnLabel, sourceItemsProjection); @@ -104,14 +90,30 @@ private Expression GetEntryValueProjection( private Expression GetSourceElementConversion(Expression sourceParameter) => Builder.GetSimpleElementConversion(sourceParameter); - private Expression GetSourceElementMapping(Expression sourceParameter, Expression counter) - => Builder.MapperData.GetMapCall(sourceParameter); - public Expression GetSourceCountAccess() => _instanceDictionaryAdapter.GetSourceCountAccess(); public override bool UseReadOnlyTargetWrapper => base.UseReadOnlyTargetWrapper && Builder.Context.ElementTypesAreSimple; + public Expression GetMappingShortCircuitOrNull() + { + if (Builder.ElementTypesAreSimple) + { + return null; + } + + var sourceEnumerableFoundTest = SourceObjectDictionaryPopulationLoopData + .GetSourceEnumerableFoundTest(_emptyTarget, Builder); + + var projectionAsTargetType = Expression.TypeAs(Builder.SourceValue, Builder.MapperData.TargetType); + var convertedProjection = TargetTypeHelper.GetEnumerableConversion(Builder.SourceValue); + var projectionResult = Expression.Coalesce(projectionAsTargetType, convertedProjection); + var returnConvertedProjection = Expression.Return(Builder.MapperData.ReturnLabelTarget, projectionResult); + var ifProjectedReturn = Expression.IfThen(sourceEnumerableFoundTest, returnConvertedProjection); + + return ifProjectedReturn; + } + public IPopulationLoopData GetPopulationLoopData() { return new SourceObjectDictionaryPopulationLoopData( diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs index aee3bef61..c783e6399 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs @@ -22,7 +22,7 @@ public SourceObjectDictionaryPopulationLoopData( _enumerableLoopData = new EnumerableSourcePopulationLoopData(builder); _elementsDictionaryLoopData = new SourceElementsDictionaryPopulationLoopData(dictionaryVariables, builder); - _sourceEnumerableFound = Expression.Variable(typeof(bool), "sourceEnumerableFound"); + _sourceEnumerableFound = Parameters.Create("sourceEnumerableFound"); ContinueLoopTarget = Expression.Label(typeof(void), "Continue"); LoopExitCheck = GetCompositeLoopExitCheck(); @@ -58,7 +58,7 @@ public Expression GetElementMapping(IObjectMappingData enumerableMappingData) public Expression Adapt(LoopExpression loop) { - var sourceEnumerableFoundTest = Expression.NotEqual(_builder.SourceValue, _emptyTarget); + var sourceEnumerableFoundTest = GetSourceEnumerableFoundTest(_emptyTarget, _builder); var assignSourceEnumerableFound = (Expression)_sourceEnumerableFound.AssignTo(sourceEnumerableFoundTest); var adaptedLoop = _elementsDictionaryLoopData.Adapt(loop); @@ -73,6 +73,13 @@ public Expression Adapt(LoopExpression loop) new[] { assignSourceEnumerableFound }.Append(enumerableLoopBlock.Expressions)); } + public static BinaryExpression GetSourceEnumerableFoundTest( + Expression emptyTarget, + EnumerablePopulationBuilder builder) + { + return Expression.NotEqual(builder.SourceValue, emptyTarget); + } + private Expression GetEnumeratorIfNecessary(Expression getEnumeratorCall) { return Expression.Condition( diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index e9150ec14..d2caf490e 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -235,6 +235,10 @@ public void AssignSourceVariableFromSourceObject() } AssignSourceVariableFrom(SourceValue); + + var shortCircuit = _sourceAdapter.GetMappingShortCircuitOrNull(); + + _populationExpressions.AddUnlessNullOrEmpty(shortCircuit); } public void AssignSourceVariableFrom(Func sourceItemsSelection) diff --git a/AgileMapper/ObjectPopulation/Enumerables/ISourceEnumerableAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/ISourceEnumerableAdapter.cs index 23347ce7d..3c0ac8277 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/ISourceEnumerableAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/ISourceEnumerableAdapter.cs @@ -10,6 +10,8 @@ internal interface ISourceEnumerableAdapter bool UseReadOnlyTargetWrapper { get; } + Expression GetMappingShortCircuitOrNull(); + IPopulationLoopData GetPopulationLoopData(); } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index de4839b32..03e8b9f55 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -54,7 +54,12 @@ public static Expression GetChildMapping( if (childMapperData.TargetMemberEverRecurses()) { - childMapperData.CacheMappedObjects = childMapperData.SourceIsNotFlatObject(); + if (childMapperData.SourceIsFlatObject()) + { + return Constants.EmptyExpression; + } + + childMapperData.CacheMappedObjects = true; var mapRecursionCall = GetMapRecursionCallFor( childMappingData, From f049f0493b75fa05e8b422ea93d3d2fde22c2e37 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 9 Dec 2017 22:42:07 +0000 Subject: [PATCH 18/74] Test coverage for mapping from a dynamic null complex type collection member over a complex type collection member --- ...nMappingFromDynamicsOverEnumerableMembers.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs index 1bbfe1cef..91ae51d85 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs @@ -26,6 +26,23 @@ public void ShouldOverwriteASimpleTypeCollection() target.Value.ShouldBe("10", "20", "30"); } + [Fact] + public void ShouldOverwriteAComplexTypeCollectionToEmpty() + { + dynamic source = new ExpandoObject(); + + source.Value = default(List); + + var target = new PublicField> + { + Value = new List { new ProductDto { ProductId = "p-1" } } + }; + + Mapper.Map(source).Over(target); + + target.Value.ShouldBeEmpty(); + } + [Fact] public void ShouldOverwriteAnIndentifableComplexTypeCollection() { From ef37bfb2913c8e89bbc9a8b454607cc886c55c4d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 10:18:42 +0000 Subject: [PATCH 19/74] Support for mapping from object-valued IDictionary<,> IDictionary<,> entries to nested complex type members --- ...ppingFromDynamicsToNewComplexTypeMembers.cs | 18 ++++++++++++++++++ .../DictionaryEntrySourceMember.cs | 9 ++++++++- .../Dictionaries/DictionarySourceMember.cs | 4 +++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs index 02060a664..3c410a19e 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypeMembers.cs @@ -41,5 +41,23 @@ public void ShouldMapFlattenedPropertiesToNestedSimpleTypeMembers() result.Address.Line1.ShouldBe("Dynamic Line 1"); result.Address.Line2.ShouldBe("Dynamic Line 2"); } + + [Fact] + public void ShouldMapANestedDynamicToANestedComplexTypeMember() + { + dynamic source = new ExpandoObject(); + + source.Name = "Captain Dynamic"; + source.Address = new ExpandoObject(); + source.Address.Line1 = "Dynamic House"; + source.Address.Line2 = "Dynamic Street"; + + var result = (Customer)Mapper.Map(source).ToANew(); + + result.Name.ShouldBe("Captain Dynamic"); + result.Address.ShouldNotBeNull(); + result.Address.Line1.ShouldBe("Dynamic House"); + result.Address.Line2.ShouldBe("Dynamic Street"); + } } } \ No newline at end of file diff --git a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index c03530562..7d678b9ae 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -92,12 +92,19 @@ public IQualifiedMember WithType(Type runtimeType) Array.Copy(_childMembers, 0, childMembers, 0, childMembers.Length - 1); childMembers[childMembers.Length - 1] = _childMembers.Last().WithType(runtimeType); - return new DictionaryEntrySourceMember( + var dictionaryEntry = new DictionaryEntrySourceMember( runtimeType, _pathFactory, _matchedTargetMember, Parent, childMembers); + + if (runtimeType.IsDictionary()) + { + return new DictionarySourceMember(dictionaryEntry, _matchedTargetMember); + } + + return dictionaryEntry; } public bool HasCompatibleType(Type type) => false; diff --git a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs index a298cc301..5383d610a 100644 --- a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs @@ -49,7 +49,9 @@ private DictionarySourceMember( ValueType = valueType; } - EntryMember = new DictionaryEntrySourceMember(ValueType, matchedTargetMember, this); + EntryMember = (wrappedSourceMember as DictionaryEntrySourceMember) ?? + new DictionaryEntrySourceMember(ValueType, matchedTargetMember, this); + HasObjectEntries = ValueType == typeof(object); CouldContainSourceInstance = From bf8282d87f88af490ac21c24365841b6ef3e3df7 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 10:26:27 +0000 Subject: [PATCH 20/74] Test coverage for namming ExpandoObject (not IDictionary<,>) in thrown MappingExceptions --- ...henMappingFromDynamicsToNewComplexTypes.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs index 78acadd7a..e0f13f00c 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsToNewComplexTypes.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { + using System; using System.Dynamic; using Shouldly; using TestClasses; @@ -11,6 +12,7 @@ public class WhenMappingFromDynamicsToNewComplexTypes public void ShouldMapToASimpleTypeMember() { dynamic source = new ExpandoObject(); + source.value = 123; var result = (PublicField)Mapper.Map(source).ToANew>(); @@ -22,6 +24,7 @@ public void ShouldMapToASimpleTypeMember() public void ShouldConvertASimpleTypeMemberValue() { dynamic source = new ExpandoObject(); + source.Value = "728"; var result = (PublicField)Mapper.Map(source).ToANew>(); @@ -32,12 +35,33 @@ public void ShouldConvertASimpleTypeMemberValue() [Fact] public void ShouldHandleANullASimpleTypeMemberValue() { - dynamic source = new ExpandoObject{}; + dynamic source = new ExpandoObject(); + source.Value = default(string); var result = (PublicSetMethod)Mapper.Map(source).ToANew>(); result.Value.ShouldBeNull(); } + + [Fact] + public void ShouldWrapAMappingException() + { + using (var mapper = Mapper.CreateNew()) + { + dynamic source = new ExpandoObject(); + + source.ValueLine1 = "1 Exception Road"; + + mapper.Before + .CreatingInstancesOf
() + .Call(ctx => throw new InvalidOperationException("I DON'T LIKE ADDRESSES")); + + var mappingEx = Should.Throw(() => + mapper.Map(source).ToANew>()); + + mappingEx.Message.ShouldContain(nameof(ExpandoObject)); + } + } } } From b96e451112967f5af567476b73cdd0e3632a009f Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 11:54:55 +0000 Subject: [PATCH 21/74] Support for mapping from nested dynamic members to nested complex type members / Using constants for root source and target member names --- .../AgileMapper.UnitTests.csproj | 1 + ...ppingFromDynamicsOverComplexTypeMembers.cs | 39 +++++++++++++++++++ .../Configuration/ParametersSwapper.cs | 5 ++- .../DictionaryEntrySourceMember.cs | 2 +- AgileMapper/Members/ExpressionInfoFinder.cs | 5 ++- AgileMapper/Members/Member.cs | 7 +++- .../Members/MemberMapperDataExtensions.cs | 7 ++-- AgileMapper/Members/QualifiedMember.cs | 13 ++++++- .../ObjectPopulation/ObjectMapperData.cs | 5 ++- .../ObjectPopulation/SourceAccessFinder.cs | 2 +- 10 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index b9b50a7d4..9f3d80062 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -102,6 +102,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs new file mode 100644 index 000000000..19d233bcd --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs @@ -0,0 +1,39 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsOverComplexTypeMembers + { + [Fact] + public void ShouldMapFromANestedDynamicToANestedComplexType() + { + dynamic sourceDynamic = new ExpandoObject(); + + sourceDynamic.Line1 = "Over there"; + + var source = new PublicTwoFields + { + Value1 = sourceDynamic, + Value2 = "Good Googley Moogley!" + }; + + var target = new PublicTwoFields + { + Value1 = new Address { Line1 = "Over here", Line2 = "Somewhere else" }, + Value2 = "Nothing" + }; + + var preMappingAddress = target.Value1; + + Mapper.Map(source).Over(target); + + target.Value1.ShouldBeSameAs(preMappingAddress); + target.Value1.Line1.ShouldBe("Over there"); + target.Value1.Line2.ShouldBe("Somewhere else"); + target.Value2.ShouldBe("Good Googley Moogley!"); + } + } +} diff --git a/AgileMapper/Configuration/ParametersSwapper.cs b/AgileMapper/Configuration/ParametersSwapper.cs index ec23ed863..d23c75262 100644 --- a/AgileMapper/Configuration/ParametersSwapper.cs +++ b/AgileMapper/Configuration/ParametersSwapper.cs @@ -8,6 +8,7 @@ namespace AgileObjects.AgileMapper.Configuration using Members; using NetStandardPolyfills; using ObjectPopulation; + using static Members.Member; internal class ParametersSwapper { @@ -89,8 +90,8 @@ private static Expression SwapForContextParameter(SwapArgs swapArgs) } var memberContextType = IsCallbackContext(contextTypes) ? contextType : contextType.GetAllInterfaces().First(); - var sourceProperty = memberContextType.GetPublicInstanceProperty("Source"); - var targetProperty = memberContextType.GetPublicInstanceProperty("Target"); + var sourceProperty = memberContextType.GetPublicInstanceProperty(RootSourceMemberName); + var targetProperty = memberContextType.GetPublicInstanceProperty(RootTargetMemberName); var indexProperty = memberContextType.GetPublicInstanceProperty("EnumerableIndex"); var parentProperty = memberContextType.GetPublicInstanceProperty("Parent"); diff --git a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index 7d678b9ae..f5312f0b3 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -45,7 +45,7 @@ private DictionaryEntrySourceMember( _pathFactory = pathFactory; _matchedTargetMember = matchedTargetMember; Parent = parent; - _childMembers = childMembers ?? new[] { Member.RootSource("Source", type) }; + _childMembers = childMembers ?? new[] { Member.RootSource(type) }; } public DictionarySourceMember Parent { get; } diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 6820c713a..8f7f112fc 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -5,6 +5,7 @@ namespace AgileObjects.AgileMapper.Members using System.Linq.Expressions; using Extensions; using ReadableExpressions.Extensions; + using static Member; internal class ExpressionInfoFinder { @@ -131,12 +132,12 @@ private bool IsNotRootObject(MemberExpression memberAccess) return false; } - if (memberAccess.Member.Name == "Source") + if (memberAccess.Member.Name == RootSourceMemberName) { return false; } - return _includeTargetNullChecking || (memberAccess.Member.Name != "Target"); + return _includeTargetNullChecking || (memberAccess.Member.Name != RootTargetMemberName); } private static bool IsNullableHasValueAccess(MemberExpression memberAccess) diff --git a/AgileMapper/Members/Member.cs b/AgileMapper/Members/Member.cs index e5a8a366b..d42c9a7a3 100644 --- a/AgileMapper/Members/Member.cs +++ b/AgileMapper/Members/Member.cs @@ -14,6 +14,9 @@ namespace AgileObjects.AgileMapper.Members internal class Member { + public const string RootSourceMemberName = "Source"; + public const string RootTargetMemberName = "Target"; + private readonly Func _accessFactory; private Member( @@ -71,13 +74,13 @@ private Member( public static Member RootSource() => SourceMemberCache.MemberInstance; - public static Member RootSource(Type type) => RootSource("Source", type); + public static Member RootSource(Type type) => RootSource(RootSourceMemberName, type); public static Member RootSource(string signature, Type type) => Root(signature, type); public static Member RootTarget() => TargetMemberCache.MemberInstance; - public static Member RootTarget(Type type) => Root("Target", type); + public static Member RootTarget(Type type) => Root(RootTargetMemberName, type); private static Member Root(string name, Type type) { diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 89aafdb2e..0e4d975e4 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -11,6 +11,7 @@ namespace AgileObjects.AgileMapper.Members using Extensions; using NetStandardPolyfills; using ObjectPopulation; + using static Member; internal static class MemberMapperDataExtensions { @@ -473,10 +474,10 @@ private static Expression GetAccess( return accessMethodFactory.Invoke(contextAccess, type); } - var propertyName = new[] { "Source", "Target" }[contextTypesIndex]; + var propertyName = new[] { RootSourceMemberName, RootTargetMemberName }[contextTypesIndex]; - var property = contextAccess.Type.GetPublicInstanceProperty(propertyName) - ?? typeof(IMappingData<,>) + var property = contextAccess.Type.GetPublicInstanceProperty(propertyName) ?? + typeof(IMappingData<,>) .MakeGenericType(contextTypes[0], contextTypes[1]) .GetPublicInstanceProperty(propertyName); diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index cecd5c33c..45d4d10c9 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -6,6 +6,7 @@ namespace AgileObjects.AgileMapper.Members using System.Linq; using System.Linq.Expressions; using Caching; + using Dictionaries; using Extensions; using NetStandardPolyfills; using ReadableExpressions.Extensions; @@ -244,7 +245,17 @@ public IQualifiedMember RelativeTo(IQualifiedMember otherMember) return new QualifiedMember(relativeMemberChain, this); } - IQualifiedMember IQualifiedMember.WithType(Type runtimeType) => WithType(runtimeType); + IQualifiedMember IQualifiedMember.WithType(Type runtimeType) + { + var typedMember = WithType(runtimeType); + + if (runtimeType.IsDictionary()) + { + return new DictionarySourceMember(typedMember, typedMember); + } + + return typedMember; + } public bool HasCompatibleType(Type type) => Type.IsAssignableTo(type); diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index 2cd1f74fa..8615c217f 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -11,6 +11,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using Members; using Members.Sources; using NetStandardPolyfills; + using static Members.Member; internal class ObjectMapperData : BasicMapperData, IMemberMapperData { @@ -50,8 +51,8 @@ private ObjectMapperData( SourceMember = sourceMember; var mappingDataType = typeof(IMappingData<,>).MakeGenericType(SourceType, TargetType); - SourceObject = GetMappingDataProperty(mappingDataType, "Source"); - TargetObject = GetMappingDataProperty("Target"); + SourceObject = GetMappingDataProperty(mappingDataType, RootSourceMemberName); + TargetObject = GetMappingDataProperty(RootTargetMemberName); CreatedObject = GetMappingDataProperty("CreatedObject"); var isPartOfDerivedTypeMapping = declaredTypeMapperData != null; diff --git a/AgileMapper/ObjectPopulation/SourceAccessFinder.cs b/AgileMapper/ObjectPopulation/SourceAccessFinder.cs index b3ebc6056..2a7454bcf 100644 --- a/AgileMapper/ObjectPopulation/SourceAccessFinder.cs +++ b/AgileMapper/ObjectPopulation/SourceAccessFinder.cs @@ -25,7 +25,7 @@ public static bool MultipleAccessesExist(IMemberMapperData mapperData, Expressio protected override Expression VisitMember(MemberExpression memberAccess) { if ((memberAccess.Expression == _mappingDataObject) && - (memberAccess.Member.Name == "Source")) + (memberAccess.Member.Name == Member.RootSourceMemberName)) { ++_numberOfAccesses; } From 2da3961ca1cf62f21e5fc8353e9fbed06c099075 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 14:09:13 +0000 Subject: [PATCH 22/74] Extending test coverage for mapping from nested dynamics to nested complex types --- ...ppingFromDynamicsOverComplexTypeMembers.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs index 19d233bcd..aa7021cf3 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypeMembers.cs @@ -35,5 +35,38 @@ public void ShouldMapFromANestedDynamicToANestedComplexType() target.Value1.Line2.ShouldBe("Somewhere else"); target.Value2.ShouldBe("Good Googley Moogley!"); } + + [Fact] + public void ShouldMapFlattenedMembersFromANestedDynamicToANestedComplexType() + { + dynamic sourceDynamic = new ExpandoObject(); + + sourceDynamic.Name = "Mystery :o"; + sourceDynamic.AddressLine1 = "Over here"; + sourceDynamic.AddressLine2 = "Over there"; + + var source = new PublicTwoFields + { + Value1 = sourceDynamic, + Value2 = "Blimey!!" + }; + + var target = new PublicTwoFields + { + Value1 = new MysteryCustomer { Name = "Mystery?!" }, + Value2 = "Nowt" + }; + + var preMappingCustomer = target.Value1; + + Mapper.Map(source).Over(target); + + target.Value1.ShouldBeSameAs(preMappingCustomer); + target.Value1.Name.ShouldBe("Mystery :o"); + target.Value1.Address.ShouldNotBeNull(); + target.Value1.Address.Line1.ShouldBe("Over here"); + target.Value1.Address.Line2.ShouldBe("Over there"); + target.Value2.ShouldBe("Blimey!!"); + } } } From ab2743726f352d31c0ead2b77ef4763275c91a6f Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 14:35:47 +0000 Subject: [PATCH 23/74] Support for mapping to ValueType-valued IDictionary<,>s / Using Shouldy ShouldContainKey() and ShouldNotContainKey() --- .../WhenConfiguringTargetDictionaryMapping.cs | 6 +- .../WhenMappingOverDictionaryMembers.cs | 4 +- .../WhenMappingToNewDictionaries.cs | 86 ++++++++++++++----- .../WhenMappingToNewDictionaryMembers.cs | 24 +++--- AgileMapper/Extensions/TypeExtensions.cs | 11 ++- AgileMapper/TypeConversion/ConverterSet.cs | 17 +++- 6 files changed, 104 insertions(+), 44 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index 298c99bfe..d94708ab9 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -123,8 +123,8 @@ public void ShouldApplyACustomSeparatorToASpecificSourceType() matchingResult["StreetAddress"].ShouldBe("Paddy's"); matchingResult["Address!Line2"].ShouldBe("Philly"); - matchingResult.ContainsKey("Address.Line1").ShouldBeFalse(); - matchingResult.ContainsKey("Address!Line1").ShouldBeFalse(); + matchingResult.ShouldNotContainKey("Address.Line1"); + matchingResult.ShouldNotContainKey("Address!Line1"); var nonMatchingSource = new Customer { Address = address }; var nonMatchingSourceResult = mapper.Map(nonMatchingSource).ToANew>(); @@ -241,7 +241,7 @@ public void ShouldAllowACustomTargetEntryKey() var noDiscountResult = mapper.Map(noDiscountSource).ToANew>(); noDiscountResult["CustomerName"].ShouldBe("Schumer"); - noDiscountResult.ContainsKey("Name").ShouldBeFalse(); + noDiscountResult.ShouldNotContainKey("Name"); var bigDiscountSource = new MysteryCustomerViewModel { Name = "Silverman", Discount = 0.6 }; var bigDiscountResult = mapper.Map(bigDiscountSource).ToANew>(); diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs index 6f2c251a9..32185d380 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs @@ -100,11 +100,11 @@ public void ShouldMapADictionaryMemberOverADictionaryMember() target.Value.ShouldBeSameAs(existingTarget); - target.Value.ContainsKey("One!").ShouldBeTrue(); + target.Value.ShouldContainKey("One!"); target.Value["One!"].ShouldBeOfType(); ((PersonViewModel)target.Value["One!"]).Name.ShouldBe("One!"); - target.Value.ContainsKey("Two!").ShouldBeTrue(); + target.Value.ShouldContainKey("Two!"); target.Value["Two!"].ShouldBeOfType(); ((PersonViewModel)target.Value["Two!"]).Name.ShouldBe("Two!"); } diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs index 28f48f790..2ce4a3650 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs @@ -34,7 +34,7 @@ public void ShouldMapAComplexTypeMemberToATypedDictionary() var source = new PublicProperty { Value = new Product { ProductId = "xxx" } }; var result = Mapper.Map(source).ToANew>(); - result.ContainsKey("Value").ShouldBeTrue(); + result.ShouldContainKey("Value"); result["Value"].ShouldBeOfType(); } @@ -58,7 +58,7 @@ public void ShouldMapNestedSimpleTypeMembersToATypedDictionary() var result = Mapper.Map(source).ToANew>(); result["Name"].ShouldBe("Eddie"); - result.ContainsKey("Address").ShouldBeFalse(); + result.ShouldNotContainKey("Address"); result["Address.Line1"].ShouldBe("Customer house"); result["Address.Line2"].ShouldBeNull(); } @@ -72,7 +72,7 @@ public void ShouldMapNestedSimpleTypeMembersToAnUntypedDictionary() }; var result = Mapper.Map(source).ToANew>(); - result.ContainsKey("Value").ShouldBeFalse(); + result.ShouldNotContainKey("Value"); result["Value.Value"].ShouldBe(12345); } @@ -112,7 +112,7 @@ public void ShouldMapAComplexTypeCollectionToAnUntypedDictionary() var result = Mapper.Map(source).ToANew>(); result.Count.ShouldBe(4); - result.ContainsKey("[0]").ShouldBeFalse(); + result.ShouldNotContainKey("[0]"); result["[0].Line1"].ShouldBe("LOL"); result["[0].Line2"].ShouldBeNull(); @@ -136,18 +136,18 @@ public void ShouldMapNestedComplexAndSimpleTypeEnumerablesToAnUntypedDictionary( }; var result = Mapper.Map(source).ToANew>(); - result.ContainsKey("Value1").ShouldBeFalse(); - result.ContainsKey("Value2").ShouldBeFalse(); + result.ShouldNotContainKey("Value1"); + result.ShouldNotContainKey("Value2"); result["Value1[0].Name"].ShouldBe("Clare"); - result.ContainsKey("Value1[0].Address").ShouldBeFalse(); + result.ShouldNotContainKey("Value1[0].Address"); result["Value1[0].Address.Line1"].ShouldBe("Nes"); result["Value1[0].Address.Line2"].ShouldBe("Ted"); result["Value1[1].Name"].ShouldBe("Jim"); - result.ContainsKey("Value1[1].Address").ShouldBeFalse(); - result.ContainsKey("Value1[1].Address.Line1").ShouldBeFalse(); - result.ContainsKey("Value1[1].Address.Line2").ShouldBeFalse(); + result.ShouldNotContainKey("Value1[1].Address"); + result.ShouldNotContainKey("Value1[1].Address.Line1"); + result.ShouldNotContainKey("Value1[1].Address.Line2"); result["Value2[0]"].ShouldBe(now.AddMinutes(1)); result["Value2[1]"].ShouldBe(now.AddMinutes(2)); @@ -264,11 +264,11 @@ public void ShouldMapToASimpleTypeDictionaryImplementation() result.Count.ShouldBe(3); - result.ContainsKey("[0]").ShouldBeTrue(); + result.ShouldContainKey("[0]"); result["[0]"].ShouldBe("Hello"); - result.ContainsKey("[1]").ShouldBeTrue(); + result.ShouldContainKey("[1]"); result["[1]"].ShouldBe("Goodbye"); - result.ContainsKey("[2]").ShouldBeTrue(); + result.ShouldContainKey("[2]"); result["[2]"].ShouldBe("See ya"); } @@ -285,13 +285,13 @@ public void ShouldMapFromASimpleTypeDictionaryImplementationToAnIDictionary() result.Count.ShouldBe(3); - result.ContainsKey("One").ShouldBeTrue(); + result.ShouldContainKey("One"); result["One"].ShouldBe("One!"); - result.ContainsKey("Two").ShouldBeTrue(); + result.ShouldContainKey("Two"); result["Two"].ShouldBe("Two!"); - result.ContainsKey("Three").ShouldBeTrue(); + result.ShouldContainKey("Three"); result["Three"].ShouldBe("Three!"); } @@ -307,10 +307,10 @@ public void ShouldMapBetweenSameDeclaredSimpleTypeIDictionaries() result.Count.ShouldBe(2); - result.ContainsKey("Hello").ShouldBeTrue(); + result.ShouldContainKey("Hello"); result["Hello"].ShouldBe("Bonjour"); - result.ContainsKey("Yes").ShouldBeTrue(); + result.ShouldContainKey("Yes"); result["Yes"].ShouldBe("Oui"); } @@ -327,18 +327,58 @@ public void ShouldMapBetweenSameComplexTypeDictionaryImplementations() result.Count.ShouldBe(3); - result.ContainsKey("One").ShouldBeTrue(); + result.ShouldContainKey("One"); result["One"].Line1.ShouldBe("1.1"); result["One"].Line2.ShouldBe("1.2"); - result.ContainsKey("Two").ShouldBeTrue(); + result.ShouldContainKey("Two"); result["Two"].Line1.ShouldBe("2.1"); result["Two"].Line2.ShouldBe("2.2"); - result.ContainsKey("Three").ShouldBeTrue(); + result.ShouldContainKey("Three"); result["Three"].ShouldBeNull(); } + [Fact] + public void ShouldFlattenToValueTypes() + { + var anonSource = new + { + Name = "Fred", + Array = new[] { 1, 2, 3 }, + ComplexList = new List>> + { + new PublicTwoFields> + { + Value1 = new byte[] { 4, 8, 16 }, + Value2 = new PublicField { Value = 456 } + }, + new PublicTwoFields> + { + Value1 = default(byte[]), + Value2 = new PublicField { Value = 789 } + } + } + }; + + var anonResult = Mapper.Map(anonSource).ToANew>(); + + // String members won't be mapped because they're not value types + anonResult.ShouldNotContainKey("Name"); + anonResult["Array[0]"].ShouldBe(1); + anonResult["Array[1]"].ShouldBe(2); + anonResult["Array[2]"].ShouldBe(3); + + anonResult["ComplexList[0].Value1[0]"].ShouldBe(4); + anonResult["ComplexList[0].Value1[1]"].ShouldBe(8); + anonResult["ComplexList[0].Value1[2]"].ShouldBe(16); + anonResult["ComplexList[0].Value2.Value"].ShouldBe(456); + + anonResult.ShouldNotContainKey("ComplexList[1].Value1"); + anonResult.ShouldNotContainKey("ComplexList[1].Value1[0]"); + anonResult["ComplexList[1].Value2.Value"].ShouldBe(789); + } + [Fact] public void ShouldHandleANullComplexTypeMember() { @@ -346,8 +386,8 @@ public void ShouldHandleANullComplexTypeMember() var result = Mapper.Map(source).ToANew>(); result["Name"].ShouldBe("Richie"); - result.ContainsKey("Address.Line1").ShouldBeFalse(); - result.ContainsKey("Address.Line2").ShouldBeFalse(); + result.ShouldNotContainKey("Address.Line1"); + result.ShouldNotContainKey("Address.Line2"); } [Fact] diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs index 32f16599d..caf86a7b5 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs @@ -25,7 +25,7 @@ public void ShouldMapNestedSimpleTypeMembersToANestedUntypedDictionary() result.Value.ShouldNotBeNull(); result.Value.ShouldNotBeEmpty(); result.Value["Name"].ShouldBe("Someone"); - result.Value.ContainsKey("Address").ShouldBeFalse(); + result.Value.ShouldNotContainKey("Address"); result.Value["Address.Line1"].ShouldBe("Some Place"); } @@ -55,15 +55,15 @@ public void ShouldMapANestedComplexTypeArrayToANestedTypedDictionary() }; var result = Mapper.Map(source).ToANew>>(); - result.Value.ContainsKey("[0]").ShouldBeFalse(); + result.Value.ShouldNotContainKey("[0]"); result.Value["[0].Name"].ShouldBeDefault(); - result.Value.ContainsKey("[0].Id").ShouldBeFalse(); // <- because id is a Guid, which can't be parsed to a decimal + result.Value.ShouldNotContainKey("[0].Id"); // <- because id is a Guid, which can't be parsed to a decimal result.Value["[0].AddressLine1"].ShouldBeDefault(); result.Value["[0].Discount"].ShouldBe(0.5); result.Value["[1].Name"].ShouldBeDefault(); - result.Value.ContainsKey("[1].Id").ShouldBeFalse(); + result.Value.ShouldNotContainKey("[1].Id"); result.Value["[1].AddressLine1"].ShouldBeDefault(); result.Value["[1].Discount"].ShouldBe(0.6); result.Value["[1].Report"].ShouldBe(0.075); @@ -82,7 +82,7 @@ public void ShouldFlattenANestedArrayOfArraysToANestedTypedDictionary() }; var result = Mapper.Map(source).ToANew>>(); - result.Value.ContainsKey("[0]").ShouldBeFalse(); + result.Value.ShouldNotContainKey("[0]"); result.Value["[0][0]"].ShouldBe(1.0); result.Value["[0][1]"].ShouldBe(2.0); @@ -105,7 +105,7 @@ public void ShouldMapANestedEnumerableOfArraysToANestedEnumerableTypedDictionary }; var result = Mapper.Map(source).ToANew>>>(); - result.Value.ContainsKey("[0][0]").ShouldBeFalse(); + result.Value.ShouldNotContainKey("[0][0]"); result.Value["[0]"].ShouldBe("1", "2", "3"); result.Value["[1]"].ShouldBe("4", "5", "6"); @@ -171,10 +171,10 @@ public void ShouldMapADictionaryObjectValuesToNewDictionaryObjectValues() var result = Mapper.Map(source).ToANew>>(); result.Value.ShouldNotBeNull(); - result.Value.ContainsKey("key1").ShouldBeTrue(); + result.Value.ShouldContainKey("key1"); result.Value["key1"].ShouldBeOfType(); result.Value["key1"].ShouldNotBeSameAs(source.Value["key1"]); - result.Value.ContainsKey("key2").ShouldBeTrue(); + result.Value.ShouldContainKey("key2"); result.Value["key2"].ShouldBeOfType(); result.Value["key2"].ShouldNotBeSameAs(source.Value["key2"]); } @@ -215,7 +215,7 @@ public void ShouldFlattenAComplexTypeCollectionToANestedObjectDictionaryImplemen result.Value["[0].Id"].ShouldBe(source.Value.First().Id); result.Value["[0].Title"].ShouldBe(Title.Count); result.Value["[0].Name"].ShouldBe("Customer 1"); - result.Value.ContainsKey("[0].Address").ShouldBeFalse(); + result.Value.ShouldNotContainKey("[0].Address"); result.Value["[0].Address.Line1"].ShouldBe("This place"); result.Value["[0].Address.Line2"].ShouldBe("That place"); @@ -224,9 +224,9 @@ public void ShouldFlattenAComplexTypeCollectionToANestedObjectDictionaryImplemen result.Value["[1].Name"].ShouldBe("Customer 2"); result.Value["[1].Discount"].ShouldBe(0.3m); result.Value["[1].Report"].ShouldBe("It was all a mystery :o"); - result.Value.ContainsKey("[1].Address").ShouldBeFalse(); - result.Value.ContainsKey("[1].Address.Line1").ShouldBeFalse(); - result.Value.ContainsKey("[1].Address.Line2").ShouldBeFalse(); + result.Value.ShouldNotContainKey("[1].Address"); + result.Value.ShouldNotContainKey("[1].Address.Line1"); + result.Value.ShouldNotContainKey("[1].Address.Line2"); } } diff --git a/AgileMapper/Extensions/TypeExtensions.cs b/AgileMapper/Extensions/TypeExtensions.cs index 8ddf302e0..0d8398bf6 100644 --- a/AgileMapper/Extensions/TypeExtensions.cs +++ b/AgileMapper/Extensions/TypeExtensions.cs @@ -185,12 +185,17 @@ public static bool IsSimple(this Type type) { type = type.GetNonNullableType(); - if (type.GetTypeCode() == NetStandardTypeCode.Object) + if (type == typeof(ValueType)) { - return type == typeof(Guid); + return true; } - return true; + if (type.GetTypeCode() != NetStandardTypeCode.Object) + { + return true; + } + + return type == typeof(Guid); } public static bool IsDictionary(this Type type) diff --git a/AgileMapper/TypeConversion/ConverterSet.cs b/AgileMapper/TypeConversion/ConverterSet.cs index 5861d433f..e754809d6 100644 --- a/AgileMapper/TypeConversion/ConverterSet.cs +++ b/AgileMapper/TypeConversion/ConverterSet.cs @@ -82,7 +82,7 @@ public Expression GetConversion(Expression sourceValue, Type targetType) if (sourceValue.Type.IsAssignableTo(targetType)) { - return targetType.IsNullableType() || (sourceValue.Type.IsSimple() && (targetType == typeof(object))) + return ConvertSourceValueToTargetType(sourceValue, targetType) ? sourceValue.GetConversionTo(targetType) : sourceValue; } @@ -97,6 +97,21 @@ public Expression GetConversion(Expression sourceValue, Type targetType) return conversion; } + private static bool ConvertSourceValueToTargetType(Expression sourceValue, Type targetType) + { + if (targetType.IsNullableType()) + { + return true; + } + + if (!sourceValue.Type.IsSimple()) + { + return false; + } + + return (targetType == typeof(object)) || (targetType == typeof(ValueType)); + } + public void CloneTo(ConverterSet converterSet) { if (_converters.Count == converterSet._converters.Count) From caa11ab404edb60936ec753e38f691398604125a Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 15:23:37 +0000 Subject: [PATCH 24/74] Support for IDictionary<,> -> complex type enumerable mapping short-circuits for target Collection<>s / Test coverage updates / Removing unused code --- .../WhenMappingToNewDictionaries.cs | 4 +- ...appingFromDynamicsOverEnumerableMembers.cs | 24 ++++++++++ AgileMapper.sln.DotSettings | 2 +- .../Inline/InlineMapperContextSet.cs | 5 ++ .../MappingConfigurationException.cs | 5 ++ .../DictionaryDataSourceFactory.cs | 2 +- .../DictionaryEntryVariablePair.cs | 9 +--- .../Extensions/ExpressionExtensions.cs | 45 ++++++++++-------- .../Extensions/ReflectionExtensions.cs | 6 +++ .../Members/MemberMapperDataExtensions.cs | 10 ---- .../SourceDictionaryShortCircuitFactory.cs | 46 +------------------ .../DictionaryMappingExpressionFactory.cs | 31 +++---------- ...rceElementsDictionaryPopulationLoopData.cs | 8 ---- .../EnumerablePopulationBuilder.cs | 18 -------- .../Enumerables/EnumerableTypeHelper.cs | 7 ++- .../Enumerables/ReadOnlyCollectionWrapper.cs | 16 +++++-- .../Validation/MappingValidationException.cs | 10 ++++ 17 files changed, 105 insertions(+), 143 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs index 2ce4a3650..13abd7016 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaries.cs @@ -342,7 +342,7 @@ public void ShouldMapBetweenSameComplexTypeDictionaryImplementations() [Fact] public void ShouldFlattenToValueTypes() { - var anonSource = new + var source = new { Name = "Fred", Array = new[] { 1, 2, 3 }, @@ -361,7 +361,7 @@ public void ShouldFlattenToValueTypes() } }; - var anonResult = Mapper.Map(anonSource).ToANew>(); + var anonResult = Mapper.Map(source).ToANew>(); // String members won't be mapped because they're not value types anonResult.ShouldNotContainKey("Name"); diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs index 91ae51d85..c94e3e676 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerableMembers.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Dynamic; using System.Linq; using Shouldly; @@ -73,5 +74,28 @@ public void ShouldOverwriteAnIndentifableComplexTypeCollection() target.Value.ShouldBe(p => p.ProductId, "prod-2", "prod-1"); target.Value.ShouldBe(p => p.Price, 15.00, 12.99); } + + [Fact] + public void ShouldOverwriteAComplexTypeCollectionFromElementEntries() + { + dynamic source = new ExpandoObject(); + + source.Value_0_ = new PublicField { Value = "Value 0" }; + source.Value_1_ = new PublicField { Value = "Value 1" }; + + var target = new PublicField>> + { + Value = new Collection> + { + new PublicField { Value = "Value 1" }, + new PublicField { Value = "Value 2" }, + } + }; + + Mapper.Map(source).Over(target); + + target.Value.Count.ShouldBe(2); + target.Value.ShouldBe(pf => pf.Value, "Value 0", "Value 1"); + } } } diff --git a/AgileMapper.sln.DotSettings b/AgileMapper.sln.DotSettings index 06f73f9ea..10b838ca2 100644 --- a/AgileMapper.sln.DotSettings +++ b/AgileMapper.sln.DotSettings @@ -1,3 +1,3 @@  <data><IncludeFilters /><ExcludeFilters /></data> - <data><AttributeFilter ClassMask="*.ExcludeFromCodeCoverageAttribute" IsEnabled="True" /></data> \ No newline at end of file + <data><AttributeFilter ClassMask="AgileObjects.AgileMapper.ExcludeFromCodeCoverageAttribute" IsEnabled="True" /></data> \ No newline at end of file diff --git a/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs b/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs index fc8e7fce5..c2a578a0f 100644 --- a/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs +++ b/AgileMapper/Configuration/Inline/InlineMapperContextSet.cs @@ -36,6 +36,11 @@ public IEnumerator GetEnumerator() return _inlineContextsCache.Values.GetEnumerator(); } + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion diff --git a/AgileMapper/Configuration/MappingConfigurationException.cs b/AgileMapper/Configuration/MappingConfigurationException.cs index 7552feaf8..b0ec29dbc 100644 --- a/AgileMapper/Configuration/MappingConfigurationException.cs +++ b/AgileMapper/Configuration/MappingConfigurationException.cs @@ -10,6 +10,11 @@ public class MappingConfigurationException : Exception /// /// Initializes a new instance of the MappingConfigurationException class. /// + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion public MappingConfigurationException() { } diff --git a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs index 30637377e..a3e5bb5f0 100644 --- a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs @@ -82,7 +82,7 @@ private static DictionarySourceMember GetSourceMember(IMemberMapperData mapperDa while (!parentMapperData.IsRoot) { - if (parentMapperData.TargetMember.LeafMember == mapperData.TargetMember.LeafMember) + if (parentMapperData.TargetMember.LeafMember.Equals(mapperData.TargetMember.LeafMember)) { break; } diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index e3f30323a..432f19a79 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -42,14 +42,7 @@ public DictionaryEntryVariablePair(DictionarySourceMember sourceMember, IMemberM } private static string GetTargetMemberName(IBasicMapperData mapperData) - { - if (mapperData.TargetMemberIsEnumerableElement()) - { - return "element"; - } - - return mapperData.TargetMember.Name.ToCamelCase(); - } + => mapperData.TargetMember.Name.ToCamelCase(); public DictionarySourceMember SourceMember { get; } diff --git a/AgileMapper/Extensions/ExpressionExtensions.cs b/AgileMapper/Extensions/ExpressionExtensions.cs index 49ed77ed2..623e12b00 100644 --- a/AgileMapper/Extensions/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/ExpressionExtensions.cs @@ -113,20 +113,7 @@ public static Expression GetIsNotDefaultComparison(this Expression expression) var typeDefault = expression.Type.ToDefaultExpression(); - if (!expression.Type.IsValueType() || !expression.Type.IsComplex()) - { - return Expression.NotEqual(expression, typeDefault); - } - - var objectEquals = typeof(object).GetPublicStaticMethod("Equals"); - - var objectEqualsCall = Expression.Call( - null, - objectEquals, - expression.GetConversionTo(typeof(object)), - typeDefault.GetConversionTo(typeof(object))); - - return Expression.IsFalse(objectEqualsCall); + return Expression.NotEqual(expression, typeDefault); } public static Expression GetIndexAccess(this Expression indexedExpression, Expression indexValue) @@ -221,13 +208,12 @@ private static MethodInfo GetNonListToArrayConversionMethod(EnumerableTypeHelper : _linqToArrayMethod; } - public static Expression WithToReadOnlyCollectionCall(this Expression enumerable, Type elementType) + public static Expression WithToReadOnlyCollectionCall(this Expression enumerable, EnumerableTypeHelper typeHelper) { - var typeHelper = new EnumerableTypeHelper(enumerable.Type, elementType); - if (TryGetWrapperMethod(typeHelper, "ToReadOnlyCollection", out var method)) { - return GetToEnumerableCall(enumerable, method, elementType); + + return GetToEnumerableCall(enumerable, method, typeHelper.ElementType); } if (typeHelper.IsList) @@ -241,11 +227,24 @@ public static Expression WithToReadOnlyCollectionCall(this Expression enumerable } var nonListToArrayMethod = GetNonListToArrayConversionMethod(typeHelper); - var toArrayCall = GetToEnumerableCall(enumerable, nonListToArrayMethod, elementType); + var toArrayCall = GetToEnumerableCall(enumerable, nonListToArrayMethod, typeHelper.ElementType); return GetReadOnlyCollectionCreation(typeHelper, toArrayCall); } + public static Expression WithToCollectionCall(this Expression enumerable, EnumerableTypeHelper typeHelper) + { + if (typeHelper.HasListInterface) + { + return GetCollectionCreation(typeHelper, enumerable.GetConversionTo(typeHelper.ListInterfaceType)); + } + + var nonListToArrayMethod = GetNonListToArrayConversionMethod(typeHelper); + var toArrayCall = GetToEnumerableCall(enumerable, nonListToArrayMethod, typeHelper.ElementType); + + return GetCollectionCreation(typeHelper, toArrayCall); + } + public static Expression WithToListCall(this Expression enumerable, Type elementType) => GetToEnumerableCall(enumerable, _linqToListMethod, elementType); @@ -300,6 +299,14 @@ private static Expression GetReadOnlyCollectionCreation(EnumerableTypeHelper typ list); } + private static Expression GetCollectionCreation(EnumerableTypeHelper typeHelper, Expression list) + { + // ReSharper disable once AssignNullToNotNullAttribute + return Expression.New( + typeHelper.CollectionType.GetPublicInstanceConstructor(typeHelper.ListInterfaceType), + list); + } + private static Type GetDictionaryType(Type dictionaryType) { return dictionaryType.IsInterface() diff --git a/AgileMapper/Extensions/ReflectionExtensions.cs b/AgileMapper/Extensions/ReflectionExtensions.cs index 8d24a45eb..93fad03b4 100644 --- a/AgileMapper/Extensions/ReflectionExtensions.cs +++ b/AgileMapper/Extensions/ReflectionExtensions.cs @@ -7,6 +7,12 @@ internal static class ReflectionExtensions { public static readonly bool ReflectionNotPermitted; + // This definitely get executed, but code coverage doesn't pick it up + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion static ReflectionExtensions() { try diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 0e4d975e4..14939e395 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -106,16 +106,6 @@ public static DictionarySourceMember GetDictionarySourceMemberOrNull(this IMembe return dictionarySourceMember; } - if (!(mapperData.SourceMember is DictionaryEntrySourceMember dictionaryEntrySourceMember)) - { - return null; - } - - if (dictionaryEntrySourceMember.Type.IsDictionary()) - { - return dictionaryEntrySourceMember.Parent; - } - // We're mapping a dictionary entry by its runtime type: return null; } diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs index cf52a608e..c33df3a52 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs @@ -1,12 +1,9 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes { - using System.Collections.Generic; using System.Linq.Expressions; using DataSources; using Extensions; using Members; - using NetStandardPolyfills; - using ReadableExpressions.Extensions; internal class SourceDictionaryShortCircuitFactory : ISourceShortCircuitFactory { @@ -51,11 +48,6 @@ public Expression GetShortCircuit(IObjectMappingData mappingData) var mapValueCall = GetMapValueCall(dictionaryVariables.Value, mapperData); var fallbackValue = GetFallbackValue(mappingData); - if (mapperData.TargetMember.IsRecursionRoot()) - { - AdjustForStandaloneContext(ref mapValueCall, ref fallbackValue, mapperData); - } - var valueMappingOrFallback = Expression.Condition(foundValueNonNull, mapValueCall, fallbackValue); var returnMapValueResult = Expression.Return(mapperData.ReturnLabelTarget, valueMappingOrFallback); var ifEntryExistsShortCircuit = Expression.IfThen(entryExistsTest, returnMapValueResult); @@ -87,51 +79,15 @@ private static Expression GetEntryExistsTest(DictionaryEntryVariablePair diction } private static MethodCallExpression GetMapValueCall(Expression sourceValue, IMemberMapperData mapperData) - { - if (mapperData.TargetMemberIsEnumerableElement()) - { - return mapperData.Parent.GetMapCall(sourceValue); - } - - return mapperData.Parent.GetMapCall(sourceValue, mapperData.TargetMember, dataSourceIndex: 0); - } + => mapperData.Parent.GetMapCall(sourceValue, mapperData.TargetMember, dataSourceIndex: 0); private static Expression GetFallbackValue(IObjectMappingData mappingData) { - if (mappingData.MapperData.TargetMemberIsEnumerableElement()) - { - return mappingData.MapperData.TargetMember.Type.ToDefaultExpression(); - } - return mappingData.MappingContext .RuleSet .FallbackDataSourceFactory .Create(mappingData.MapperData) .Value; } - - private static void AdjustForStandaloneContext( - ref MethodCallExpression mapValueCall, - ref Expression fallbackValue, - IMemberMapperData mapperData) - { - var parentMappingTypes = mapperData.Parent.MappingDataObject.Type.GetGenericTypeArguments(); - var parentContextAccess = mapperData.GetAppropriateMappingContextAccess(parentMappingTypes); - var typedParentContextAccess = mapperData.GetTypedContextAccess(parentContextAccess, parentMappingTypes); - var parentTargetAccess = mapperData.GetTargetAccess(parentContextAccess, mapperData.TargetType); - - var replacements = new Dictionary(2) - { - [mapValueCall.GetSubject()] = typedParentContextAccess, - [mapValueCall.Arguments[1]] = parentTargetAccess - }; - - mapValueCall = mapValueCall.Replace(replacements); - - if (fallbackValue.NodeType != ExpressionType.Default) - { - fallbackValue = parentTargetAccess; - } - } } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index a15adfecd..65fc14750 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -285,33 +285,14 @@ private static Expression GetMappedDictionaryAssignment( var comparerProperty = mapperData.SourceObject.Type.GetPublicInstanceProperty("Comparer"); - Expression dictionaryConstruction; + var comparer = Expression.Property(mapperData.SourceObject, comparerProperty); - if (comparerProperty == null) - { - if (mapperData.TargetType.IsInterface()) - { - dictionaryConstruction = Expression.New(GetConcreteDictionaryType(mapperData.TargetType)); - } - else - { - dictionaryConstruction = mapperData - .MapperContext - .ConstructionFactory - .GetNewObjectCreation(mappingData); - } - } - else - { - var comparer = Expression.Property(mapperData.SourceObject, comparerProperty); - - var constructor = FindDictionaryConstructor( - mapperData.TargetType, - comparer.Type, - numberOfParameters: 1); + var constructor = FindDictionaryConstructor( + mapperData.TargetType, + comparer.Type, + numberOfParameters: 1); - dictionaryConstruction = Expression.New(constructor, comparer); - } + var dictionaryConstruction = Expression.New(constructor, comparer); return GetDictionaryAssignment(dictionaryConstruction, mappingData); } diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs index a5eb764ea..e985b2fbc 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs @@ -5,7 +5,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries using System.Linq.Expressions; using DataSources; using Extensions; - using Members.Dictionaries; using NetStandardPolyfills; internal class SourceElementsDictionaryPopulationLoopData : IPopulationLoopData @@ -18,13 +17,6 @@ internal class SourceElementsDictionaryPopulationLoopData : IPopulationLoopData private readonly Expression _sourceElement; private ParameterExpression _elementKeyExists; - public SourceElementsDictionaryPopulationLoopData( - DictionarySourceMember sourceMember, - EnumerablePopulationBuilder builder) - : this(new DictionaryEntryVariablePair(sourceMember, builder.MapperData), builder) - { - } - public SourceElementsDictionaryPopulationLoopData( DictionaryEntryVariablePair dictionaryVariables, EnumerablePopulationBuilder builder) diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index d2caf490e..14208ae61 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -20,12 +20,6 @@ internal class EnumerablePopulationBuilder (m.GetParameters().Length == 2) && (m.GetParameters()[1].ParameterType.GetGenericTypeArguments().Length == 2)); - private static readonly MethodInfo _selectWithIndexMethod = typeof(Enumerable) - .GetPublicStaticMethods("Select") - .Last(m => - (m.GetParameters().Length == 2) && - (m.GetParameters()[1].ParameterType.GetGenericTypeArguments().Length == 3)); - private static readonly MethodInfo _forEachMethod = typeof(EnumerableExtensions) .GetPublicStaticMethods("ForEach") .First(); @@ -545,18 +539,6 @@ public Expression GetSourceItemsProjection( _sourceElementParameter); } - public Expression GetSourceItemsProjection( - Expression sourceEnumerableValue, - Func projectionFuncFactory) - { - return GetSourceItemsProjection( - sourceEnumerableValue, - _selectWithIndexMethod, - projectionFuncFactory, - _sourceElementParameter, - Counter); - } - private Expression GetSourceItemsProjection( Expression sourceEnumerableValue, MethodInfo linqSelectOverload, diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs index 0b3d991a9..8f5784979 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs @@ -105,7 +105,12 @@ public Expression GetEnumerableConversion(Expression instance) if (IsReadOnlyCollection) { - return instance.WithToReadOnlyCollectionCall(ElementType); + return instance.WithToReadOnlyCollectionCall(this); + } + + if (IsCollection) + { + return instance.WithToCollectionCall(this); } return instance.WithToListCall(ElementType); diff --git a/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs b/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs index c189317af..6e4bc7b0d 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs @@ -92,14 +92,20 @@ public ReadOnlyCollectionWrapper(IList existingItems, int numberOfNewItems) /// /// The zero-based index of the element to get or set. /// The element at the specified index. - #region ExcludeFromCodeCoverage -#if DEBUG - [ExcludeFromCodeCoverage] -#endif - #endregion public T this[int index] { + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion get => _items[index]; + + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion set => _items[index] = value; } diff --git a/AgileMapper/Validation/MappingValidationException.cs b/AgileMapper/Validation/MappingValidationException.cs index 6561c59bb..ec04030b1 100644 --- a/AgileMapper/Validation/MappingValidationException.cs +++ b/AgileMapper/Validation/MappingValidationException.cs @@ -12,6 +12,11 @@ public class MappingValidationException : Exception /// /// Initializes a new instance of the MappingValidationException class. /// + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion public MappingValidationException() : this("Mapping validation failed.") { @@ -33,6 +38,11 @@ public MappingValidationException(string message) /// The SerializationInfo containing serialization information. /// The StreamingContext in which the deserialization is being performed. // ReSharper disable UnusedParameter.Local + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion protected MappingValidationException(SerializationInfo info, StreamingContext context) { } From 066a02f7ad1b31185942d3314bf53537a1b5df56 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 10 Dec 2017 15:29:10 +0000 Subject: [PATCH 25/74] Test coverage for mapping over root complex type members --- .../AgileMapper.UnitTests.csproj | 1 + ...WhenMappingFromDynamicsOverComplexTypes.cs | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypes.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 9f3d80062..5c1c1b068 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -103,6 +103,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypes.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypes.cs new file mode 100644 index 000000000..fb9c74f29 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverComplexTypes.cs @@ -0,0 +1,26 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsOverComplexTypes + { + [Fact] + public void ShouldOverwriteComplexTypeMembers() + { + dynamic source = new ExpandoObject(); + + source.Line1 = "Up there!"; + source.Line2 = default(string); + + var target = new Address { Line2 = "Up where?!" }; + + Mapper.Map(source).Over(target); + + target.Line1.ShouldBe("Up there!"); + target.Line2.ShouldBeNull(); + } + } +} From 6fb02d34e581644111996b45bd532f86add51e68 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 17:58:29 +0000 Subject: [PATCH 26/74] Extending enum -> enum mapping test coverage --- .../SimpleTypeConversion/WhenConvertingToEnums.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs b/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs index 1965afc76..db1e65352 100644 --- a/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs +++ b/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs @@ -159,6 +159,15 @@ public void ShouldMapANonMatchingEnumToANullableEnum() result.Value.ShouldBeNull(); } + [Fact] + public void ShouldMapANullableEnumToAnEnum() + { + var source = new PublicProperty { Value = Title.Dr }; + var result = Mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(Title.Dr); + } + [Fact] public void ShouldMapANullNullableEnumToAnEnum() { From a9c3fdb19c993fa03950e44fa8dfb2f0bbbfd571 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 18:37:41 +0000 Subject: [PATCH 27/74] Start of mapping to dynamic test coverage --- .../AgileMapper.UnitTests.csproj | 1 + .../Dynamics/WhenMappingToNewDynamics.cs | 17 +++++++++++++++++ AgileMapper/Extensions/ExpressionExtensions.cs | 14 +++++++------- .../Enumerables/EnumerableTypeHelper.cs | 4 ++-- 4 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 5c1c1b068..b60f8907f 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -109,6 +109,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs new file mode 100644 index 000000000..3da2e8122 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs @@ -0,0 +1,17 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using Shouldly; + using Xunit; + + public class WhenMappingToNewDynamics + { + [Fact] + public void ShouldMapToASimpleTypeMember() + { + var result = Mapper.Map(new { Value = 123 }).ToANew(); + + ((object)result).ShouldNotBeNull(); + ((int)result.Value).ShouldBe(123); + } + } +} diff --git a/AgileMapper/Extensions/ExpressionExtensions.cs b/AgileMapper/Extensions/ExpressionExtensions.cs index 623e12b00..e281b4d64 100644 --- a/AgileMapper/Extensions/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/ExpressionExtensions.cs @@ -202,14 +202,12 @@ private static bool TryGetWrapperMethod( } private static MethodInfo GetNonListToArrayConversionMethod(EnumerableTypeHelper typeHelper) - { - return typeHelper.HasCollectionInterface - ? _collectionToArrayMethod - : _linqToArrayMethod; - } + => typeHelper.HasCollectionInterface ? _collectionToArrayMethod : _linqToArrayMethod; - public static Expression WithToReadOnlyCollectionCall(this Expression enumerable, EnumerableTypeHelper typeHelper) + public static Expression WithToReadOnlyCollectionCall(this Expression enumerable, Type elementType) { + var typeHelper = new EnumerableTypeHelper(enumerable.Type, elementType); + if (TryGetWrapperMethod(typeHelper, "ToReadOnlyCollection", out var method)) { @@ -232,8 +230,10 @@ public static Expression WithToReadOnlyCollectionCall(this Expression enumerable return GetReadOnlyCollectionCreation(typeHelper, toArrayCall); } - public static Expression WithToCollectionCall(this Expression enumerable, EnumerableTypeHelper typeHelper) + public static Expression WithToCollectionCall(this Expression enumerable, Type elementType) { + var typeHelper = new EnumerableTypeHelper(enumerable.Type, elementType); + if (typeHelper.HasListInterface) { return GetCollectionCreation(typeHelper, enumerable.GetConversionTo(typeHelper.ListInterfaceType)); diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs index 8f5784979..03e4979b2 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs @@ -105,12 +105,12 @@ public Expression GetEnumerableConversion(Expression instance) if (IsReadOnlyCollection) { - return instance.WithToReadOnlyCollectionCall(this); + return instance.WithToReadOnlyCollectionCall(ElementType); } if (IsCollection) { - return instance.WithToCollectionCall(this); + return instance.WithToCollectionCall(ElementType); } return instance.WithToListCall(ElementType); From 56f81086d3a4a1251050a1019332f38136e6c68a Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 18:46:27 +0000 Subject: [PATCH 28/74] Extending map-to-dynamic test coverage --- .../Dynamics/WhenMappingToNewDynamics.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs index 3da2e8122..0c3be67f2 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs @@ -1,17 +1,27 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { + using System.Dynamic; using Shouldly; using Xunit; public class WhenMappingToNewDynamics { [Fact] - public void ShouldMapToASimpleTypeMember() + public void ShouldMapToADynamicSimpleTypeMember() { var result = Mapper.Map(new { Value = 123 }).ToANew(); ((object)result).ShouldNotBeNull(); ((int)result.Value).ShouldBe(123); } + + [Fact] + public void ShouldMapToAnExpandoObjectSimpleTypeMember() + { + dynamic result = Mapper.Map(new { Value = "Oh so dynamic" }).ToANew(); + + ((object)result).ShouldNotBeNull(); + ((string)result.Value).ShouldBe("Oh so dynamic"); + } } } From 17d6fb750f35c3661b7d6828654371d59e2ff6e3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 18:52:01 +0000 Subject: [PATCH 29/74] Start of test coverage for mapping over dynamics / Skipping 'assign to itself' target variable populations --- .../AgileMapper.UnitTests.csproj | 1 + .../Dynamics/WhenMappingOverDynamics.cs | 22 +++++++++++++++++++ .../DictionaryMappingExpressionFactory.cs | 5 +++++ 3 files changed, 28 insertions(+) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index b60f8907f..c5405dc73 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -109,6 +109,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs new file mode 100644 index 000000000..c1c370ebe --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs @@ -0,0 +1,22 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using Xunit; + + public class WhenMappingOverDynamics + { + [Fact] + public void ShouldOverwriteASimpleTypeProperty() + { + var source = new { Value = 123 }; + + dynamic target = new ExpandoObject(); + + target.Value = 456; + + Mapper.Map(source).Over(target); + + ((int)target.Value).ShouldBe(123); + } + } +} diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 65fc14750..cc07fb4c5 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -331,6 +331,11 @@ private static Expression GetDictionaryAssignment(Expression value, IObjectMappi mappingData, mapperData.HasMapperFuncs); + if (valueResolution == mapperData.TargetInstance) + { + return null; + } + return mapperData.TargetInstance.AssignTo(valueResolution); } From e3903f4c64a93c471cd1669850d9fe880dfce7ff Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 20:16:14 +0000 Subject: [PATCH 30/74] Extending coverage for mapping over dynamic root members --- .../Dynamics/WhenMappingOverDynamics.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs index c1c370ebe..7c97bedec 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { using System.Dynamic; + using TestClasses; using Xunit; public class WhenMappingOverDynamics @@ -18,5 +19,22 @@ public void ShouldOverwriteASimpleTypeProperty() ((int)target.Value).ShouldBe(123); } + + [Fact] + public void ShouldOverwriteAnEnumProperty() + { + var source = new PublicPropertyStruct + { + Value = TitleShortlist.Mrs + }; + + dynamic target = new ExpandoObject(); + + target.Value = Title.Mr; + + Mapper.Map(source).Over(target); + + ((TitleShortlist)target.Value).ShouldBe(TitleShortlist.Mrs); + } } } From 941363ae3821a6afc329756cb35af7b51a8358c3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 20:45:44 +0000 Subject: [PATCH 31/74] Start of merging to dynamics support --- .../AgileMapper.UnitTests.csproj | 1 + .../Dynamics/WhenMappingOnToDynamics.cs | 29 +++++++++++++++++++ .../Dictionaries/DictionaryTargetMember.cs | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamics.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index c5405dc73..e50e61c84 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -109,6 +109,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamics.cs new file mode 100644 index 000000000..58bda2f47 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamics.cs @@ -0,0 +1,29 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using TestClasses; + using Xunit; + + public class WhenMappingOnToDynamics + { + [Fact] + public void ShouldUpdateANullMemberValue() + { + var source = new PublicTwoFieldsStruct + { + Value1 = "New value!", + Value2 = "Won't be a new value!" + }; + + dynamic target = new ExpandoObject(); + + target.Value1 = default(string); + target.Value2 = "Already populated!"; + + Mapper.Map(source).OnTo(target); + + ((string)target.Value1).ShouldBe("New value!"); + ((string)target.Value2).ShouldBe("Already populated!"); + } + } +} diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 5b66f587f..efa24dd06 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -210,7 +210,7 @@ public override BlockExpression GetAccessChecked(IMemberMapperData mapperData) private Expression GetTryGetValueCall(IMemberMapperData mapperData, out ParameterExpression valueVariable) { var dictionaryAccess = GetDictionaryAccess(mapperData); - var tryGetValueMethod = dictionaryAccess.Type.GetPublicInstanceMethod("TryGetValue"); + var tryGetValueMethod = dictionaryAccess.Type.GetDictionaryType().GetPublicInstanceMethod("TryGetValue"); var key = GetKey(mapperData); valueVariable = Expression.Variable(ValueType, "existingValue"); From 957bee0f2a17477fbf302da7bf3dc1d2b38241d0 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 12 Dec 2017 21:53:07 +0000 Subject: [PATCH 32/74] Restricting WhenMapping.DictionariesWithValueType mappings to dictionaries of that value type / Applying WhenMapping.Dictionaries to all dictionaries of all value types / Applying source dictionary configuration to dictionaries, not ExpandoObjects --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenConfiguringSourceDictionaryMapping.cs | 31 +++++++++++++++++ .../WhenConfiguringSourceDynamicMapping.cs | 33 +++++++++++++++++++ .../Dictionaries/DictionaryConfigurator.cs | 15 ++++++--- .../Dictionaries/DictionaryType.cs | 8 +++++ .../SourceDictionaryMappingConfigurator.cs | 6 ++-- .../MappingConfigStartingPoint.cs | 15 +++++++-- .../Configuration/CustomDictionaryKey.cs | 17 +++++++++- .../Configuration/DictionarySettings.cs | 10 +++--- .../Configuration/MappingConfigInfo.cs | 12 +++++++ 10 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs create mode 100644 AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index e50e61c84..31eb2231a 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -102,6 +102,7 @@ + diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs index 7b5905eb9..3f435f927 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs @@ -419,5 +419,36 @@ public void ShouldConditionallyMapToDerivedTypesFromASpecificValueTypeDictionary ((MysteryCustomerViewModel)mysteryCustomerResult).Report.ShouldBe("Plenty long enough!"); } } + + [Fact] + public void ShouldRestrictCustomKeysByDictionaryValueType() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .DictionariesWithValueType() + .ToANew>() + .MapFullKey("LaLaLa") + .To(p => p.Value); + + var matchingSource = new Dictionary + { + ["LaLaLa"] = "1", + ["Value"] = "2" + }; + var matchingResult = mapper.Map(matchingSource).ToANew>(); + + matchingResult.Value.ShouldBe("1"); + + var nonMatchingSource = new Dictionary + { + ["LaLaLa"] = "20", + ["Value"] = "10" + }; + var nonMatchingResult = mapper.Map(nonMatchingSource).ToANew>(); + + nonMatchingResult.Value.ShouldBe("10"); + } + } } } diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs new file mode 100644 index 000000000..922b2ec21 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -0,0 +1,33 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics.Configuration +{ + using System.Dynamic; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenConfiguringSourceDynamicMapping + { + [Fact] + public void ShouldNotApplyDictionaryConfiguration() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .DictionariesWithValueType() + .To>() + .MapFullKey("LaLaLa") + .To(pf => pf.Value); + + dynamic source = new ExpandoObject(); + + source.LaLaLa = 1; + source.Value = 2; + + var result = (PublicField)mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Value.ShouldBe("2"); + } + } + } +} diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs index 318d82cb1..6e860ddda 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs @@ -15,7 +15,12 @@ public class DictionaryConfigurator : IGlobalDictionarySettings internal DictionaryConfigurator(MappingConfigInfo configInfo) { - _configInfo = configInfo.ForSourceValueType(); + _configInfo = configInfo.Set(DictionaryType.Dictionary); + + if (_configInfo.SourceValueType == null) + { + _configInfo.ForSourceValueType(); + } } #region IGlobalDictionarySettings Members @@ -79,7 +84,7 @@ public IGlobalDictionarySettings UseElementKeyPattern(string pattern) /// /// The target type to which the configuration will apply. /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator To() where TTarget : class + public ISourceDictionaryMappingConfigurator To() => CreateConfigurator(_configInfo.ForAllRuleSets()); /// @@ -88,7 +93,7 @@ public ISourceDictionaryMappingConfigurator To() where /// /// The target type to which the configuration will apply. /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator ToANew() where TTarget : class + public ISourceDictionaryMappingConfigurator ToANew() => CreateConfigurator(Constants.CreateNew); /// @@ -97,7 +102,7 @@ public ISourceDictionaryMappingConfigurator ToANew() w /// /// The target type to which the configuration will apply. /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator OnTo() where TTarget : class + public ISourceDictionaryMappingConfigurator OnTo() => CreateConfigurator(Constants.Merge); /// @@ -106,7 +111,7 @@ public ISourceDictionaryMappingConfigurator OnTo() whe /// /// The target type to which the configuration will apply. /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator Over() where TTarget : class + public ISourceDictionaryMappingConfigurator Over() => CreateConfigurator(Constants.Overwrite); private ISourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs new file mode 100644 index 000000000..b30589abe --- /dev/null +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs @@ -0,0 +1,8 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries +{ + internal enum DictionaryType + { + Dictionary, + ExpandoObject + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs index 24269ce80..52c9a1079 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs @@ -33,19 +33,19 @@ ISourceDictionaryMappingConfigurator ISourceDictionaryConfigSet #endregion public CustomDictionaryMappingTargetMemberSpecifier MapFullKey(string fullMemberNameKey) - => CreateTargetMemberSpecifier("keys", fullMemberNameKey, (settings, customKey) => settings.AddFullKey(customKey)); + => CreateTargetMemberSpecifier(fullMemberNameKey, "keys", (settings, customKey) => settings.AddFullKey(customKey)); public CustomDictionaryMappingTargetMemberSpecifier MapMemberNameKey(string memberNameKeyPart) { return CreateTargetMemberSpecifier( - "member names", memberNameKeyPart, + "member names", (settings, customKey) => settings.AddMemberKey(customKey)); } private CustomDictionaryMappingTargetMemberSpecifier CreateTargetMemberSpecifier( - string keyName, string key, + string keyName, Action dictionarySettingsAction) { if (key == null) diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index bdfd63124..1112b6cbb 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -346,7 +346,8 @@ public InstanceConfigurator InstancesOf() where TObject : clas /// /// Configure how this mapper performs mappings from source Dictionary{string, T} instances. /// - public DictionaryConfigurator Dictionaries => DictionariesWithValueType(); + public DictionaryConfigurator Dictionaries + => CreateDictionaryConfigurator(config => config.ForSourceValueType(Constants.AllTypes)); /// /// Configure how this mapper performs mappings from source Dictionary{string, TValue} instances. @@ -356,7 +357,17 @@ public InstanceConfigurator InstancesOf() where TObject : clas /// /// A DictionaryConfigurator with which to continue the configuration. public DictionaryConfigurator DictionariesWithValueType() - => new DictionaryConfigurator(_configInfo.ForAllSourceTypes()); + => CreateDictionaryConfigurator(); + + private DictionaryConfigurator CreateDictionaryConfigurator( + Action configInfoConfigurator = null) + { + var configInfo = _configInfo.ForAllSourceTypes(); + + configInfoConfigurator?.Invoke(configInfo); + + return new DictionaryConfigurator(configInfo); + } /// /// Configure how this mapper performs mappings from the source type specified by the given diff --git a/AgileMapper/Configuration/CustomDictionaryKey.cs b/AgileMapper/Configuration/CustomDictionaryKey.cs index 7fb57ff66..8446d2efa 100644 --- a/AgileMapper/Configuration/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/CustomDictionaryKey.cs @@ -1,8 +1,11 @@ namespace AgileObjects.AgileMapper.Configuration { using System; + using System.Dynamic; using System.Linq.Expressions; + using Api.Configuration.Dictionaries; using DataSources; + using Extensions; using Members; internal class CustomDictionaryKey : UserConfiguredItemBase @@ -51,13 +54,25 @@ public string GetConflictMessage(ConfiguredDataSourceFactory conflictingDataSour return $"Configured dictionary key member {TargetMember.GetPath()} has a configured data source"; } - public bool AppliesTo(Member member, IBasicMapperData mapperData) + public bool AppliesTo(Member member, IMemberMapperData mapperData) { if (!base.AppliesTo(mapperData)) { return false; } + if (((ConfigInfo.SourceValueType ?? Constants.AllTypes) != Constants.AllTypes) && + (mapperData.SourceType.GetDictionaryTypes().Value != ConfigInfo.SourceValueType)) + { + return false; + } + + if ((ConfigInfo.Get() != DictionaryType.ExpandoObject) && + (mapperData.SourceMember.GetFriendlyTypeName() == nameof(ExpandoObject))) + { + return false; + } + if (_sourceMember == null) { return true; diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/DictionarySettings.cs index c5e1bee1b..b483525cc 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/DictionarySettings.cs @@ -36,10 +36,10 @@ public void AddFullKey(CustomDictionaryKey configuredKey) _configuredFullKeys.Add(configuredKey); } - public Expression GetFullKeyOrNull(IBasicMapperData mapperData) + public Expression GetFullKeyOrNull(IMemberMapperData mapperData) => GetFullKeyValueOrNull(mapperData)?.ToConstantExpression(); - public string GetFullKeyValueOrNull(IBasicMapperData mapperData) + public string GetFullKeyValueOrNull(IMemberMapperData mapperData) { if (mapperData.TargetMember.IsCustom) { @@ -59,16 +59,16 @@ public void AddMemberKey(CustomDictionaryKey customKey) _configuredMemberKeys.Add(customKey); } - public string GetMemberKeyOrNull(IBasicMapperData mapperData) + public string GetMemberKeyOrNull(IMemberMapperData mapperData) => GetMemberKeyOrNull(mapperData.TargetMember.LeafMember, mapperData); - public string GetMemberKeyOrNull(Member member, IBasicMapperData mapperData) + public string GetMemberKeyOrNull(Member member, IMemberMapperData mapperData) => FindKeyOrNull(_configuredMemberKeys, member, mapperData)?.Key; private static CustomDictionaryKey FindKeyOrNull( IEnumerable keys, Member member, - IBasicMapperData mapperData) + IMemberMapperData mapperData) => keys.FirstOrDefault(k => k.AppliesTo(member, mapperData)); public void Add(JoiningNameFactory joiningNameFactory) diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index b13f35237..4d058717a 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Configuration { using System; + using System.Collections.Generic; using System.Globalization; using System.Linq.Expressions; using Extensions; @@ -14,6 +15,7 @@ internal class MappingConfigInfo : ITypePair private ConfiguredLambdaInfo _conditionLambda; private bool _negateCondition; + private Dictionary _data; public MappingConfigInfo(MapperContext mapperContext) { @@ -172,6 +174,16 @@ public Expression GetConditionOrNull( #endregion + public T Get() => Data.TryGetValue(typeof(T), out var value) ? (T)value : default(T); + + public MappingConfigInfo Set(T value) + { + Data[typeof(T)] = value; + return this; + } + + private Dictionary Data => (_data ?? (_data = new Dictionary())); + public IBasicMapperData ToMapperData() { var dummyTargetMember = QualifiedMember From 1bc103a99253b5ebadd6248796a70af1dd05f3af Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 15 Dec 2017 18:50:32 +0000 Subject: [PATCH 33/74] Extending member matching test coverage --- .../Members/WhenFindingDataSources.cs | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs index b1180e8bc..c85c3ba1b 100644 --- a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs +++ b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs @@ -1,5 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Members { + using System; + using System.Linq.Expressions; using AgileMapper.Members; using ObjectPopulation; using Shouldly; @@ -13,7 +15,31 @@ public void ShouldNotMatchSameNameIncompatibleTypeProperties() { var source = new TwoValues { Value = new int[5], value = string.Empty }; var target = new PublicProperty(); - var targetMember = TargetMemberFor>(x => x.Value); + + var matchingSourceMember = GetMatchingSourceMember(source, target, pp => pp.Value); + + matchingSourceMember.ShouldNotBeNull(); + matchingSourceMember.Name.ShouldBe("value"); + } + + [Fact] + public void ShouldUseBaseClassMembers() + { + var source = new Derived { Value = 123 }; + var target = new PublicProperty(); + + var matchingSourceMember = GetMatchingSourceMember(source, target, pp => pp.Value); + + matchingSourceMember.ShouldNotBeNull(); + matchingSourceMember.Name.ShouldBe("Value"); + } + + private IQualifiedMember GetMatchingSourceMember( + TSource source, + TTarget target, + Expression> childMemberExpression) + { + var targetMember = TargetMemberFor(childMemberExpression); var mappingContext = new SimpleMappingContext(DefaultMapperContext.RuleSets.CreateNew, DefaultMapperContext); var rootMappingData = ObjectMappingDataFactory.ForRoot(source, target, mappingContext); @@ -23,11 +49,11 @@ public void ShouldNotMatchSameNameIncompatibleTypeProperties() var childMappingContext = rootMappingData.GetChildMappingData(childMapperData); var matchingSourceMember = SourceMemberMatcher.GetMatchFor(childMappingContext); - - matchingSourceMember.ShouldNotBeNull(); - matchingSourceMember.Name.ShouldBe("value"); + return matchingSourceMember; } + #region Helper Classes + private class TwoValues { // ReSharper disable once InconsistentNaming @@ -42,5 +68,16 @@ public TwoValues() // ReSharper disable once UnusedAutoPropertyAccessor.Local public int[] Value { get; set; } } + + private abstract class Base + { + public virtual int Value { get; set; } + } + + private class Derived : Base + { + } + + #endregion } } \ No newline at end of file From e6f33c9691f96c9106a583e7c7e75bb09ec9b38a Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 15 Dec 2017 19:50:23 +0000 Subject: [PATCH 34/74] Selecting flattened source members from parent contexts to map to nested dynamics (and dictionaries) --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenIgnoringMembersByFilter.cs | 4 +- .../WhenIgnoringMembersByGlobalFilter.cs | 5 +- .../WhenMappingToNewDynamicMembers.cs | 29 +++++ AgileMapper/Extensions/StringExtensions.cs | 11 +- AgileMapper/Members/MemberExtensions.cs | 6 +- AgileMapper/Members/MemberFinder.cs | 4 +- AgileMapper/Members/SourceMemberMatcher.cs | 22 ++-- .../DictionaryMappingExpressionFactory.cs | 123 ++++++++++++++++-- 9 files changed, 173 insertions(+), 32 deletions(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 31eb2231a..c371f08af 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -112,6 +112,7 @@ + diff --git a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs index 5ed307c0a..54fb25960 100644 --- a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs +++ b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { using System; + using AgileMapper.Extensions; using TestClasses; using Xunit; @@ -331,8 +332,7 @@ public void ShouldIgnoreMembersBySourceTypeTargetTypeAndPathMatch() mapper.WhenMapping .From>() .To>() - .IgnoreTargetMembersWhere(member => - member.Path.Equals("Value.Line2", StringComparison.OrdinalIgnoreCase)); + .IgnoreTargetMembersWhere(member => member.Path.EqualsIgnoreCase("Value.Line2")); var matchingSource = new PublicField
{ Value = new Address { Line1 = "Here", Line2 = "Here!" } }; var nonMatchingSource = new { Value = new Address { Line1 = "There", Line2 = "There!" } }; diff --git a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs index 802192323..74a37781c 100644 --- a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs +++ b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { using System; - using System.Linq; + using AgileMapper.Extensions; using Shouldly; using TestClasses; using Xunit; @@ -209,8 +209,7 @@ public void ShouldIgnoreMembersByPathMatch() var source = new { Address = new Address { Line1 = "ONE!", Line2 = "TWO!" } }; mapper.WhenMapping - .IgnoreTargetMembersWhere(member => - member.Path.Equals("Value.Line1", StringComparison.OrdinalIgnoreCase)); + .IgnoreTargetMembersWhere(member => member.Path.EqualsIgnoreCase("Value.Line1")); mapper.WhenMapping .From(source) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs new file mode 100644 index 000000000..0b35bc76c --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs @@ -0,0 +1,29 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingToNewDynamicMembers + { + [Fact] + public void ShouldMapFromAFlattenedMember() + { + var source = new + { + ValueLine1 = "Over here!", + Value = new { Line2 = "Over there!" }, + Va = new { Lu = new { E = new { Line3 = "Over where?!" } } } + }; + + var result = Mapper.Map(source).ToANew>(); + + ((object)result.Value).ShouldNotBeNull(); + dynamic resultDynamic = result.Value; + resultDynamic.Line1.ShouldBe("Over here!"); + resultDynamic.Line2.ShouldBe("Over there!"); + resultDynamic.Line3.ShouldBe("Over where?!"); + } + } +} diff --git a/AgileMapper/Extensions/StringExtensions.cs b/AgileMapper/Extensions/StringExtensions.cs index be0ab8a63..a29869591 100644 --- a/AgileMapper/Extensions/StringExtensions.cs +++ b/AgileMapper/Extensions/StringExtensions.cs @@ -20,6 +20,12 @@ public static string FirstOrDefault(this string value) return value[0].ToString(); } + public static bool EqualsIgnoreCase(this string value, string otherValue) + => value.Equals(otherValue, StringComparison.OrdinalIgnoreCase); + + public static bool StartsWithIgnoreCase(this string value, string substring) + => value.StartsWith(substring, StringComparison.OrdinalIgnoreCase); + public static bool MatchesKey(this string subjectKey, string queryKey) { if (queryKey == null) @@ -31,13 +37,12 @@ public static bool MatchesKey(this string subjectKey, string queryKey) return false; } - if (subjectKey.Equals(queryKey, StringComparison.OrdinalIgnoreCase)) + if (subjectKey.EqualsIgnoreCase(queryKey)) { return true; } - return (queryKey.IndexOf('.') != -1) && - subjectKey.Equals(queryKey.Replace(".", null), StringComparison.OrdinalIgnoreCase); + return (queryKey.IndexOf('.') != -1) && subjectKey.EqualsIgnoreCase(queryKey.Replace(".", null)); } } } \ No newline at end of file diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 77c709f41..22024ddf1 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -141,7 +141,7 @@ public static bool CouldMatch(this ICollection memberNames, ICollection< return otherMemberNames .Any(otherJoinedName => (otherJoinedName == Constants.RootMemberName) || memberNames - .Any(joinedName => (joinedName == Constants.RootMemberName) || otherJoinedName.StartsWith(joinedName, OrdinalIgnoreCase))); + .Any(joinedName => (joinedName == Constants.RootMemberName) || otherJoinedName.StartsWithIgnoreCase(joinedName))); } public static bool Match(this ICollection memberNames, ICollection otherMemberNames) @@ -156,8 +156,8 @@ public static bool Match(this ICollection memberNames, ICollection otherMemberName.Equals(memberName, OrdinalIgnoreCase)); + ? memberName.EqualsIgnoreCase(otherMemberNames.First()) + : otherMemberNames.Any(otherMemberName => otherMemberName.EqualsIgnoreCase(memberName)); } public static TMember GetElementMember(this TMember enumerableMember) diff --git a/AgileMapper/Members/MemberFinder.cs b/AgileMapper/Members/MemberFinder.cs index a39107f11..93331c65e 100644 --- a/AgileMapper/Members/MemberFinder.cs +++ b/AgileMapper/Members/MemberFinder.cs @@ -105,7 +105,7 @@ private static IEnumerable GetMethods( private static bool OnlyRelevantCallable(MethodBase method) { return !method.IsSpecialName && - method.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && + method.Name.StartsWithIgnoreCase("Get") && (Array.IndexOf(_methodsToIgnore, method.Name) == -1) && method.GetParameters().None(); } @@ -113,7 +113,7 @@ private static bool OnlyRelevantCallable(MethodBase method) private static bool OnlyCallableSetters(MethodInfo method) { return !method.IsSpecialName && - method.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && + method.Name.StartsWithIgnoreCase("Set") && method.GetParameters().HasOne(); } diff --git a/AgileMapper/Members/SourceMemberMatcher.cs b/AgileMapper/Members/SourceMemberMatcher.cs index e992f8017..733e6a0b8 100644 --- a/AgileMapper/Members/SourceMemberMatcher.cs +++ b/AgileMapper/Members/SourceMemberMatcher.cs @@ -26,17 +26,6 @@ public static IQualifiedMember GetMatchFor(IChildMemberMappingData targetData) return GetFinalSourceMember(matchingMember, targetData); } - private static IQualifiedMember GetFinalSourceMember( - IQualifiedMember sourceMember, - IChildMemberMappingData targetData) - { - return targetData - .MapperData - .MapperContext - .QualifiedMemberFactory - .GetFinalSourceMember(sourceMember, targetData.MapperData.TargetMember); - } - private static bool ExactMatchingSourceMemberExists( IQualifiedMember parentSourceMember, IChildMemberMappingData targetData, @@ -70,6 +59,17 @@ private static IEnumerable QuerySourceMembers( .Where(filter); } + private static IQualifiedMember GetFinalSourceMember( + IQualifiedMember sourceMember, + IChildMemberMappingData targetData) + { + return targetData + .MapperData + .MapperContext + .QualifiedMemberFactory + .GetFinalSourceMember(sourceMember, targetData.MapperData.TargetMember); + } + private static IEnumerable EnumerateSourceMembers( IQualifiedMember parentMember, IChildMemberMappingData rootData) diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index cc07fb4c5..6c20d3a2b 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -29,7 +29,7 @@ private DictionaryMappingExpressionFactory() private static IEnumerable GetAllTargetMembers(ObjectMapperData mapperData) { - var targetMembersFromSource = EnumerateTargetMembers(mapperData).ToArray(); + var targetMembersFromSource = EnumerateAllTargetMembers(mapperData).ToArray(); var configuredDataSourceFactories = mapperData.MapperContext .UserConfigurations @@ -50,29 +50,136 @@ private static IEnumerable GetAllTargetMembers(ObjectMapperData return allTargetMembers; } - private static IEnumerable EnumerateTargetMembers(ObjectMapperData mapperData) + private static IEnumerable EnumerateAllTargetMembers(ObjectMapperData mapperData) { - var targetDictionaryMember = (DictionaryTargetMember)mapperData.TargetMember; var sourceMembers = GlobalContext.Instance.MemberCache.GetSourceMembers(mapperData.SourceType); + var targetDictionaryMember = (DictionaryTargetMember)mapperData.TargetMember; + + var targetMembers = EnumerateTargetMembers( + sourceMembers, + targetDictionaryMember, + mapperData, + m => m.Name); + + foreach (var targetMember in targetMembers) + { + yield return targetMember; + } + + foreach (var targetMember in GetParentContextFlattenedTargetMembers(mapperData, targetDictionaryMember)) + { + yield return targetMember; + } + } + + private static IEnumerable GetParentContextFlattenedTargetMembers( + ObjectMapperData mapperData, + DictionaryTargetMember targetDictionaryMember) + { + while (mapperData.Parent != null) + { + mapperData = mapperData.Parent; + + var sourceMembers = GlobalContext.Instance + .MemberCache + .GetSourceMembers(mapperData.SourceType) + .SelectMany(sm => MatchingFlattenedMembers(sm, targetDictionaryMember)) + .ToArray(); + + var targetMembers = EnumerateTargetMembers( + sourceMembers, + targetDictionaryMember, + mapperData, + m => m.Name.StartsWithIgnoreCase(targetDictionaryMember.Name) + ? m.Name.Substring(targetDictionaryMember.Name.Length) + : m.Name); + + foreach (var targetMember in targetMembers) + { + yield return targetMember; + } + } + } + + private static IEnumerable MatchingFlattenedMembers(Member sourceMember, IQualifiedMember targetDictionaryMember) + { + if (sourceMember.Name.EqualsIgnoreCase(targetDictionaryMember.Name)) + { + return Enumerable.Empty; + } + + if (sourceMember.Name.StartsWithIgnoreCase(targetDictionaryMember.Name)) + { + // e.g. ValueLine1 -> Value + return new[] { sourceMember }; + } + + if (!targetDictionaryMember.Name.StartsWithIgnoreCase(sourceMember.Name)) + { + return Enumerable.Empty; + } + // e.g. Val => Value + return GetNestedFlattenedMembers(sourceMember, sourceMember.Name, targetDictionaryMember.Name); + } + + private static IEnumerable GetNestedFlattenedMembers( + Member parentMember, + string sourceMemberNameMatchSoFar, + string targetMemberName) + { + return GlobalContext.Instance + .MemberCache + .GetSourceMembers(parentMember.Type) + .SelectMany(sm => + { + var flattenedSourceMemberName = sourceMemberNameMatchSoFar + sm.Name; + + if (!targetMemberName.StartsWithIgnoreCase(flattenedSourceMemberName)) + { + return Enumerable.Empty; + } + + if (targetMemberName.EqualsIgnoreCase(flattenedSourceMemberName)) + { + return GlobalContext.Instance + .MemberCache + .GetSourceMembers(sm.Type); + } + + return GetNestedFlattenedMembers( + sm, + flattenedSourceMemberName, + targetMemberName); + }) + .ToArray(); + } + + private static IEnumerable EnumerateTargetMembers( + IEnumerable sourceMembers, + DictionaryTargetMember targetDictionaryMember, + ObjectMapperData mapperData, + Func targetMemberNameFactory) + { foreach (var sourceMember in sourceMembers) { - var entryTargetMember = targetDictionaryMember.Append(sourceMember.DeclaringType, sourceMember.Name); + var targetEntryMemberName = targetMemberNameFactory.Invoke(sourceMember); + var targetEntryMember = targetDictionaryMember.Append(sourceMember.DeclaringType, targetEntryMemberName); - var entryMapperData = new ChildMemberMapperData(entryTargetMember, mapperData); + var entryMapperData = new ChildMemberMapperData(targetEntryMember, mapperData); var configuredKey = GetCustomKeyOrNull(entryMapperData); if (configuredKey != null) { - entryTargetMember.SetCustomKey(configuredKey); + targetEntryMember.SetCustomKey(configuredKey); } if (!sourceMember.IsSimple) { - entryTargetMember = entryTargetMember.WithTypeOf(sourceMember); + targetEntryMember = targetEntryMember.WithTypeOf(sourceMember); } - yield return entryTargetMember; + yield return targetEntryMember; } } From 1cbb5034390c872fd1e1fb2977d97a7436f5fbde Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 15 Dec 2017 21:36:45 +0000 Subject: [PATCH 35/74] Support for mapping flattened members from parent contexts to nested dynamics (and dictionaries) --- .../WhenMappingToNewDynamicMembers.cs | 6 ++-- .../Members/WhenFindingDataSources.cs | 2 +- AgileMapper/DataSources/DataSourceFinder.cs | 4 +-- AgileMapper/Members/SourceMemberMatcher.cs | 33 ++++++++++++++++--- .../ChildMemberMappingData.cs | 5 +-- .../ObjectPopulation/ObjectMappingData.cs | 13 +------- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs index 0b35bc76c..38e58c50f 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs @@ -21,9 +21,9 @@ public void ShouldMapFromAFlattenedMember() ((object)result.Value).ShouldNotBeNull(); dynamic resultDynamic = result.Value; - resultDynamic.Line1.ShouldBe("Over here!"); - resultDynamic.Line2.ShouldBe("Over there!"); - resultDynamic.Line3.ShouldBe("Over where?!"); + ((string)resultDynamic.Line1).ShouldBe("Over here!"); + ((string)resultDynamic.Line2).ShouldBe("Over there!"); + ((string)resultDynamic.Line3).ShouldBe("Over where?!"); } } } diff --git a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs index c85c3ba1b..4b9fb5ac0 100644 --- a/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs +++ b/AgileMapper.UnitTests/Members/WhenFindingDataSources.cs @@ -48,7 +48,7 @@ private IQualifiedMember GetMatchingSourceMember( var childMapperData = new ChildMemberMapperData(targetMember, rootMapperData); var childMappingContext = rootMappingData.GetChildMappingData(childMapperData); - var matchingSourceMember = SourceMemberMatcher.GetMatchFor(childMappingContext); + var matchingSourceMember = SourceMemberMatcher.GetMatchFor(childMappingContext, out var _); return matchingSourceMember; } diff --git a/AgileMapper/DataSources/DataSourceFinder.cs b/AgileMapper/DataSources/DataSourceFinder.cs index 1c2774a91..7e8e12aae 100644 --- a/AgileMapper/DataSources/DataSourceFinder.cs +++ b/AgileMapper/DataSources/DataSourceFinder.cs @@ -114,8 +114,8 @@ private static IEnumerable GetSourceMemberDataSources( yield break; } - var bestMatchingSourceMember = SourceMemberMatcher.GetMatchFor(mappingData); - var matchingSourceMemberDataSource = GetSourceMemberDataSourceOrNull(bestMatchingSourceMember, mappingData); + var bestMatchingSourceMember = SourceMemberMatcher.GetMatchFor(mappingData, out var contextMappingData); + var matchingSourceMemberDataSource = GetSourceMemberDataSourceOrNull(bestMatchingSourceMember, contextMappingData); if ((matchingSourceMemberDataSource == null) || configuredDataSources.Any(cds => cds.IsSameAs(matchingSourceMemberDataSource))) diff --git a/AgileMapper/Members/SourceMemberMatcher.cs b/AgileMapper/Members/SourceMemberMatcher.cs index 733e6a0b8..553ab4ea8 100644 --- a/AgileMapper/Members/SourceMemberMatcher.cs +++ b/AgileMapper/Members/SourceMemberMatcher.cs @@ -3,27 +3,52 @@ using System; using System.Collections.Generic; using System.Linq; + using ObjectPopulation; internal static class SourceMemberMatcher { - public static IQualifiedMember GetMatchFor(IChildMemberMappingData targetData) + public static IQualifiedMember GetMatchFor( + IChildMemberMappingData targetData, + out IChildMemberMappingData contextMappingData) { var parentSourceMember = targetData.MapperData.SourceMember; if (ExactMatchingSourceMemberExists(parentSourceMember, targetData, out var matchingMember)) { + contextMappingData = targetData; return GetFinalSourceMember(matchingMember, targetData); } matchingMember = EnumerateSourceMembers(parentSourceMember, targetData) .FirstOrDefault(sm => IsMatchingMember(sm, targetData.MapperData)); - if (matchingMember == null) + if (matchingMember != null) { - return null; + contextMappingData = targetData; + return GetFinalSourceMember(matchingMember, targetData); + } + + var mappingData = targetData.Parent; + + while (mappingData.Parent != null) + { + mappingData = mappingData.Parent; + + matchingMember = EnumerateSourceMembers(mappingData.MapperData.SourceMember, targetData) + .FirstOrDefault(sm => IsMatchingMember(sm, targetData.MapperData)); + + if (matchingMember == null) + { + continue; + } + + var childMapperData = new ChildMemberMapperData(targetData.MapperData.TargetMember, mappingData.MapperData); + contextMappingData = mappingData.GetChildMappingData(childMapperData); + return GetFinalSourceMember(matchingMember, targetData); } - return GetFinalSourceMember(matchingMember, targetData); + contextMappingData = null; + return null; } private static bool ExactMatchingSourceMemberExists( diff --git a/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs b/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs index 6a84f8e93..371a452a6 100644 --- a/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs +++ b/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs @@ -12,17 +12,18 @@ internal class ChildMemberMappingData : IChildMemberMappingDat private readonly ObjectMappingData _parent; private readonly ICache> _runtimeTypeGettersCache; - public ChildMemberMappingData(ObjectMappingData parent) + public ChildMemberMappingData(ObjectMappingData parent, IMemberMapperData mapperData) { _parent = parent; _runtimeTypeGettersCache = parent.MapperContext.Cache.CreateScoped>(); + MapperData = mapperData; } public MappingRuleSet RuleSet => _parent.MappingContext.RuleSet; public IObjectMappingData Parent => _parent; - public IMemberMapperData MapperData { get; internal set; } + public IMemberMapperData MapperData { get; } public Type GetSourceMemberRuntimeType(IQualifiedMember sourceMember) { diff --git a/AgileMapper/ObjectPopulation/ObjectMappingData.cs b/AgileMapper/ObjectPopulation/ObjectMappingData.cs index 85eb6afe4..97c52c8a6 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingData.cs @@ -98,19 +98,8 @@ public ObjectMapperData MapperData private Dictionary> MappedObjectsBySource => _mappedObjectsBySource ?? (_mappedObjectsBySource = new Dictionary>(13)); - private ChildMemberMappingData _childMappingData; - IChildMemberMappingData IObjectMappingData.GetChildMappingData(IMemberMapperData childMapperData) - { - if (_childMappingData == null) - { - _childMappingData = new ChildMemberMappingData(this); - } - - _childMappingData.MapperData = childMapperData; - - return _childMappingData; - } + => new ChildMemberMappingData(this, childMapperData); #endregion From 60c19ec815009d653515b5b4195a9db297c884cb Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 07:46:37 +0000 Subject: [PATCH 36/74] Fixing broken tests / Moving internal extension methods into dedicated namespace --- .../WhenConfiguringNameMatching.cs | 2 +- .../WhenIgnoringMembersByFilter.cs | 2 +- .../WhenIgnoringMembersByGlobalFilter.cs | 2 +- .../WhenMappingOverDictionaries.cs | 2 +- .../Extensions/WhenEquatingExpressions.cs | 2 +- .../Extensions/WhenGeneratingVariableNames.cs | 2 +- .../Members/MemberTestsBase.cs | 2 +- .../WhenAccessingTypeInformation.cs | 2 +- .../WhenConfiguringStructDataSources.cs | 2 +- .../WhenConfiguringStructMappingCallbacks.cs | 2 +- .../WhenAnalysingCollections.cs | 2 +- .../WhenMappingCircularReferences.cs | 2 +- .../WhenUsingPartialTrust.cs | 5 +-- .../CustomDataSourceTargetMemberSpecifier.cs | 2 +- .../Api/Configuration/EnumPairSpecifier.cs | 2 +- .../MappingConfigStartingPoint.cs | 2 +- .../Api/Configuration/MappingConfigurator.cs | 2 +- .../Configuration/ConfiguredLambdaInfo.cs | 2 +- .../Configuration/CustomDictionaryKey.cs | 2 +- .../Configuration/DerivedTypePairSet.cs | 2 +- .../Configuration/DictionarySettings.cs | 2 +- .../Configuration/ElementKeyPartFactory.cs | 2 +- .../Configuration/EnumComparisonFixer.cs | 2 +- AgileMapper/Configuration/EnumMemberPair.cs | 2 +- .../Configuration/Inline/InlineMapperKey.cs | 2 +- .../Configuration/JoiningNameFactory.cs | 2 +- .../Configuration/MappingConfigInfo.cs | 2 +- .../Configuration/ParametersSwapper.cs | 2 +- .../Configuration/PotentialCloneExtensions.cs | 2 +- .../Configuration/UserConfigurationSet.cs | 2 +- AgileMapper/Constants.cs | 2 +- .../DataSources/ConfiguredDataSource.cs | 2 +- AgileMapper/DataSources/DataSourceBase.cs | 2 +- AgileMapper/DataSources/DataSourceFinder.cs | 2 +- AgileMapper/DataSources/DataSourceSet.cs | 2 +- .../DictionaryDataSourceFactory.cs | 2 +- .../DataSources/DictionaryEntryDataSource.cs | 2 +- .../DictionaryEntryVariablePair.cs | 2 +- AgileMapper/DataSources/IDataSource.cs | 2 +- .../DataSources/SourceMemberDataSource.cs | 2 +- AgileMapper/DerivedTypesCache.cs | 2 +- .../{ => Internal}/CollectionData.cs | 2 +- .../{ => Internal}/EnumerableExtensions.cs | 2 +- .../{ => Internal}/ExpressionEquator.cs | 2 +- .../ExpressionExtensions.Replace.cs | 2 +- .../{ => Internal}/ExpressionExtensions.cs | 2 +- .../ExpressionReplacementDictionary.cs | 2 +- .../{ => Internal}/IConditionallyChainable.cs | 2 +- .../Extensions/Internal/ObjectExtensions.cs | 31 +++++++++++++++++++ .../{ => Internal}/ReflectionExtensions.cs | 2 +- .../StringExpressionExtensions.cs | 2 +- .../{ => Internal}/StringExtensions.cs | 2 +- .../Extensions/{ => Internal}/TrustTester.cs | 2 +- .../{ => Internal}/TypeExtensions.cs | 2 +- .../Extensions/{ => Internal}/TypeInfo.cs | 2 +- AgileMapper/Extensions/ObjectExtensions.cs | 23 -------------- AgileMapper/Flattening/ObjectFlattener.cs | 2 +- AgileMapper/MappingExecutor.cs | 2 +- AgileMapper/MappingRuleSetCollection.cs | 2 +- AgileMapper/Members/ConfiguredSourceMember.cs | 2 +- .../DictionaryEntrySourceMember.cs | 2 +- .../DictionaryMemberMapperDataExtensions.cs | 2 +- .../Dictionaries/DictionarySourceMember.cs | 2 +- .../Dictionaries/DictionaryTargetMember.cs | 2 +- AgileMapper/Members/ExpressionInfoFinder.cs | 2 +- AgileMapper/Members/MappingTypes.cs | 2 +- AgileMapper/Members/Member.cs | 2 +- AgileMapper/Members/MemberExtensions.cs | 2 +- AgileMapper/Members/MemberFinder.cs | 2 +- .../Members/MemberMapperDataExtensions.cs | 2 +- AgileMapper/Members/NamingSettings.cs | 2 +- .../Members/Population/MemberPopulation.cs | 2 +- AgileMapper/Members/QualifiedMember.cs | 2 +- AgileMapper/Members/QualifiedMemberFactory.cs | 2 +- AgileMapper/Members/SourceMemberMatcher.cs | 14 ++++++--- .../ChildMemberMappingData.cs | 2 +- .../ComplexTypeConstructionFactory.cs | 2 +- .../ComplexTypeMappingExpressionFactory.cs | 2 +- .../PopulationExpressionFactoryBase.cs | 2 +- .../SourceDictionaryShortCircuitFactory.cs | 2 +- .../StructPopulationExpressionFactory.cs | 2 +- .../TargetObjectResolutionFactory.cs | 2 +- .../DefaultValueDataSourceFactory.cs | 2 +- .../DerivedComplexTypeMappingsFactory.cs | 2 +- .../ObjectPopulation/DerivedMappingFactory.cs | 2 +- .../DerivedSourceTypeCheck.cs | 2 +- .../DictionaryMappingExpressionFactory.cs | 2 +- .../DictionaryPopulationBuilder.cs | 2 +- .../SourceElementsDictionaryAdapter.cs | 2 +- ...rceElementsDictionaryPopulationLoopData.cs | 2 +- .../SourceInstanceDictionaryAdapter.cs | 2 +- .../SourceObjectDictionaryAdapter.cs | 2 +- ...ourceObjectDictionaryPopulationLoopData.cs | 2 +- .../EnumerablePopulationBuilder.cs | 2 +- .../EnumerablePopulationContext.cs | 2 +- .../EnumerableSourcePopulationLoopData.cs | 2 +- .../Enumerables/EnumerableTypeHelper.cs | 2 +- .../Enumerables/IPopulationLoopData.cs | 2 +- .../IndexedSourcePopulationLoopData.cs | 2 +- .../Enumerables/ReadOnlyCollectionWrapper.cs | 2 +- ...ExistingOrDefaultValueDataSourceFactory.cs | 2 +- .../ObjectPopulation/MapperDataContext.cs | 2 +- .../MappingDataCreationFactory.cs | 2 +- .../ObjectPopulation/MappingDataFactory.cs | 2 +- .../MappingExpressionFactoryBase.cs | 2 +- .../ObjectPopulation/MappingFactory.cs | 2 +- .../ObjectCreationCallbackFactory.cs | 2 +- .../ObjectPopulation/ObjectMapperData.cs | 2 +- .../ObjectPopulation/ObjectMapperFactory.cs | 2 +- .../ObjectMappingDataFactory.cs | 2 +- .../SimpleTypeMappingExpressionFactory.cs | 2 +- .../SourceMemberTypeDependentKeyBase.cs | 2 +- AgileMapper/Parameters.cs | 2 +- AgileMapper/TypeConversion/ConverterSet.cs | 2 +- .../FallbackNonSimpleTypeValueConverter.cs | 2 +- .../TypeConversion/NumericConversions.cs | 2 +- .../NumericValueIsInRangeComparison.cs | 2 +- AgileMapper/TypeConversion/ToBoolConverter.cs | 2 +- .../TypeConversion/ToCharacterConverter.cs | 2 +- AgileMapper/TypeConversion/ToEnumConverter.cs | 2 +- .../ToFormattedStringConverter.cs | 2 +- .../TypeConversion/ToNumericConverter.cs | 2 +- .../TypeConversion/ToNumericConverterBase.cs | 2 +- .../TypeConversion/ToStringConverter.cs | 2 +- .../TypeConversion/TryParseConverterBase.cs | 2 +- .../Validation/EnumMappingMismatchFinder.cs | 2 +- .../Validation/EnumMappingMismatchSet.cs | 2 +- AgileMapper/Validation/MappingValidator.cs | 2 +- 128 files changed, 166 insertions(+), 155 deletions(-) rename AgileMapper/Extensions/{ => Internal}/CollectionData.cs (98%) rename AgileMapper/Extensions/{ => Internal}/EnumerableExtensions.cs (99%) rename AgileMapper/Extensions/{ => Internal}/ExpressionEquator.cs (99%) rename AgileMapper/Extensions/{ => Internal}/ExpressionExtensions.Replace.cs (99%) rename AgileMapper/Extensions/{ => Internal}/ExpressionExtensions.cs (99%) rename AgileMapper/Extensions/{ => Internal}/ExpressionReplacementDictionary.cs (85%) rename AgileMapper/Extensions/{ => Internal}/IConditionallyChainable.cs (78%) create mode 100644 AgileMapper/Extensions/Internal/ObjectExtensions.cs rename AgileMapper/Extensions/{ => Internal}/ReflectionExtensions.cs (94%) rename AgileMapper/Extensions/{ => Internal}/StringExpressionExtensions.cs (98%) rename AgileMapper/Extensions/{ => Internal}/StringExtensions.cs (96%) rename AgileMapper/Extensions/{ => Internal}/TrustTester.cs (73%) rename AgileMapper/Extensions/{ => Internal}/TypeExtensions.cs (99%) rename AgileMapper/Extensions/{ => Internal}/TypeInfo.cs (78%) delete mode 100644 AgileMapper/Extensions/ObjectExtensions.cs diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringNameMatching.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringNameMatching.cs index aeab6c395..286193cde 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringNameMatching.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringNameMatching.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using AgileMapper.Configuration; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs index 54fb25960..ad98fa64e 100644 --- a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs +++ b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByFilter.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { using System; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs index 74a37781c..b0936b8e0 100644 --- a/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs +++ b/AgileMapper.UnitTests/Configuration/WhenIgnoringMembersByGlobalFilter.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { using System; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaries.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaries.cs index 5c5d76889..2072b35af 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaries.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaries.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Extensions/WhenEquatingExpressions.cs b/AgileMapper.UnitTests/Extensions/WhenEquatingExpressions.cs index 14fda35d9..40305c0f0 100644 --- a/AgileMapper.UnitTests/Extensions/WhenEquatingExpressions.cs +++ b/AgileMapper.UnitTests/Extensions/WhenEquatingExpressions.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Extensions/WhenGeneratingVariableNames.cs b/AgileMapper.UnitTests/Extensions/WhenGeneratingVariableNames.cs index cd2543c3e..be8f2bffe 100644 --- a/AgileMapper.UnitTests/Extensions/WhenGeneratingVariableNames.cs +++ b/AgileMapper.UnitTests/Extensions/WhenGeneratingVariableNames.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Extensions { using System.Collections.Generic; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Members/MemberTestsBase.cs b/AgileMapper.UnitTests/Members/MemberTestsBase.cs index b726ebd3a..7e0c0dc61 100644 --- a/AgileMapper.UnitTests/Members/MemberTestsBase.cs +++ b/AgileMapper.UnitTests/Members/MemberTestsBase.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Linq.Expressions; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using AgileMapper.Members; using NetStandardPolyfills; diff --git a/AgileMapper.UnitTests/Reflection/WhenAccessingTypeInformation.cs b/AgileMapper.UnitTests/Reflection/WhenAccessingTypeInformation.cs index 0b25c2a51..1c02983f0 100644 --- a/AgileMapper.UnitTests/Reflection/WhenAccessingTypeInformation.cs +++ b/AgileMapper.UnitTests/Reflection/WhenAccessingTypeInformation.cs @@ -3,7 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructDataSources.cs b/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructDataSources.cs index a55acb152..b0e03065c 100644 --- a/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructDataSources.cs +++ b/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructDataSources.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.UnitTests.Structs.Configuration { using System; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructMappingCallbacks.cs b/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructMappingCallbacks.cs index 1f7133be2..893669441 100644 --- a/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructMappingCallbacks.cs +++ b/AgileMapper.UnitTests/Structs/Configuration/WhenConfiguringStructMappingCallbacks.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using AgileMapper.Configuration; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/WhenAnalysingCollections.cs b/AgileMapper.UnitTests/WhenAnalysingCollections.cs index f97b325e1..ed82ec355 100644 --- a/AgileMapper.UnitTests/WhenAnalysingCollections.cs +++ b/AgileMapper.UnitTests/WhenAnalysingCollections.cs @@ -2,7 +2,7 @@ { using System; using System.Linq; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/WhenMappingCircularReferences.cs b/AgileMapper.UnitTests/WhenMappingCircularReferences.cs index 72c885e67..dbd4b3c7e 100644 --- a/AgileMapper.UnitTests/WhenMappingCircularReferences.cs +++ b/AgileMapper.UnitTests/WhenMappingCircularReferences.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - using AgileMapper.Extensions; + using AgileMapper.Extensions.Internal; using Shouldly; using TestClasses; using Xunit; diff --git a/AgileMapper.UnitTests/WhenUsingPartialTrust.cs b/AgileMapper.UnitTests/WhenUsingPartialTrust.cs index cb428ca80..e8bcb1132 100644 --- a/AgileMapper.UnitTests/WhenUsingPartialTrust.cs +++ b/AgileMapper.UnitTests/WhenUsingPartialTrust.cs @@ -53,10 +53,7 @@ public void ShouldHandleAMaptimeException() [Fact] public void ShouldCreateAMappingPlan() { - ExecuteInPartialTrust(helper => - { - helper.TestMappingPlan(); - }); + ExecuteInPartialTrust(helper => helper.TestMappingPlan()); } private static void ExecuteInPartialTrust( diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 2e2f7ae41..5148117ed 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -7,7 +7,7 @@ using System.Reflection; using AgileMapper.Configuration; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; using NetStandardPolyfills; diff --git a/AgileMapper/Api/Configuration/EnumPairSpecifier.cs b/AgileMapper/Api/Configuration/EnumPairSpecifier.cs index 354554185..9e8af13bc 100644 --- a/AgileMapper/Api/Configuration/EnumPairSpecifier.cs +++ b/AgileMapper/Api/Configuration/EnumPairSpecifier.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Linq; using AgileMapper.Configuration; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index 1112b6cbb..8b0eb0d6c 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -6,7 +6,7 @@ using System.Reflection; using AgileMapper.Configuration; using Dictionaries; - using Extensions; + using Extensions.Internal; using Members; /// diff --git a/AgileMapper/Api/Configuration/MappingConfigurator.cs b/AgileMapper/Api/Configuration/MappingConfigurator.cs index 68af19692..c502b6811 100644 --- a/AgileMapper/Api/Configuration/MappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/MappingConfigurator.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; using System.Reflection; using AgileMapper.Configuration; - using Extensions; + using Extensions.Internal; using Members; using Validation; diff --git a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs index 3da954940..0db220cdb 100644 --- a/AgileMapper/Configuration/ConfiguredLambdaInfo.cs +++ b/AgileMapper/Configuration/ConfiguredLambdaInfo.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; using ObjectPopulation; diff --git a/AgileMapper/Configuration/CustomDictionaryKey.cs b/AgileMapper/Configuration/CustomDictionaryKey.cs index 8446d2efa..d0e2c6143 100644 --- a/AgileMapper/Configuration/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/CustomDictionaryKey.cs @@ -5,7 +5,7 @@ using System.Linq.Expressions; using Api.Configuration.Dictionaries; using DataSources; - using Extensions; + using Extensions.Internal; using Members; internal class CustomDictionaryKey : UserConfiguredItemBase diff --git a/AgileMapper/Configuration/DerivedTypePairSet.cs b/AgileMapper/Configuration/DerivedTypePairSet.cs index 14fa48708..98e61552e 100644 --- a/AgileMapper/Configuration/DerivedTypePairSet.cs +++ b/AgileMapper/Configuration/DerivedTypePairSet.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/DictionarySettings.cs index b483525cc..f8824cab6 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/DictionarySettings.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal class DictionarySettings diff --git a/AgileMapper/Configuration/ElementKeyPartFactory.cs b/AgileMapper/Configuration/ElementKeyPartFactory.cs index 005ff98b7..b7eb7fe05 100644 --- a/AgileMapper/Configuration/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/ElementKeyPartFactory.cs @@ -4,7 +4,7 @@ using System.Dynamic; using System.Linq.Expressions; using System.Text.RegularExpressions; - using Extensions; + using Extensions.Internal; using Members; internal class ElementKeyPartFactory : UserConfiguredItemBase diff --git a/AgileMapper/Configuration/EnumComparisonFixer.cs b/AgileMapper/Configuration/EnumComparisonFixer.cs index c905f46e2..988102f22 100644 --- a/AgileMapper/Configuration/EnumComparisonFixer.cs +++ b/AgileMapper/Configuration/EnumComparisonFixer.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class EnumComparisonFixer : ExpressionVisitor diff --git a/AgileMapper/Configuration/EnumMemberPair.cs b/AgileMapper/Configuration/EnumMemberPair.cs index 2b17f0a1d..1e7e58403 100644 --- a/AgileMapper/Configuration/EnumMemberPair.cs +++ b/AgileMapper/Configuration/EnumMemberPair.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using TypeConversion; internal class EnumMemberPair diff --git a/AgileMapper/Configuration/Inline/InlineMapperKey.cs b/AgileMapper/Configuration/Inline/InlineMapperKey.cs index 258d43400..0a43b4de6 100644 --- a/AgileMapper/Configuration/Inline/InlineMapperKey.cs +++ b/AgileMapper/Configuration/Inline/InlineMapperKey.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.Configuration.Inline using System.Collections.Generic; using System.Linq.Expressions; using Api.Configuration; - using Extensions; + using Extensions.Internal; using Members; internal class InlineMapperKey : IInlineMapperKey diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/JoiningNameFactory.cs index 358d6b0b1..843d0bbe0 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/JoiningNameFactory.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index 4d058717a..eccc4ea1e 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using ObjectPopulation; using ReadableExpressions; diff --git a/AgileMapper/Configuration/ParametersSwapper.cs b/AgileMapper/Configuration/ParametersSwapper.cs index d23c75262..f2104f331 100644 --- a/AgileMapper/Configuration/ParametersSwapper.cs +++ b/AgileMapper/Configuration/ParametersSwapper.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.Configuration using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; using ObjectPopulation; diff --git a/AgileMapper/Configuration/PotentialCloneExtensions.cs b/AgileMapper/Configuration/PotentialCloneExtensions.cs index c5ba95e68..4d92597ef 100644 --- a/AgileMapper/Configuration/PotentialCloneExtensions.cs +++ b/AgileMapper/Configuration/PotentialCloneExtensions.cs @@ -2,7 +2,7 @@ { using System.Collections.Generic; using System.Linq; - using Extensions; + using Extensions.Internal; internal static class PotentialCloneExtensions { diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index 7f4190e70..b859f9da6 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using ObjectPopulation; diff --git a/AgileMapper/Constants.cs b/AgileMapper/Constants.cs index 7c740920d..aa623bcd6 100644 --- a/AgileMapper/Constants.cs +++ b/AgileMapper/Constants.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal static class Constants diff --git a/AgileMapper/DataSources/ConfiguredDataSource.cs b/AgileMapper/DataSources/ConfiguredDataSource.cs index 4ae4fd643..1a625c53f 100644 --- a/AgileMapper/DataSources/ConfiguredDataSource.cs +++ b/AgileMapper/DataSources/ConfiguredDataSource.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal class ConfiguredDataSource : DataSourceBase, IConfiguredDataSource diff --git a/AgileMapper/DataSources/DataSourceBase.cs b/AgileMapper/DataSources/DataSourceBase.cs index aebb8a1a9..daa258e25 100644 --- a/AgileMapper/DataSources/DataSourceBase.cs +++ b/AgileMapper/DataSources/DataSourceBase.cs @@ -2,7 +2,7 @@ { using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using ReadableExpressions.Extensions; diff --git a/AgileMapper/DataSources/DataSourceFinder.cs b/AgileMapper/DataSources/DataSourceFinder.cs index 7e8e12aae..69e215f58 100644 --- a/AgileMapper/DataSources/DataSourceFinder.cs +++ b/AgileMapper/DataSources/DataSourceFinder.cs @@ -2,7 +2,7 @@ { using System.Collections.Generic; using System.Linq; - using Extensions; + using Extensions.Internal; using Members; internal class DataSourceFinder diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index 65827053d..adb90fe0c 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.DataSources using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal class DataSourceSet : IEnumerable diff --git a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs index a3e5bb5f0..633af3b5a 100644 --- a/AgileMapper/DataSources/DictionaryDataSourceFactory.cs +++ b/AgileMapper/DataSources/DictionaryDataSourceFactory.cs @@ -2,7 +2,7 @@ { using System; using System.Collections.Generic; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; diff --git a/AgileMapper/DataSources/DictionaryEntryDataSource.cs b/AgileMapper/DataSources/DictionaryEntryDataSource.cs index d90d1f83d..fd796ed1a 100644 --- a/AgileMapper/DataSources/DictionaryEntryDataSource.cs +++ b/AgileMapper/DataSources/DictionaryEntryDataSource.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal class DictionaryEntryDataSource : DataSourceBase { diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index 432f19a79..a569b9eb7 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -5,7 +5,7 @@ namespace AgileObjects.AgileMapper.DataSources using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; using NetStandardPolyfills; diff --git a/AgileMapper/DataSources/IDataSource.cs b/AgileMapper/DataSources/IDataSource.cs index 436525359..49078e2ae 100644 --- a/AgileMapper/DataSources/IDataSource.cs +++ b/AgileMapper/DataSources/IDataSource.cs @@ -2,7 +2,7 @@ { using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal interface IDataSource : IConditionallyChainable diff --git a/AgileMapper/DataSources/SourceMemberDataSource.cs b/AgileMapper/DataSources/SourceMemberDataSource.cs index aa6191fea..67edd2784 100644 --- a/AgileMapper/DataSources/SourceMemberDataSource.cs +++ b/AgileMapper/DataSources/SourceMemberDataSource.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; #if NET_STANDARD #endif - using Extensions; + using Extensions.Internal; using Members; using ReadableExpressions.Extensions; diff --git a/AgileMapper/DerivedTypesCache.cs b/AgileMapper/DerivedTypesCache.cs index 14877a1df..1f5ba85e4 100644 --- a/AgileMapper/DerivedTypesCache.cs +++ b/AgileMapper/DerivedTypesCache.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Reflection; using Caching; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class DerivedTypesCache diff --git a/AgileMapper/Extensions/CollectionData.cs b/AgileMapper/Extensions/Internal/CollectionData.cs similarity index 98% rename from AgileMapper/Extensions/CollectionData.cs rename to AgileMapper/Extensions/Internal/CollectionData.cs index 1a8c54f40..6f9a9572c 100644 --- a/AgileMapper/Extensions/CollectionData.cs +++ b/AgileMapper/Extensions/Internal/CollectionData.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; using System.Collections.Generic; diff --git a/AgileMapper/Extensions/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs similarity index 99% rename from AgileMapper/Extensions/EnumerableExtensions.cs rename to AgileMapper/Extensions/Internal/EnumerableExtensions.cs index 93fdda177..5923fba15 100644 --- a/AgileMapper/Extensions/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; using System.Collections.Generic; diff --git a/AgileMapper/Extensions/ExpressionEquator.cs b/AgileMapper/Extensions/Internal/ExpressionEquator.cs similarity index 99% rename from AgileMapper/Extensions/ExpressionEquator.cs rename to AgileMapper/Extensions/Internal/ExpressionEquator.cs index 871b7afb9..16d9308b9 100644 --- a/AgileMapper/Extensions/ExpressionEquator.cs +++ b/AgileMapper/Extensions/Internal/ExpressionEquator.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; using System.Collections.Generic; diff --git a/AgileMapper/Extensions/ExpressionExtensions.Replace.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs similarity index 99% rename from AgileMapper/Extensions/ExpressionExtensions.Replace.cs rename to AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs index a741f058b..5d6f5d1cc 100644 --- a/AgileMapper/Extensions/ExpressionExtensions.Replace.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; using System.Collections.Generic; diff --git a/AgileMapper/Extensions/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs similarity index 99% rename from AgileMapper/Extensions/ExpressionExtensions.cs rename to AgileMapper/Extensions/Internal/ExpressionExtensions.cs index e281b4d64..243d57ed5 100644 --- a/AgileMapper/Extensions/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; using System.Collections.Generic; diff --git a/AgileMapper/Extensions/ExpressionReplacementDictionary.cs b/AgileMapper/Extensions/Internal/ExpressionReplacementDictionary.cs similarity index 85% rename from AgileMapper/Extensions/ExpressionReplacementDictionary.cs rename to AgileMapper/Extensions/Internal/ExpressionReplacementDictionary.cs index 1fc2d97b0..4de3cac55 100644 --- a/AgileMapper/Extensions/ExpressionReplacementDictionary.cs +++ b/AgileMapper/Extensions/Internal/ExpressionReplacementDictionary.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System.Collections.Generic; using System.Linq.Expressions; diff --git a/AgileMapper/Extensions/IConditionallyChainable.cs b/AgileMapper/Extensions/Internal/IConditionallyChainable.cs similarity index 78% rename from AgileMapper/Extensions/IConditionallyChainable.cs rename to AgileMapper/Extensions/Internal/IConditionallyChainable.cs index 5acead483..4560f3325 100644 --- a/AgileMapper/Extensions/IConditionallyChainable.cs +++ b/AgileMapper/Extensions/Internal/IConditionallyChainable.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System.Linq.Expressions; diff --git a/AgileMapper/Extensions/Internal/ObjectExtensions.cs b/AgileMapper/Extensions/Internal/ObjectExtensions.cs new file mode 100644 index 000000000..af8875ce2 --- /dev/null +++ b/AgileMapper/Extensions/Internal/ObjectExtensions.cs @@ -0,0 +1,31 @@ +namespace AgileObjects.AgileMapper.Extensions.Internal +{ + using System; + using System.Reflection; + using NetStandardPolyfills; + + /// + /// Provides extensions methods on the Object Type. This class is not intended + /// to be used from your code. + /// + public static class ObjectExtensions + { + internal static readonly MethodInfo GetRuntimeSourceTypeMethod = + typeof(ObjectExtensions).GetPublicStaticMethod("GetRuntimeSourceType"); + + /// + /// Gets the runtime type of the given object. + /// + /// The declared Type of the given object. + /// The source object for which to determine the runtime Type. + /// The runtime type of the given object. + public static Type GetRuntimeSourceType(this TDeclared source) + => source?.GetType() ?? typeof(TDeclared); + + internal static Type GetRuntimeTargetType(this TDeclared item, Type sourceType) + => (item != null) ? item.GetType() : typeof(TDeclared).GetRuntimeTargetType(sourceType); + + internal static Type GetRuntimeTargetType(this Type targetType, Type sourceType) + => sourceType.IsAssignableTo(targetType) ? sourceType : targetType; + } +} diff --git a/AgileMapper/Extensions/ReflectionExtensions.cs b/AgileMapper/Extensions/Internal/ReflectionExtensions.cs similarity index 94% rename from AgileMapper/Extensions/ReflectionExtensions.cs rename to AgileMapper/Extensions/Internal/ReflectionExtensions.cs index 93fad03b4..723699ea3 100644 --- a/AgileMapper/Extensions/ReflectionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ReflectionExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System.Reflection; using NetStandardPolyfills; diff --git a/AgileMapper/Extensions/StringExpressionExtensions.cs b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs similarity index 98% rename from AgileMapper/Extensions/StringExpressionExtensions.cs rename to AgileMapper/Extensions/Internal/StringExpressionExtensions.cs index 721557277..e3f1724d9 100644 --- a/AgileMapper/Extensions/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System.Collections.Generic; using System.Linq; diff --git a/AgileMapper/Extensions/StringExtensions.cs b/AgileMapper/Extensions/Internal/StringExtensions.cs similarity index 96% rename from AgileMapper/Extensions/StringExtensions.cs rename to AgileMapper/Extensions/Internal/StringExtensions.cs index a29869591..d34dcda73 100644 --- a/AgileMapper/Extensions/StringExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; diff --git a/AgileMapper/Extensions/TrustTester.cs b/AgileMapper/Extensions/Internal/TrustTester.cs similarity index 73% rename from AgileMapper/Extensions/TrustTester.cs rename to AgileMapper/Extensions/Internal/TrustTester.cs index 40d619b22..382a86388 100644 --- a/AgileMapper/Extensions/TrustTester.cs +++ b/AgileMapper/Extensions/Internal/TrustTester.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { internal static class TrustTester { diff --git a/AgileMapper/Extensions/TypeExtensions.cs b/AgileMapper/Extensions/Internal/TypeExtensions.cs similarity index 99% rename from AgileMapper/Extensions/TypeExtensions.cs rename to AgileMapper/Extensions/Internal/TypeExtensions.cs index 0d8398bf6..20f68e9ad 100644 --- a/AgileMapper/Extensions/TypeExtensions.cs +++ b/AgileMapper/Extensions/Internal/TypeExtensions.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { using System; using System.Collections; diff --git a/AgileMapper/Extensions/TypeInfo.cs b/AgileMapper/Extensions/Internal/TypeInfo.cs similarity index 78% rename from AgileMapper/Extensions/TypeInfo.cs rename to AgileMapper/Extensions/Internal/TypeInfo.cs index 66655b679..e02a12cd3 100644 --- a/AgileMapper/Extensions/TypeInfo.cs +++ b/AgileMapper/Extensions/Internal/TypeInfo.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Extensions +namespace AgileObjects.AgileMapper.Extensions.Internal { internal static class TypeInfo { diff --git a/AgileMapper/Extensions/ObjectExtensions.cs b/AgileMapper/Extensions/ObjectExtensions.cs deleted file mode 100644 index 5a4078958..000000000 --- a/AgileMapper/Extensions/ObjectExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace AgileObjects.AgileMapper.Extensions -{ - using System; - using System.Reflection; - using NetStandardPolyfills; - - internal static class ObjectExtensions - { - public static readonly MethodInfo GetRuntimeSourceTypeMethod = - typeof(ObjectExtensions).GetPublicStaticMethod("GetRuntimeSourceType"); - - public static Type GetRuntimeSourceType(this TDeclared item) - { - return (item != null) ? item.GetType() : typeof(TDeclared); - } - - public static Type GetRuntimeTargetType(this TDeclared item, Type sourceType) - => (item != null) ? item.GetType() : typeof(TDeclared).GetRuntimeTargetType(sourceType); - - public static Type GetRuntimeTargetType(this Type targetType, Type sourceType) - => sourceType.IsAssignableTo(targetType) ? sourceType : targetType; - } -} diff --git a/AgileMapper/Flattening/ObjectFlattener.cs b/AgileMapper/Flattening/ObjectFlattener.cs index f38eba71f..cfb28c5f8 100644 --- a/AgileMapper/Flattening/ObjectFlattener.cs +++ b/AgileMapper/Flattening/ObjectFlattener.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/MappingExecutor.cs b/AgileMapper/MappingExecutor.cs index 9e7fddbb9..615ead039 100644 --- a/AgileMapper/MappingExecutor.cs +++ b/AgileMapper/MappingExecutor.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; using Api; using Api.Configuration; - using Extensions; + using Extensions.Internal; using ObjectPopulation; internal class MappingExecutor : ITargetTypeSelector, IMappingContext diff --git a/AgileMapper/MappingRuleSetCollection.cs b/AgileMapper/MappingRuleSetCollection.cs index 47d619d01..9ba548543 100644 --- a/AgileMapper/MappingRuleSetCollection.cs +++ b/AgileMapper/MappingRuleSetCollection.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper { using System.Collections.Generic; - using Extensions; + using Extensions.Internal; using Members.Population; using ObjectPopulation; using ObjectPopulation.Enumerables; diff --git a/AgileMapper/Members/ConfiguredSourceMember.cs b/AgileMapper/Members/ConfiguredSourceMember.cs index 8c2ff2841..80ff6be45 100644 --- a/AgileMapper/Members/ConfiguredSourceMember.cs +++ b/AgileMapper/Members/ConfiguredSourceMember.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.Members using System.Collections.Generic; using System.Linq.Expressions; using Caching; - using Extensions; + using Extensions.Internal; using ReadableExpressions; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index f5312f0b3..2ec9bef87 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using ReadableExpressions.Extensions; internal class DictionaryEntrySourceMember : IQualifiedMember diff --git a/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs b/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs index 830ae57aa..25aa10e5d 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal static class DictionaryMemberMapperDataExtensions diff --git a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs index 5383d610a..d44bfef1b 100644 --- a/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionarySourceMember.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal class DictionarySourceMember : IQualifiedMember { diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index efa24dd06..e0b179c11 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries using System; using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 8f7f112fc..ebbee8f21 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.Members using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using ReadableExpressions.Extensions; using static Member; diff --git a/AgileMapper/Members/MappingTypes.cs b/AgileMapper/Members/MappingTypes.cs index 22d226a50..c2cc770c3 100644 --- a/AgileMapper/Members/MappingTypes.cs +++ b/AgileMapper/Members/MappingTypes.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.Members { using System; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class MappingTypes diff --git a/AgileMapper/Members/Member.cs b/AgileMapper/Members/Member.cs index d42c9a7a3..c6d0e4c85 100644 --- a/AgileMapper/Members/Member.cs +++ b/AgileMapper/Members/Member.cs @@ -7,7 +7,7 @@ namespace AgileObjects.AgileMapper.Members using System.Linq.Expressions; using System.Reflection; using Dictionaries; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ObjectPopulation; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 22024ddf1..4f7376362 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; using static System.StringComparison; diff --git a/AgileMapper/Members/MemberFinder.cs b/AgileMapper/Members/MemberFinder.cs index 93331c65e..59f62f929 100644 --- a/AgileMapper/Members/MemberFinder.cs +++ b/AgileMapper/Members/MemberFinder.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Reflection; using Caching; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class MemberCache diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 14939e395..5570ad6ca 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -8,7 +8,7 @@ namespace AgileObjects.AgileMapper.Members using System.Reflection; using DataSources; using Dictionaries; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ObjectPopulation; using static Member; diff --git a/AgileMapper/Members/NamingSettings.cs b/AgileMapper/Members/NamingSettings.cs index fdc267932..6c81aff4f 100644 --- a/AgileMapper/Members/NamingSettings.cs +++ b/AgileMapper/Members/NamingSettings.cs @@ -6,7 +6,7 @@ using System.Text.RegularExpressions; using Caching; using Configuration; - using Extensions; + using Extensions.Internal; internal class NamingSettings { diff --git a/AgileMapper/Members/Population/MemberPopulation.cs b/AgileMapper/Members/Population/MemberPopulation.cs index 3ac7537a3..bbfca6153 100644 --- a/AgileMapper/Members/Population/MemberPopulation.cs +++ b/AgileMapper/Members/Population/MemberPopulation.cs @@ -5,7 +5,7 @@ namespace AgileObjects.AgileMapper.Members.Population using System.Linq.Expressions; using Configuration; using DataSources; - using Extensions; + using Extensions.Internal; using ReadableExpressions; internal class MemberPopulation : IMemberPopulation diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index 45d4d10c9..54afe03a7 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -7,7 +7,7 @@ namespace AgileObjects.AgileMapper.Members using System.Linq.Expressions; using Caching; using Dictionaries; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; diff --git a/AgileMapper/Members/QualifiedMemberFactory.cs b/AgileMapper/Members/QualifiedMemberFactory.cs index fa63cbeb2..f54aa88c8 100644 --- a/AgileMapper/Members/QualifiedMemberFactory.cs +++ b/AgileMapper/Members/QualifiedMemberFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.Members { using Caching; using Dictionaries; - using Extensions; + using Extensions.Internal; internal class QualifiedMemberFactory { diff --git a/AgileMapper/Members/SourceMemberMatcher.cs b/AgileMapper/Members/SourceMemberMatcher.cs index 553ab4ea8..8458f2f51 100644 --- a/AgileMapper/Members/SourceMemberMatcher.cs +++ b/AgileMapper/Members/SourceMemberMatcher.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; - using ObjectPopulation; internal static class SourceMemberMatcher { @@ -32,9 +31,18 @@ public static IQualifiedMember GetMatchFor( while (mappingData.Parent != null) { + if (mappingData.MapperData.TargetMemberIsEnumerableElement()) + { + contextMappingData = null; + return null; + } + mappingData = mappingData.Parent; - matchingMember = EnumerateSourceMembers(mappingData.MapperData.SourceMember, targetData) + var childMapperData = new ChildMemberMapperData(targetData.MapperData.TargetMember, mappingData.MapperData); + contextMappingData = mappingData.GetChildMappingData(childMapperData); + + matchingMember = EnumerateSourceMembers(mappingData.MapperData.SourceMember, contextMappingData) .FirstOrDefault(sm => IsMatchingMember(sm, targetData.MapperData)); if (matchingMember == null) @@ -42,8 +50,6 @@ public static IQualifiedMember GetMatchFor( continue; } - var childMapperData = new ChildMemberMapperData(targetData.MapperData.TargetMember, mappingData.MapperData); - contextMappingData = mappingData.GetChildMappingData(childMapperData); return GetFinalSourceMember(matchingMember, targetData); } diff --git a/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs b/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs index 371a452a6..73401394a 100644 --- a/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs +++ b/AgileMapper/ObjectPopulation/ChildMemberMappingData.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System; using System.Linq.Expressions; using Caching; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs index 37480c979..250c49da1 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeConstructionFactory.cs @@ -7,7 +7,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using System.Reflection; using Caching; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs index e52a9bb4c..5678191f2 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; using ReadableExpressions; diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs index 17d0bf215..918e58c75 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using Members.Population; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs index c33df3a52..3b26db8ee 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/SourceDictionaryShortCircuitFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes { using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members; internal class SourceDictionaryShortCircuitFactory : ISourceShortCircuitFactory diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/StructPopulationExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/StructPopulationExpressionFactory.cs index b99aab546..26f408b0a 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/StructPopulationExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/StructPopulationExpressionFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes { using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members.Population; internal class StructPopulationExpressionFactory : PopulationExpressionFactoryBase diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs index 7cc585a1f..43b0caffa 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/TargetObjectResolutionFactory.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal static class TargetObjectResolutionFactory diff --git a/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs b/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs index 24cab7ba2..6cc1f2bbb 100644 --- a/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs +++ b/AgileMapper/ObjectPopulation/DefaultValueDataSourceFactory.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using DataSources; - using Extensions; + using Extensions.Internal; using Members; internal class DefaultValueDataSourceFactory : IDataSourceFactory diff --git a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs index 8e331cc7f..358390938 100644 --- a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs @@ -5,7 +5,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Linq; using System.Linq.Expressions; using Configuration; - using Extensions; + using Extensions.Internal; using Members; internal static class DerivedComplexTypeMappingsFactory diff --git a/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs b/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs index b94dee13b..d30227485 100644 --- a/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedMappingFactory.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal static class DerivedMappingFactory diff --git a/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs b/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs index 892f1020e..29fde6b8a 100644 --- a/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs +++ b/AgileMapper/ObjectPopulation/DerivedSourceTypeCheck.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal class DerivedSourceTypeCheck diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 6c20d3a2b..fe42c5900 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -8,7 +8,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using ComplexTypes; using DataSources; using Enumerables.Dictionaries; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 3f19836a3..15a133a9e 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; using Members.Population; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs index dfe25935d..54cc79416 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryAdapter.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries using System.Linq; using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members.Dictionaries; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs index e985b2fbc..45ad8a862 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceElementsDictionaryPopulationLoopData.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries using System.Collections.Generic; using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class SourceElementsDictionaryPopulationLoopData : IPopulationLoopData diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs index 38cb83f56..1f757e853 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members.Dictionaries; internal class SourceInstanceDictionaryAdapter : SourceEnumerableAdapterBase, ISourceEnumerableAdapter diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs index 91c40f4f1..88e425207 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryAdapter.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries using System.Collections; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members.Dictionaries; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs index c783e6399..9cde53b83 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; internal class SourceObjectDictionaryPopulationLoopData : IPopulationLoopData { diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 14208ae61..81de79d65 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -6,7 +6,7 @@ using System.Linq.Expressions; using System.Reflection; using Caching; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationContext.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationContext.cs index 6d979e852..50c98adf2 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationContext.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationContext.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs index 3ca5e3f33..138f719f6 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs @@ -5,7 +5,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class EnumerableSourcePopulationLoopData : IPopulationLoopData diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs index 03e4979b2..ceac09f66 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class EnumerableTypeHelper diff --git a/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs index 53e5a67e8..cdcb6ea98 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal interface IPopulationLoopData { diff --git a/AgileMapper/ObjectPopulation/Enumerables/IndexedSourcePopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/IndexedSourcePopulationLoopData.cs index 607a9e096..c3bfb842f 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/IndexedSourcePopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/IndexedSourcePopulationLoopData.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables { using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal class IndexedSourcePopulationLoopData : IPopulationLoopData { diff --git a/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs b/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs index 6e4bc7b0d..22a2a72eb 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/ReadOnlyCollectionWrapper.cs @@ -4,7 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; - using Extensions; + using Extensions.Internal; /// /// Wraps a readonly collection to enable efficient creation of a new array. This object diff --git a/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs b/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs index 9e6166654..f8dd5a345 100644 --- a/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs +++ b/AgileMapper/ObjectPopulation/ExistingOrDefaultValueDataSourceFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using Members.Dictionaries; diff --git a/AgileMapper/ObjectPopulation/MapperDataContext.cs b/AgileMapper/ObjectPopulation/MapperDataContext.cs index 146101e04..899b6780d 100644 --- a/AgileMapper/ObjectPopulation/MapperDataContext.cs +++ b/AgileMapper/ObjectPopulation/MapperDataContext.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Linq; - using Extensions; + using Extensions.Internal; using Members; internal class MapperDataContext diff --git a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs index 51ab63986..f14ebd795 100644 --- a/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal static class MappingDataCreationFactory diff --git a/AgileMapper/ObjectPopulation/MappingDataFactory.cs b/AgileMapper/ObjectPopulation/MappingDataFactory.cs index ecd7840d7..b75d845e8 100644 --- a/AgileMapper/ObjectPopulation/MappingDataFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingDataFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Reflection; using Enumerables; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 222c42b88..f916b7838 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; #if NET_STANDARD #endif using Members; diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index 03e8b9f55..9353e5726 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal static class MappingFactory diff --git a/AgileMapper/ObjectPopulation/ObjectCreationCallbackFactory.cs b/AgileMapper/ObjectPopulation/ObjectCreationCallbackFactory.cs index 51e2fe979..2e5a37eec 100644 --- a/AgileMapper/ObjectPopulation/ObjectCreationCallbackFactory.cs +++ b/AgileMapper/ObjectPopulation/ObjectCreationCallbackFactory.cs @@ -3,7 +3,7 @@ using System; using System.Linq.Expressions; using Configuration; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index 8615c217f..d237bcf2e 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -7,7 +7,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Reflection; using DataSources; using Enumerables; - using Extensions; + using Extensions.Internal; using Members; using Members.Sources; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs b/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs index 42e9e6413..6fe927f28 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperFactory.cs @@ -6,7 +6,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using Caching; using ComplexTypes; using Enumerables; - using Extensions; + using Extensions.Internal; using Validation; internal class ObjectMapperFactory diff --git a/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs b/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs index b272db0bd..c69c78cb0 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs @@ -4,7 +4,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Linq; using System.Linq.Expressions; using Enumerables; - using Extensions; + using Extensions.Internal; using Members; using Members.Sources; using NetStandardPolyfills; diff --git a/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs index 250e7f6a0..15c9d5e44 100644 --- a/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/SimpleTypeMappingExpressionFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System.Collections.Generic; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal class SimpleTypeMappingExpressionFactory : MappingExpressionFactoryBase diff --git a/AgileMapper/ObjectPopulation/SourceMemberTypeDependentKeyBase.cs b/AgileMapper/ObjectPopulation/SourceMemberTypeDependentKeyBase.cs index a268f0821..f6aa4588f 100644 --- a/AgileMapper/ObjectPopulation/SourceMemberTypeDependentKeyBase.cs +++ b/AgileMapper/ObjectPopulation/SourceMemberTypeDependentKeyBase.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; internal abstract class SourceMemberTypeDependentKeyBase diff --git a/AgileMapper/Parameters.cs b/AgileMapper/Parameters.cs index 7ec2a598f..2f2f4856b 100644 --- a/AgileMapper/Parameters.cs +++ b/AgileMapper/Parameters.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using Members; using ObjectPopulation; diff --git a/AgileMapper/TypeConversion/ConverterSet.cs b/AgileMapper/TypeConversion/ConverterSet.cs index e754809d6..a1f9c8705 100644 --- a/AgileMapper/TypeConversion/ConverterSet.cs +++ b/AgileMapper/TypeConversion/ConverterSet.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Linq.Expressions; using Configuration; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; diff --git a/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs b/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs index 73f9097a7..f5c9ce18e 100644 --- a/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs +++ b/AgileMapper/TypeConversion/FallbackNonSimpleTypeValueConverter.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.TypeConversion { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal class FallbackNonSimpleTypeValueConverter : ValueConverterBase { diff --git a/AgileMapper/TypeConversion/NumericConversions.cs b/AgileMapper/TypeConversion/NumericConversions.cs index b25c59e08..3024674ca 100644 --- a/AgileMapper/TypeConversion/NumericConversions.cs +++ b/AgileMapper/TypeConversion/NumericConversions.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal static class NumericConversions { diff --git a/AgileMapper/TypeConversion/NumericValueIsInRangeComparison.cs b/AgileMapper/TypeConversion/NumericValueIsInRangeComparison.cs index 9f23ac437..f69a52b70 100644 --- a/AgileMapper/TypeConversion/NumericValueIsInRangeComparison.cs +++ b/AgileMapper/TypeConversion/NumericValueIsInRangeComparison.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; internal static class NumericValueIsInRangeComparison { diff --git a/AgileMapper/TypeConversion/ToBoolConverter.cs b/AgileMapper/TypeConversion/ToBoolConverter.cs index 41bf75fd9..7abee406b 100644 --- a/AgileMapper/TypeConversion/ToBoolConverter.cs +++ b/AgileMapper/TypeConversion/ToBoolConverter.cs @@ -5,7 +5,7 @@ namespace AgileObjects.AgileMapper.TypeConversion using System.Globalization; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using ReadableExpressions.Extensions; internal class ToBoolConverter : ValueConverterBase diff --git a/AgileMapper/TypeConversion/ToCharacterConverter.cs b/AgileMapper/TypeConversion/ToCharacterConverter.cs index 5badd3e61..cb96d3ebc 100644 --- a/AgileMapper/TypeConversion/ToCharacterConverter.cs +++ b/AgileMapper/TypeConversion/ToCharacterConverter.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class ToCharacterConverter : ValueConverterBase diff --git a/AgileMapper/TypeConversion/ToEnumConverter.cs b/AgileMapper/TypeConversion/ToEnumConverter.cs index cf8776a32..2976fc397 100644 --- a/AgileMapper/TypeConversion/ToEnumConverter.cs +++ b/AgileMapper/TypeConversion/ToEnumConverter.cs @@ -2,7 +2,7 @@ { using System; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class ToEnumConverter : ValueConverterBase diff --git a/AgileMapper/TypeConversion/ToFormattedStringConverter.cs b/AgileMapper/TypeConversion/ToFormattedStringConverter.cs index cccec138b..8de7c361c 100644 --- a/AgileMapper/TypeConversion/ToFormattedStringConverter.cs +++ b/AgileMapper/TypeConversion/ToFormattedStringConverter.cs @@ -4,7 +4,7 @@ using System.Linq.Expressions; using System.Reflection; using Configuration; - using Extensions; + using Extensions.Internal; using ReadableExpressions.Extensions; internal class ToFormattedStringConverter : ValueConverterBase diff --git a/AgileMapper/TypeConversion/ToNumericConverter.cs b/AgileMapper/TypeConversion/ToNumericConverter.cs index c104dc426..536fa9873 100644 --- a/AgileMapper/TypeConversion/ToNumericConverter.cs +++ b/AgileMapper/TypeConversion/ToNumericConverter.cs @@ -2,7 +2,7 @@ { using System; using System.Linq; - using Extensions; + using Extensions.Internal; internal class ToNumericConverter : ToNumericConverterBase { diff --git a/AgileMapper/TypeConversion/ToNumericConverterBase.cs b/AgileMapper/TypeConversion/ToNumericConverterBase.cs index c38fb3161..b48eaab11 100644 --- a/AgileMapper/TypeConversion/ToNumericConverterBase.cs +++ b/AgileMapper/TypeConversion/ToNumericConverterBase.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Linq.Expressions; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal abstract class ToNumericConverterBase : TryParseConverterBase diff --git a/AgileMapper/TypeConversion/ToStringConverter.cs b/AgileMapper/TypeConversion/ToStringConverter.cs index e76f1ebd7..70e8f7990 100644 --- a/AgileMapper/TypeConversion/ToStringConverter.cs +++ b/AgileMapper/TypeConversion/ToStringConverter.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal class ToStringConverter : ValueConverterBase diff --git a/AgileMapper/TypeConversion/TryParseConverterBase.cs b/AgileMapper/TypeConversion/TryParseConverterBase.cs index 99438da38..a72fb2a20 100644 --- a/AgileMapper/TypeConversion/TryParseConverterBase.cs +++ b/AgileMapper/TypeConversion/TryParseConverterBase.cs @@ -3,7 +3,7 @@ namespace AgileObjects.AgileMapper.TypeConversion using System; using System.Linq.Expressions; using System.Reflection; - using Extensions; + using Extensions.Internal; using NetStandardPolyfills; internal abstract class TryParseConverterBase : ValueConverterBase diff --git a/AgileMapper/Validation/EnumMappingMismatchFinder.cs b/AgileMapper/Validation/EnumMappingMismatchFinder.cs index bc74264ea..7f766349b 100644 --- a/AgileMapper/Validation/EnumMappingMismatchFinder.cs +++ b/AgileMapper/Validation/EnumMappingMismatchFinder.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using NetStandardPolyfills; using ObjectPopulation; diff --git a/AgileMapper/Validation/EnumMappingMismatchSet.cs b/AgileMapper/Validation/EnumMappingMismatchSet.cs index 04c3d2d59..94662e57d 100644 --- a/AgileMapper/Validation/EnumMappingMismatchSet.cs +++ b/AgileMapper/Validation/EnumMappingMismatchSet.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Linq.Expressions; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using ReadableExpressions; diff --git a/AgileMapper/Validation/MappingValidator.cs b/AgileMapper/Validation/MappingValidator.cs index fe25a93f5..0000fd595 100644 --- a/AgileMapper/Validation/MappingValidator.cs +++ b/AgileMapper/Validation/MappingValidator.cs @@ -5,7 +5,7 @@ using System.Text; using Configuration; using DataSources; - using Extensions; + using Extensions.Internal; using Members; using ObjectPopulation; From 7f3283fa25c5d9f61b25da2c3fec1ab9e9fcfb13 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 07:49:03 +0000 Subject: [PATCH 37/74] Extending test coverage for mapping to nested dynamic members --- .../WhenMappingToNewDynamicMembers.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs index 38e58c50f..e0e9f8944 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamicMembers.cs @@ -25,5 +25,25 @@ public void ShouldMapFromAFlattenedMember() ((string)resultDynamic.Line2).ShouldBe("Over there!"); ((string)resultDynamic.Line3).ShouldBe("Over where?!"); } + + [Fact] + public void ShouldMapFromNestedMembers() + { + var source = new PublicField
+ { + Value = new Address + { + Line1 = "One One One", + Line2 = "Two Two Two" + } + }; + + var result = Mapper.Map(source).ToANew>(); + + ((object)result.Value).ShouldNotBeNull(); + dynamic resultDynamic = result.Value; + ((string)resultDynamic.Line1).ShouldBe("One One One"); + ((string)resultDynamic.Line2).ShouldBe("Two Two Two"); + } } } From 87253c0d7cc578d99b15e034e1c77ee3051ecd1d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 07:55:45 +0000 Subject: [PATCH 38/74] Test coverage for mapping to root enumerable --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenMappingToNewEnumerablesOfDynamic.cs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index c371f08af..def33c094 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -112,6 +112,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs new file mode 100644 index 000000000..a61190f75 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs @@ -0,0 +1,28 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Collections.Generic; + using System.Linq; + using TestClasses; + using Xunit; + + public class WhenMappingToNewEnumerablesOfDynamic + { + [Fact] + public void ShouldMapAComplexTypeArray() + { + var source = new[] + { + new PublicField { Value = '1' }, + new PublicField { Value = '2' }, + new PublicField { Value = '3' } + }; + var result = Mapper.Map(source).ToANew>(); + + result.Count.ShouldBe(3); + + ((char)result.First().Value).ShouldBe('1'); + ((char)result.Second().Value).ShouldBe('2'); + ((char)result.Third().Value).ShouldBe('3'); + } + } +} \ No newline at end of file From c09cf7aabfd0d8acc958b0199e0aec32df52d3f9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 09:47:11 +0000 Subject: [PATCH 39/74] Test coverage for mapping derived complex type collection elements to dynamics --- .../WhenMappingToNewEnumerablesOfDynamic.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs index a61190f75..10c84005b 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewEnumerablesOfDynamic.cs @@ -24,5 +24,22 @@ public void ShouldMapAComplexTypeArray() ((char)result.Second().Value).ShouldBe('2'); ((char)result.Third().Value).ShouldBe('3'); } + + [Fact] + public void ShouldMapAComplexTypeCollectionByRuntimeType() + { + ICollection source = new List + { + new PersonViewModel { Name = "Person" }, + new CustomerViewModel { Name = "Customer", Discount = 0.2 } + }; + var result = Mapper.Map(source).ToANew(); + + result.Length.ShouldBe(2); + + ((string)result.First().Name).ShouldBe("Person"); + ((string)result.Second().Name).ShouldBe("Customer"); + ((double)result.Second().Discount).ShouldBe(0.2); + } } } \ No newline at end of file From a96fcf2acc9ef21c9e268abca63dc99a053cb5f5 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 10:58:42 +0000 Subject: [PATCH 40/74] Separating global dictionary configuration from source dictionary configuration --- ...ConfiguringDictionaryMappingIncorrectly.cs | 22 ++-- .../WhenConfiguringSourceDictionaryMapping.cs | 30 +++-- .../WhenConfiguringTargetDictionaryMapping.cs | 2 +- .../Dictionaries/DictionaryConfigurator.cs | 123 ------------------ .../DictionaryMappingConfigurator.cs | 99 ++++++++++++++ .../DictionaryMappingConfiguratorBase.cs | 2 +- .../Dictionaries/IGlobalDictionarySettings.cs | 31 ++--- .../Dictionaries/ISourceDictionarySettings.cs | 59 +++++++++ .../ISourceDictionaryTargetTypeSelector.cs | 43 ++++++ .../TargetDictionaryMappingConfigurator.cs | 2 +- .../MappingConfigStartingPoint.cs | 55 +++++--- 11 files changed, 285 insertions(+), 183 deletions(-) delete mode 100644 AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs create mode 100644 AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs create mode 100644 AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs create mode 100644 AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs index b8ca5d475..26e93753b 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs @@ -14,7 +14,7 @@ public void ShouldErrorIfCustomMemberKeyIsNull() var configEx = Should.Throw(() => { Mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>() .MapFullKey(null) .To(pf => pf.Value); @@ -29,7 +29,7 @@ public void ShouldErrorIfCustomMemberNameIsNull() var configEx = Should.Throw(() => { Mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>() .MapMemberNameKey(null) .To(pf => pf.Value); @@ -46,12 +46,12 @@ public void ShouldErrorIfIgnoredMemberIsGivenCustomMemberKey() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .Ignore(p => p.Id); mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .MapFullKey("PersonId") .To(p => p.Id); @@ -69,12 +69,12 @@ public void ShouldErrorIfIgnoredMemberIsGivenCustomMemberName() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>() .Ignore(pf => pf.Value); mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>() .MapMemberNameKey("ValueValue") .To(pf => pf.Value); @@ -92,13 +92,13 @@ public void ShouldErrorIfCustomDataSourceMemberIsGivenCustomMemberKey() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .Map((d, p) => d.Count) .To(p => p.Name); mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .MapFullKey("PersonName") .To(p => p.Name); @@ -116,13 +116,13 @@ public void ShouldErrorIfCustomDataSourceMemberIsGivenCustomMemberName() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .Map((d, p) => d.Count) .To(p => p.Name); mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .MapMemberNameKey("PersonName") .To(p => p.Name); @@ -202,7 +202,7 @@ public void ShouldErrorIfDifferentSeparatorsSpecifiedForASpecificTargetType() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>>() .UseMemberNameSeparator("-") .UseMemberNameSeparator("_"); diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs index 3f435f927..76b983e0c 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs @@ -14,7 +14,7 @@ public void ShouldUseACustomFullDictionaryKeyForARootMember() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>() .MapFullKey("BoomDiddyBoom") .To(pf => pf.Value); @@ -32,7 +32,7 @@ public void ShouldUseACustomFullDictionaryKeyForANestedMember() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .OnTo>>() .MapFullKey("BoomDiddyMcBoom") .To(pf => pf.Value.Value); @@ -54,7 +54,7 @@ public void ShouldUseCustomMemberNameDictionaryKeysForRootMembers() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .Over
() .MapMemberNameKey("HouseNumber") .To(a => a.Line1) @@ -81,7 +81,7 @@ public void ShouldUseACustomMemberNameDictionaryKeyForANestedMember() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionariesWithValueType() .ToANew
() .MapMemberNameKey("HouseName") .To(a => a.Line1); @@ -99,7 +99,7 @@ public void ShouldUseACustomMemberNameDictionaryKeyForANestedArrayMember() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To>() .MapMemberNameKey("Strings") .To(pp => pp.Value); @@ -125,7 +125,7 @@ public void ShouldApplyNonDictionarySpecificConfiguration() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionariesWithValueType() .To() .MapFullKey("BlahBlahBlah") .To(p => p.ProductId) @@ -175,7 +175,7 @@ public void ShouldApplyFlattenedMemberNamesToASpecificTargetType() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .To() .MapMemberNameKey("OrderCode") .To(o => o.OrderId); @@ -230,7 +230,7 @@ public void ShouldApplyACustomSeparatorToASpecificTargetType() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .ToANew() .UseMemberNameSeparator("_") .And @@ -268,6 +268,7 @@ public void ShouldApplyGlobalThenSpecificCustomSeparators() .Dictionaries .UseMemberNameSeparator("+") .AndWhenMapping + .FromDictionaries .ToANew
() .UseMemberNameSeparator("-"); @@ -294,7 +295,7 @@ public void ShouldApplyACustomEnumerableElementPatternGlobally() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .UseElementKeyPattern("_i_") .AndWhenMapping .To>() @@ -325,10 +326,11 @@ public void ShouldApplyACustomEnumerableElementPatternToASpecificTargetType() { mapper.WhenMapping .Dictionaries - .OnTo
() .UseMemberNameSeparator("-") .UseElementKeyPattern("i") - .And + .AndWhenMapping + .FromDictionariesWithValueType() + .OnTo
() .MapMemberNameKey("StreetName") .To(a => a.Line1) .And @@ -362,7 +364,7 @@ public void ShouldConditionallyMapToDerivedTypes() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .ToANew() .If(s => s.Source.ContainsKey("Discount")) .MapTo() @@ -396,7 +398,7 @@ public void ShouldConditionallyMapToDerivedTypesFromASpecificValueTypeDictionary using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .DictionariesWithValueType() + .FromDictionariesWithValueType() .ToANew() .If(s => s.Source["Report"].Length > 10) .MapTo(); @@ -426,7 +428,7 @@ public void ShouldRestrictCustomKeysByDictionaryValueType() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .DictionariesWithValueType() + .FromDictionariesWithValueType() .ToANew>() .MapFullKey("LaLaLa") .To(p => p.Value); diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index d94708ab9..cadd1133b 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -257,7 +257,7 @@ public void ShouldApplyACustomConfiguredMember() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .ToANew>() .Map(ctx => ctx.Source.Count) .To(pf => pf.Value); diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs deleted file mode 100644 index 6e860ddda..000000000 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryConfigurator.cs +++ /dev/null @@ -1,123 +0,0 @@ -namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries -{ - using AgileMapper.Configuration; - - /// - /// Provides options for configuring how a mapper performs mapping from or to Dictionary{string, TValue} - /// instances. - /// - /// - /// The type of values stored in the dictionary to which the configurations will apply. - /// - public class DictionaryConfigurator : IGlobalDictionarySettings - { - private readonly MappingConfigInfo _configInfo; - - internal DictionaryConfigurator(MappingConfigInfo configInfo) - { - _configInfo = configInfo.Set(DictionaryType.Dictionary); - - if (_configInfo.SourceValueType == null) - { - _configInfo.ForSourceValueType(); - } - } - - #region IGlobalDictionarySettings Members - - /// - /// Construct dictionary keys for nested members using flattened member names. For example, a - /// Person.Address.StreetName member would be populated using the dictionary entry with key - /// 'AddressStreetName' when mapping to a root Person object. - /// - public IGlobalDictionarySettings UseFlattenedTargetMemberNames() - { - var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(GlobalConfigInfo); - - _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(flattenedJoiningNameFactory); - return this; - } - - /// - /// Use the given to separate member names when mapping to nested - /// complex type members of any target type. For example, calling UseMemberName("_") will require - /// a dictionary entry with the key 'Address_Line1' to map to an Address.Line1 member. - /// - /// - /// The separator to use to separate member names when constructing dictionary keys for nested - /// members. - /// - public IGlobalDictionarySettings UseMemberNameSeparator(string separator) - { - var joiningNameFactory = JoiningNameFactory.For(separator, GlobalConfigInfo); - - _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(joiningNameFactory); - return this; - } - - /// - /// Use the given to create the part of a dictionary key representing an - /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the - /// enmerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a dictionary - /// to a collection of ints will generate searches for keys '(0)', '(1)', '(2)', etc. - /// - /// - /// The pattern to use to create a dictionary key part representing an enumerable element. - /// - public IGlobalDictionarySettings UseElementKeyPattern(string pattern) - { - var keyPartFactory = ElementKeyPartFactory.For(pattern, GlobalConfigInfo); - - _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(keyPartFactory); - return this; - } - - DictionaryConfigurator IGlobalDictionarySettings.AndWhenMapping => this; - - #endregion - - private MappingConfigInfo GlobalConfigInfo => _configInfo.Clone().ForAllRuleSets().ForAllTargetTypes(); - - /// - /// Configure how this mapper performs mappings from dictionaries in all MappingRuleSets - /// (create new, overwrite, etc), to the target type specified by the type argument. - /// - /// The target type to which the configuration will apply. - /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator To() - => CreateConfigurator(_configInfo.ForAllRuleSets()); - - /// - /// Configure how this mapper performs object creation mappings from dictionaries to the target type - /// specified by the type argument. - /// - /// The target type to which the configuration will apply. - /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator ToANew() - => CreateConfigurator(Constants.CreateNew); - - /// - /// Configure how this mapper performs OnTo (merge) mappings from dictionaries to the target type - /// specified by the type argument. - /// - /// The target type to which the configuration will apply. - /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator OnTo() - => CreateConfigurator(Constants.Merge); - - /// - /// Configure how this mapper performs Over (overwrite) mappings from dictionaries to the target type - /// specified by the type argument. - /// - /// The target type to which the configuration will apply. - /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. - public ISourceDictionaryMappingConfigurator Over() - => CreateConfigurator(Constants.Overwrite); - - private ISourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) - => CreateConfigurator(_configInfo.ForRuleSet(ruleSetName)); - - private static ISourceDictionaryMappingConfigurator CreateConfigurator(MappingConfigInfo configInfo) - => new SourceDictionaryMappingConfigurator(configInfo); - } -} diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs new file mode 100644 index 000000000..2d800a9a7 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -0,0 +1,99 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries +{ + using AgileMapper.Configuration; + + internal class DictionaryMappingConfigurator : + IGlobalDictionarySettings, + ISourceDictionaryTargetTypeSelector + { + private readonly MappingConfigInfo _configInfo; + + internal DictionaryMappingConfigurator(MappingConfigInfo configInfo) + { + _configInfo = configInfo.Set(DictionaryType.Dictionary); + + if (_configInfo.SourceValueType == null) + { + _configInfo.ForSourceValueType(); + } + } + + #region Dictionary Mapping Settings + + private MappingConfigInfo GetConfigInfo() + => (_configInfo.TargetType != null) ? _configInfo : GlobalConfigInfo; + + private MappingConfigInfo GlobalConfigInfo => _configInfo.Clone().ForAllRuleSets().ForAllTargetTypes(); + + IGlobalDictionarySettings IGlobalDictionarySettings.UseFlattenedTargetMemberNames() + => RegisterFlattenedTargetMemberNames(GlobalConfigInfo); + + public ISourceDictionarySettings UseFlattenedTargetMemberNames() + => RegisterFlattenedTargetMemberNames(GetConfigInfo()); + + private DictionaryMappingConfigurator RegisterFlattenedTargetMemberNames(MappingConfigInfo configInfo) + { + var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(configInfo); + + _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(flattenedJoiningNameFactory); + return this; + } + + IGlobalDictionarySettings IGlobalDictionarySettings.UseMemberNameSeparator(string separator) + => RegisterMemberNameSeparator(separator, GlobalConfigInfo); + + public ISourceDictionarySettings UseMemberNameSeparator(string separator) + => RegisterMemberNameSeparator(separator, GetConfigInfo()); + + private DictionaryMappingConfigurator RegisterMemberNameSeparator( + string separator, + MappingConfigInfo configInfo) + { + var joiningNameFactory = JoiningNameFactory.For(separator, configInfo); + + _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(joiningNameFactory); + return this; + } + + IGlobalDictionarySettings IGlobalDictionarySettings.UseElementKeyPattern(string pattern) + => RegisterElementKeyPattern(pattern, GlobalConfigInfo); + + public ISourceDictionarySettings UseElementKeyPattern(string pattern) + => RegisterElementKeyPattern(pattern, GetConfigInfo()); + + private DictionaryMappingConfigurator RegisterElementKeyPattern( + string pattern, + MappingConfigInfo configInfo) + { + var keyPartFactory = ElementKeyPartFactory.For(pattern, configInfo); + + _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(keyPartFactory); + return this; + } + + MappingConfigStartingPoint IGlobalDictionarySettings.AndWhenMapping + => new MappingConfigStartingPoint(_configInfo.MapperContext); + + public ISourceDictionaryTargetTypeSelector AndWhenMapping => this; + + #endregion + + public ISourceDictionaryMappingConfigurator To() + => CreateConfigurator(_configInfo.ForAllRuleSets()); + + public ISourceDictionaryMappingConfigurator ToANew() + => CreateConfigurator(Constants.CreateNew); + + public ISourceDictionaryMappingConfigurator OnTo() + => CreateConfigurator(Constants.Merge); + + public ISourceDictionaryMappingConfigurator Over() + => CreateConfigurator(Constants.Overwrite); + + private ISourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) + => CreateConfigurator(_configInfo.ForRuleSet(ruleSetName)); + + private static ISourceDictionaryMappingConfigurator CreateConfigurator(MappingConfigInfo configInfo) + => new SourceDictionaryMappingConfigurator(configInfo); + } +} diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs index 4e55e54f5..2cd4539f1 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs @@ -10,7 +10,7 @@ protected DictionaryMappingConfiguratorBase(MappingConfigInfo configInfo) { } - protected void SetupFlattenedMemberNames() + protected void SetupFlattenedTargetMemberNames() { var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(ConfigInfo); diff --git a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs index 860a3a5ee..70e34c07d 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { /// - /// Provides options for globally configuring how all mappers will perform mappings from dictionaries. + /// Provides options for configuring how this mapper will perform mappings from and to dictionaries. /// /// /// The type of values stored in the dictionary to which the configurations will apply. @@ -9,49 +9,50 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries public interface IGlobalDictionarySettings { /// - /// Construct keys for target dictionary members using flattened member names. For example, a - /// Person.Address.StreetName member would be mapped to a dictionary entry with the key + /// Construct keys for target Dictionary members using flattened member names. For example, + /// a Person.Address.StreetName member would be mapped to a Dictionary entry with the key /// 'AddressStreetName'. /// /// /// An with which to globally configure other - /// dictionary mapping aspects. + /// Dictionary mapping aspects. /// IGlobalDictionarySettings UseFlattenedTargetMemberNames(); /// - /// Use the given to separate member names when mapping to nested - /// complex type members of any target type. For example, calling UseMemberName("_") will require - /// a dictionary entry with the key 'Address_Line1' to map to an Address.Line1 member. + /// Use the given to construct source and target Dictionary keys, and + /// to separate member names when mapping to nested complex type members of any target type. For + /// example, calling UseMemberName("_") will require a Dictionary entry with the key 'Address_Line1' + /// to map to an Address.Line1 member. /// /// - /// The separator to use to separate member names when constructing dictionary keys for nested + /// The separator to use to separate member names when constructing Dictionary keys for nested /// members. /// /// /// An with which to globally configure other - /// dictionary mapping aspects. + /// Dictionary mapping aspects. /// IGlobalDictionarySettings UseMemberNameSeparator(string separator); /// - /// Use the given to create the part of a dictionary key representing an + /// Use the given to create the part of a Dictionary key representing an /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the - /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a dictionary + /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a Dictionary /// to a collection of ints will generate searches for keys '(0)', '(1)', '(2)', etc. /// /// - /// The pattern to use to create a dictionary key part representing an enumerable element. + /// The pattern to use to create a Dictionary key part representing an enumerable element. /// /// /// An with which to globally configure other - /// dictionary mapping aspects. + /// Dictionary mapping aspects. /// IGlobalDictionarySettings UseElementKeyPattern(string pattern); /// - /// Gets a link back to the full , for api fluency. + /// Gets a link back to the full , for api fluency. /// - DictionaryConfigurator AndWhenMapping { get; } + MappingConfigStartingPoint AndWhenMapping { get; } } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs new file mode 100644 index 000000000..6071182cb --- /dev/null +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs @@ -0,0 +1,59 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries +{ + /// + /// Provides options for configuring how this mapper will perform mappings from and to dictionaries. + /// + /// + /// The type of values stored in the dictionary to which the configurations will apply. + /// + public interface ISourceDictionarySettings + { + /// + /// Construct keys for target Dictionary members using flattened member names. For example, a + /// Person.Address.StreetName member would be mapped to a Dictionary entry with the key + /// 'AddressStreetName'. + /// + /// + /// The with which to globally configure other + /// Dictionary mapping aspects. + /// + ISourceDictionarySettings UseFlattenedTargetMemberNames(); + + /// + /// Use the given to construct source and target Dictionary keys, + /// and to separate member names when mapping to nested complex type members of any target type. + /// For example, calling UseMemberName("_") will require a Dictionary entry with the key + /// 'Address_Line1' to map to an Address.Line1 member. + /// + /// + /// The separator to use to separate member names when constructing Dictionary keys for nested + /// members. + /// + /// + /// The with which to globally configure other + /// Dictionary mapping aspects. + /// + ISourceDictionarySettings UseMemberNameSeparator(string separator); + + /// + /// Use the given to create the part of a Dictionary key representing an + /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the + /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a Dictionary + /// to a collection of ints will generate searches for keys '(0)', '(1)', '(2)', etc. + /// + /// + /// The pattern to use to create a Dictionary key part representing an enumerable element. + /// + /// + /// The with which to globally configure other + /// Dictionary mapping aspects. + /// + ISourceDictionarySettings UseElementKeyPattern(string pattern); + + /// + /// Gets a link back to the full , + /// for api fluency. + /// + ISourceDictionaryTargetTypeSelector AndWhenMapping { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs new file mode 100644 index 000000000..1600fc968 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs @@ -0,0 +1,43 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries +{ + /// + /// Provides options for specifying the type of dictionary mapping to perform. + /// + /// + /// The type of values stored in the dictionary to which the configurations will apply. + /// + public interface ISourceDictionaryTargetTypeSelector : ISourceDictionarySettings + { + /// + /// Configure how this mapper performs mappings from dictionaries in all MappingRuleSets + /// (create new, overwrite, etc), to the target type specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. + ISourceDictionaryMappingConfigurator To(); + + /// + /// Configure how this mapper performs object creation mappings from dictionaries to the target type + /// specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. + ISourceDictionaryMappingConfigurator ToANew(); + + /// + /// Configure how this mapper performs OnTo (merge) mappings from dictionaries to the target type + /// specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. + ISourceDictionaryMappingConfigurator OnTo(); + + /// + /// Configure how this mapper performs Over (overwrite) mappings from dictionaries to the target type + /// specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDictionaryMappingConfigurator with which to complete the configuration. + ISourceDictionaryMappingConfigurator Over(); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs index 21444211d..be726eaa5 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs @@ -20,7 +20,7 @@ public TargetDictionaryMappingConfigurator(MappingConfigInfo configInfo) public ITargetDictionaryConfigSettings UseFlattenedMemberNames() { - SetupFlattenedMemberNames(); + SetupFlattenedTargetMemberNames(); return this; } diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index 8b0eb0d6c..9d997df75 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -17,13 +17,8 @@ public class MappingConfigStartingPoint : IGlobalConfigSettings private readonly MappingConfigInfo _configInfo; internal MappingConfigStartingPoint(MapperContext mapperContext) - : this(new MappingConfigInfo(mapperContext)) { - } - - internal MappingConfigStartingPoint(MappingConfigInfo configInfo) - { - _configInfo = configInfo; + _configInfo = new MappingConfigInfo(mapperContext); } private MapperContext MapperContext => _configInfo.MapperContext; @@ -343,11 +338,33 @@ public InstanceConfigurator InstancesOf(TObject exampleInstanc public InstanceConfigurator InstancesOf() where TObject : class => new InstanceConfigurator(GlobalConfigInfo); + #region Dictionaries + /// - /// Configure how this mapper performs mappings from source Dictionary{string, T} instances. + /// Configure how this mapper performs mappings from or to source Dictionary instances + /// with any Dictionary value type. /// - public DictionaryConfigurator Dictionaries - => CreateDictionaryConfigurator(config => config.ForSourceValueType(Constants.AllTypes)); + public IGlobalDictionarySettings Dictionaries + => CreateDictionaryConfigurator(sourceValueType: Constants.AllTypes); + + /// + /// Configure how this mapper performs mappings from or to source Dictionary{string, TValue} instances. + /// + /// + /// The type of values contained in the Dictionary to which the configuration will apply. + /// + /// + /// An IGlobalDictionarySettings with which to continue other global aspects of Dictionary mapping. + /// + public IGlobalDictionarySettings DictionariesWithValueType() + => CreateDictionaryConfigurator(); + + /// + /// Configure how this mapper performs mappings from source Dictionary instances with + /// any Dictionary value type. + /// + public ISourceDictionaryTargetTypeSelector FromDictionaries + => CreateDictionaryConfigurator(sourceValueType: Constants.AllTypes); /// /// Configure how this mapper performs mappings from source Dictionary{string, TValue} instances. @@ -355,20 +372,24 @@ public DictionaryConfigurator Dictionaries /// /// The type of values contained in the Dictionary to which the configuration will apply. /// - /// A DictionaryConfigurator with which to continue the configuration. - public DictionaryConfigurator DictionariesWithValueType() + /// + /// An ISourceDictionaryTargetTypeSelector with which to specify to which target type the + /// configuration will apply. + /// + public ISourceDictionaryTargetTypeSelector FromDictionariesWithValueType() => CreateDictionaryConfigurator(); - private DictionaryConfigurator CreateDictionaryConfigurator( - Action configInfoConfigurator = null) + private DictionaryMappingConfigurator CreateDictionaryConfigurator(Type sourceValueType = null) { - var configInfo = _configInfo.ForAllSourceTypes(); - - configInfoConfigurator?.Invoke(configInfo); + var configInfo = _configInfo + .ForAllSourceTypes() + .ForSourceValueType(sourceValueType ?? typeof(TValue)); - return new DictionaryConfigurator(configInfo); + return new DictionaryMappingConfigurator(configInfo); } + #endregion + /// /// Configure how this mapper performs mappings from the source type specified by the given /// . Use this overload for anonymous types. From 425ff9ca46767113e048fd0740dedc2700065ddf Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 13:08:50 +0000 Subject: [PATCH 41/74] Support for mapping nested members to flattened, underscore-separated target dynamic members --- .../Dynamics/WhenMappingToNewDynamics.cs | 21 ++++++++++++++++ .../Configuration/DictionarySettings.cs | 1 + .../Configuration/JoiningNameFactory.cs | 11 +++++++++ .../Dictionaries/DictionaryTargetMember.cs | 21 ++++++++++++++++ AgileMapper/Members/QualifiedMember.cs | 4 ++-- AgileMapper/Members/TypePairExtensions.cs | 24 ++++++------------- .../DictionaryMappingExpressionFactory.cs | 5 ++++ .../ObjectPopulation/ObjectMapperData.cs | 3 ++- 8 files changed, 70 insertions(+), 20 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs index 0c3be67f2..8f6e7807f 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingToNewDynamics.cs @@ -1,7 +1,9 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { using System.Dynamic; + using Microsoft.CSharp.RuntimeBinder; using Shouldly; + using TestClasses; using Xunit; public class WhenMappingToNewDynamics @@ -23,5 +25,24 @@ public void ShouldMapToAnExpandoObjectSimpleTypeMember() ((object)result).ShouldNotBeNull(); ((string)result.Value).ShouldBe("Oh so dynamic"); } + + [Fact] + public void ShouldMapNestedMembersToAnExpandoObject() + { + var source = new Customer + { + Title = Title.Mrs, + Name = "Captain Customer", + Address = new Address { Line1 = "One!", Line2 = "Two!" } + }; + dynamic result = Mapper.Map(source).ToANew(); + + ((object)result).ShouldNotBeNull(); + ((Title)result.Title).ShouldBe(Title.Mrs); + ((string)result.Name).ShouldBe("Captain Customer"); + Should.Throw(() => result.Address); + ((string)result.Address_Line1).ShouldBe("One!"); + ((string)result.Address_Line2).ShouldBe("Two!"); + } } } diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/DictionarySettings.cs index f8824cab6..860190a96 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/DictionarySettings.cs @@ -21,6 +21,7 @@ public DictionarySettings(MapperContext mapperContext) _joiningNameFactories = new List { + JoiningNameFactory.UnderscoredForDynamics(mapperContext), JoiningNameFactory.Dotted(mapperContext) }; diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/JoiningNameFactory.cs index 843d0bbe0..1048298d0 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/JoiningNameFactory.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Configuration { using System; + using System.Dynamic; using System.Linq.Expressions; using Extensions.Internal; using Members; @@ -30,6 +31,16 @@ private JoiningNameFactory( #region Factory Methods + public static JoiningNameFactory UnderscoredForDynamics(MapperContext mapperContext) + { + var targetExpandoObject = new MappingConfigInfo(mapperContext) + .ForAllRuleSets() + .ForAllSourceTypes() + .ForTargetType(); + + return For("_", targetExpandoObject); + } + public static JoiningNameFactory Dotted(MapperContext mapperContext) => For(".", MappingConfigInfo.AllRuleSetsSourceTypesAndTargetTypes(mapperContext)); diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index e0b179c11..3280359e5 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; using System.Collections.Generic; + using System.Dynamic; using System.Linq.Expressions; using Extensions.Internal; using NetStandardPolyfills; @@ -60,6 +61,26 @@ public override Type GetElementType(Type sourceElementType) public override bool GuardObjectValuePopulations => true; + public override bool HasCompatibleType(Type type) + { + if (base.HasCompatibleType(type)) + { + return true; + } + + if (this != _rootDictionaryMember) + { + return false; + } + + if (type == typeof(ExpandoObject)) + { + return Type == type; + } + + return type.IsDictionary(); + } + public DictionaryTargetMember Append(ParameterExpression key) { var memberKey = new DictionaryMemberKey(ValueType, key.Name, this); diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index 54afe03a7..9aaf29fe0 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -210,6 +210,8 @@ public bool IsRecursionRoot() public virtual bool GuardObjectValuePopulations => false; + public virtual bool HasCompatibleType(Type type) => Type.IsAssignableTo(type); + IQualifiedMember IQualifiedMember.GetElementMember() => this.GetElementMember(); IQualifiedMember IQualifiedMember.Append(Member childMember) => Append(childMember); @@ -257,8 +259,6 @@ IQualifiedMember IQualifiedMember.WithType(Type runtimeType) return typedMember; } - public bool HasCompatibleType(Type type) => Type.IsAssignableTo(type); - public QualifiedMember WithType(Type runtimeType) { if (runtimeType == Type) diff --git a/AgileMapper/Members/TypePairExtensions.cs b/AgileMapper/Members/TypePairExtensions.cs index 8c5bfb665..841dc9082 100644 --- a/AgileMapper/Members/TypePairExtensions.cs +++ b/AgileMapper/Members/TypePairExtensions.cs @@ -20,28 +20,18 @@ public static bool IsForTargetType(this ITypePair typePair, ITypePair otherTypeP public static bool HasCompatibleTypes( this ITypePair typePair, ITypePair otherTypePair, - Func sourceTypeMatcher = null) + Func sourceTypeMatcher = null, + Func targetTypeMatcher = null) { var sourceTypesMatch = typePair.IsForSourceType(otherTypePair.SourceType) || - (sourceTypeMatcher?.Invoke() == true); + (sourceTypeMatcher?.Invoke() == true); - if (!sourceTypesMatch) - { - return false; - } + var targetTypesMatch = + targetTypeMatcher?.Invoke() ?? + otherTypePair.TargetType.IsAssignableTo(typePair.TargetType); - if (otherTypePair.TargetType.IsAssignableTo(typePair.TargetType)) - { - return true; - } - - if (otherTypePair.TargetType.IsInterface()) - { - return Array.IndexOf(typePair.TargetType.GetAllInterfaces(), otherTypePair.TargetType) != -1; - } - - return false; + return sourceTypesMatch && targetTypesMatch; } } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index fe42c5900..8c305b70f 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -66,6 +66,11 @@ private static IEnumerable EnumerateAllTargetMembers(Obj yield return targetMember; } + if (mapperData.IsRoot) + { + yield break; + } + foreach (var targetMember in GetParentContextFlattenedTargetMembers(mapperData, targetDictionaryMember)) { yield return targetMember; diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index d237bcf2e..e31261834 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -332,7 +332,8 @@ public override bool HasCompatibleTypes(ITypePair typePair) { return typePair.HasCompatibleTypes( this, - () => SourceMember.HasCompatibleType(typePair.SourceType)); + () => SourceMember.HasCompatibleType(typePair.SourceType), + () => TargetMember.HasCompatibleType(typePair.TargetType)); } public IQualifiedMember GetSourceMemberFor(string targetMemberRegistrationName, int dataSourceIndex) From f63774feb402aa3c7a96861faf6e035731304f43 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 13:27:50 +0000 Subject: [PATCH 42/74] Support for mapping over nested ExpandoObject members --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenMappingOverDictionaryMembers.cs | 4 +-- .../Dynamics/WhenMappingOverDynamicMembers.cs | 31 +++++++++++++++++++ AgileMapper/Members/QualifiedMember.cs | 18 ++++++----- 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamicMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index def33c094..458bb9e35 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -111,6 +111,7 @@ + diff --git a/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs b/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs index 32185d380..779a707a8 100644 --- a/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs +++ b/AgileMapper.UnitTests/Dictionaries/WhenMappingOverDictionaryMembers.cs @@ -8,7 +8,7 @@ public class WhenMappingOverDictionaryMembers { [Fact] - public void ShouldOverwriteANestedSimpleTypedIDictionary() + public void ShouldOverwriteASimpleTypedIDictionary() { var source = new PublicField
{ @@ -25,7 +25,7 @@ public void ShouldOverwriteANestedSimpleTypedIDictionary() } [Fact] - public void ShouldOverwriteAComplexTypeArrayToANestedSameComplexTypeDictionary() + public void ShouldOverwriteAComplexTypeArrayToASameComplexTypeDictionary() { var source = new PublicField { diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamicMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamicMembers.cs new file mode 100644 index 000000000..110c4d53a --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamicMembers.cs @@ -0,0 +1,31 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Dynamic; + using Shouldly; + using TestClasses; + using Xunit; + + public class WhenMappingOverDynamicMembers + { + [Fact] + public void ShouldOverwriteASimpleTypeMember() + { + var source = new PublicField
+ { + Value = new Address { Line1 = "Here", Line2 = "There" } + }; + + dynamic targetDynamic = new ExpandoObject(); + + targetDynamic.Line1 = "La la la"; + + var target = new PublicProperty { Value = targetDynamic }; + + var result = Mapper.Map(source).Over(target); + + ((ExpandoObject)result.Value).ShouldBeSameAs((ExpandoObject)targetDynamic); + ((string)result.Value.Line1).ShouldBe("Here"); + ((string)result.Value.Line2).ShouldBe("There"); + } + } +} diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index 9aaf29fe0..c3490e570 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -221,12 +221,7 @@ public QualifiedMember Append(Member childMember) => _childMemberCache.GetOrAdd(childMember, CreateChildMember); protected virtual QualifiedMember CreateChildMember(Member childMember) - { - var qualifiedChildMember = new QualifiedMember(childMember, this, _mapperContext); - qualifiedChildMember = _mapperContext.QualifiedMemberFactory.GetFinalTargetMember(qualifiedChildMember); - - return qualifiedChildMember; - } + => CreateFinalMember(new QualifiedMember(childMember, this, _mapperContext)); public QualifiedMember GetChildMember(string registrationName) => _childMemberCache.Values.First(childMember => childMember.RegistrationName == registrationName); @@ -283,9 +278,18 @@ protected virtual QualifiedMember CreateRuntimeTypedMember(Type runtimeType) newMemberChain[MemberChain.Length - 1] = LeafMember.WithType(runtimeType); - return new QualifiedMember(newMemberChain, this); + return CreateFinalMember(new QualifiedMember(newMemberChain, this)); } + private QualifiedMember CreateFinalMember(QualifiedMember member) + { + return member.IsTargetMember + ? _mapperContext.QualifiedMemberFactory.GetFinalTargetMember(member) + : member; + } + + private bool IsTargetMember => MemberChain[0].Name == Member.RootTargetMemberName; + public bool CouldMatch(QualifiedMember otherMember) => JoinedNames.CouldMatch(otherMember.JoinedNames); public virtual bool Matches(IQualifiedMember otherMember) From 5dc1a34cfa183c39d031752ebfbb7b5af1eaf0fd Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 14:29:55 +0000 Subject: [PATCH 43/74] Beginning of dynamic configuration API / Ensuring dynamic configuration isn't applied to dictionaries --- .../WhenConfiguringSourceDynamicMapping.cs | 29 +++++++++++- ...mDictionaryMappingTargetMemberSpecifier.cs | 10 ++++- .../DictionaryMappingConfigContinuation.cs | 7 ++- .../DictionaryMappingConfigurator.cs | 12 +++-- .../ISourceDictionaryTargetTypeSelector.cs | 12 ++--- .../SourceDictionaryMappingConfigurator.cs | 22 ++++++++-- ...ISourceDynamicMappingConfigContinuation.cs | 15 +++++++ .../ISourceDynamicMappingConfigurator.cs | 44 +++++++++++++++++++ .../ISourceDynamicTargetTypeSelector.cs | 16 +++++++ .../MappingConfigStartingPoint.cs | 23 +++++++--- .../Configuration/CustomDictionaryKey.cs | 14 ++++-- .../Configuration/MappingConfigInfo.cs | 14 +++++- 12 files changed, 190 insertions(+), 28 deletions(-) create mode 100644 AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigContinuation.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index 922b2ec21..846c01e25 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics.Configuration { + using System.Collections.Generic; using System.Dynamic; using Shouldly; using TestClasses; @@ -8,12 +9,12 @@ public class WhenConfiguringSourceDynamicMapping { [Fact] - public void ShouldNotApplyDictionaryConfiguration() + public void ShouldNotApplyDictionaryConfigurationToDynamics() { using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .DictionariesWithValueType() + .FromDictionariesWithValueType() .To>() .MapFullKey("LaLaLa") .To(pf => pf.Value); @@ -29,5 +30,29 @@ public void ShouldNotApplyDictionaryConfiguration() result.Value.ShouldBe("2"); } } + + [Fact] + public void ShouldNotApplyDynamicConfigurationToDictionaries() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .To>() + .MapMember("LaLaLa") + .To(pf => pf.Value); + + var source = new Dictionary + { + ["LaLaLa"] = 1, + ["Value"] = 2 + }; + + var result = mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Value.ShouldBe("2"); + } + } } } diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs index 84f770d2e..7319f7074 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs @@ -4,6 +4,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using System.Linq.Expressions; using AgileMapper.Configuration; using DataSources; + using Dynamics; /// /// Provides options for specifying a target member to which a dictionary configuration should apply. @@ -12,8 +13,9 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries /// The type of values stored in the dictionary to which the configuration will apply. /// /// The target type to which the configuration should apply. - public class CustomDictionaryMappingTargetMemberSpecifier - : CustomDictionaryKeySpecifierBase + public class CustomDictionaryMappingTargetMemberSpecifier : + CustomDictionaryKeySpecifierBase, + ICustomDynamicMappingTargetMemberSpecifier { private readonly string _key; private readonly Action _dictionarySettingsAction; @@ -54,6 +56,10 @@ public ISourceDictionaryMappingConfigContinuation To>> targetSetMethod) => RegisterCustomKey(targetSetMethod); + ISourceDynamicMappingConfigContinuation ICustomDynamicMappingTargetMemberSpecifier.To( + Expression> targetMember) + => RegisterCustomKey(targetMember); + private DictionaryMappingConfigContinuation RegisterCustomKey(LambdaExpression targetMemberLambda) { var configuredKey = CustomDictionaryKey.ForTargetMember(_key, targetMemberLambda, ConfigInfo); diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs index 0601dac7d..ba440a571 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs @@ -1,10 +1,12 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using AgileMapper.Configuration; + using Dynamics; internal class DictionaryMappingConfigContinuation : ISourceDictionaryMappingConfigContinuation, - ITargetDictionaryMappingConfigContinuation + ITargetDictionaryMappingConfigContinuation, + ISourceDynamicMappingConfigContinuation { private readonly MappingConfigInfo _configInfo; @@ -18,5 +20,8 @@ ISourceDictionaryMappingConfigurator ISourceDictionaryMappingCo ITargetDictionaryMappingConfigurator ITargetDictionaryMappingConfigContinuation.And => new TargetDictionaryMappingConfigurator(_configInfo.Clone()); + + ISourceDynamicMappingConfigurator ISourceDynamicMappingConfigContinuation.And + => new SourceDictionaryMappingConfigurator(_configInfo.Clone()); } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 2d800a9a7..e936f3578 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -1,16 +1,18 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using AgileMapper.Configuration; + using Dynamics; internal class DictionaryMappingConfigurator : IGlobalDictionarySettings, - ISourceDictionaryTargetTypeSelector + ISourceDictionaryTargetTypeSelector, + ISourceDynamicTargetTypeSelector { private readonly MappingConfigInfo _configInfo; internal DictionaryMappingConfigurator(MappingConfigInfo configInfo) { - _configInfo = configInfo.Set(DictionaryType.Dictionary); + _configInfo = configInfo; if (_configInfo.SourceValueType == null) { @@ -81,6 +83,9 @@ MappingConfigStartingPoint IGlobalDictionarySettings.AndWhenMapping public ISourceDictionaryMappingConfigurator To() => CreateConfigurator(_configInfo.ForAllRuleSets()); + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.To() + => CreateConfigurator(_configInfo.ForAllRuleSets()); + public ISourceDictionaryMappingConfigurator ToANew() => CreateConfigurator(Constants.CreateNew); @@ -93,7 +98,8 @@ public ISourceDictionaryMappingConfigurator Over() private ISourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) => CreateConfigurator(_configInfo.ForRuleSet(ruleSetName)); - private static ISourceDictionaryMappingConfigurator CreateConfigurator(MappingConfigInfo configInfo) + private static SourceDictionaryMappingConfigurator CreateConfigurator( + MappingConfigInfo configInfo) => new SourceDictionaryMappingConfigurator(configInfo); } } diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs index 1600fc968..2b4d0d185 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryTargetTypeSelector.cs @@ -1,15 +1,15 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { /// - /// Provides options for specifying the type of dictionary mapping to perform. + /// Provides options for specifying the type of Dictionary mapping to perform. /// /// - /// The type of values stored in the dictionary to which the configurations will apply. + /// The type of values stored in the Dictionary to which the configurations will apply. /// public interface ISourceDictionaryTargetTypeSelector : ISourceDictionarySettings { /// - /// Configure how this mapper performs mappings from dictionaries in all MappingRuleSets + /// Configure how this mapper performs mappings from Dictionaries in all MappingRuleSets /// (create new, overwrite, etc), to the target type specified by the type argument. /// /// The target type to which the configuration will apply. @@ -17,7 +17,7 @@ public interface ISourceDictionaryTargetTypeSelector : ISourceDictionary ISourceDictionaryMappingConfigurator To(); /// - /// Configure how this mapper performs object creation mappings from dictionaries to the target type + /// Configure how this mapper performs object creation mappings from Dictionaries to the target type /// specified by the type argument. /// /// The target type to which the configuration will apply. @@ -25,7 +25,7 @@ public interface ISourceDictionaryTargetTypeSelector : ISourceDictionary ISourceDictionaryMappingConfigurator ToANew(); /// - /// Configure how this mapper performs OnTo (merge) mappings from dictionaries to the target type + /// Configure how this mapper performs OnTo (merge) mappings from Dictionaries to the target type /// specified by the type argument. /// /// The target type to which the configuration will apply. @@ -33,7 +33,7 @@ public interface ISourceDictionaryTargetTypeSelector : ISourceDictionary ISourceDictionaryMappingConfigurator OnTo(); /// - /// Configure how this mapper performs Over (overwrite) mappings from dictionaries to the target type + /// Configure how this mapper performs Over (overwrite) mappings from Dictionaries to the target type /// specified by the type argument. /// /// The target type to which the configuration will apply. diff --git a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs index 52c9a1079..b4353c69e 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs @@ -2,11 +2,14 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using System; using System.Collections.Generic; + using System.Linq.Expressions; using AgileMapper.Configuration; + using Dynamics; internal class SourceDictionaryMappingConfigurator : DictionaryMappingConfiguratorBase, TTarget>, - ISourceDictionaryMappingConfigurator + ISourceDictionaryMappingConfigurator, + ISourceDynamicMappingConfigurator { public SourceDictionaryMappingConfigurator(MappingConfigInfo configInfo) : base(configInfo) @@ -33,16 +36,29 @@ ISourceDictionaryMappingConfigurator ISourceDictionaryConfigSet #endregion public CustomDictionaryMappingTargetMemberSpecifier MapFullKey(string fullMemberNameKey) - => CreateTargetMemberSpecifier(fullMemberNameKey, "keys", (settings, customKey) => settings.AddFullKey(customKey)); + { + return CreateTargetMemberSpecifier( + fullMemberNameKey, + "keys", + (settings, customKey) => settings.AddFullKey(customKey)); + } public CustomDictionaryMappingTargetMemberSpecifier MapMemberNameKey(string memberNameKeyPart) { return CreateTargetMemberSpecifier( memberNameKeyPart, - "member names", + "member name", (settings, customKey) => settings.AddMemberKey(customKey)); } + public ICustomDynamicMappingTargetMemberSpecifier MapMember(string sourceMemberName) + { + return CreateTargetMemberSpecifier( + sourceMemberName, + "member name", + (settings, customKey) => settings.AddFullKey(customKey)); + } + private CustomDictionaryMappingTargetMemberSpecifier CreateTargetMemberSpecifier( string key, string keyName, diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigContinuation.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigContinuation.cs new file mode 100644 index 000000000..75f6e78dd --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigContinuation.cs @@ -0,0 +1,15 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Enables chaining of configurations for an ExpandoObject to the same target type. + /// + /// The target type to which the configuration should apply. + public interface ISourceDynamicMappingConfigContinuation + { + /// + /// Perform another configuration of how this mapper maps from an ExpandoObject to the target type + /// being configured. This property exists purely to provide a more fluent configuration interface. + /// + ISourceDynamicMappingConfigurator And { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs new file mode 100644 index 000000000..f985c0ea9 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs @@ -0,0 +1,44 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + using System; + using System.Linq.Expressions; + + /// + /// Provides options for configuring mappings from an ExpandoObject to a given . + /// + /// The target type to which the configuration should apply. + public interface ISourceDynamicMappingConfigurator + { + /// + /// Configure a custom source member for a particular target member when mapping from an ExpandoObject + /// to the target type being configured. + /// + /// + /// The name of the source member from which to retrieve the value to map to the configured target member. + /// + /// + /// An ICustomDynamicMappingTargetMemberSpecifier with which to specify the target member for which the + /// member with the given should be used. + /// + ICustomDynamicMappingTargetMemberSpecifier MapMember(string sourceMemberName); + } + + /// + /// Provides options for specifying a target member to which an ExpandoObject configuration should apply. + /// + /// The target type to which the configuration should apply. + public interface ICustomDynamicMappingTargetMemberSpecifier + { + /// + /// Apply the configuration to the given . + /// + /// The target member's type. + /// The target member to which to apply the configuration. + /// + /// An ISourceDynamicMappingConfigContinuation to enable further configuration of mappings from + /// Dynamics to the target type being configured. + /// + ISourceDynamicMappingConfigContinuation To( + Expression> targetMember); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs new file mode 100644 index 000000000..68dc83be6 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs @@ -0,0 +1,16 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Provides options for specifying the type of ExpandoObject mapping to perform. + /// + public interface ISourceDynamicTargetTypeSelector + { + /// + /// Configure how this mapper performs mappings from ExpandoObjects in all MappingRuleSets + /// (create new, overwrite, etc), to the target type specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDynamicMappingConfigurator with which to complete the configuration. + ISourceDynamicMappingConfigurator To(); + } +} diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index 9d997df75..fe970bf62 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -6,8 +6,10 @@ using System.Reflection; using AgileMapper.Configuration; using Dictionaries; + using Dynamics; using Extensions.Internal; using Members; + using static Dictionaries.DictionaryType; /// /// Provides options for configuring how a mapper performs a mapping. @@ -345,7 +347,7 @@ public InstanceConfigurator InstancesOf() where TObject : clas /// with any Dictionary value type. /// public IGlobalDictionarySettings Dictionaries - => CreateDictionaryConfigurator(sourceValueType: Constants.AllTypes); + => CreateDictionaryConfigurator(Dictionary, sourceValueType: Constants.AllTypes); /// /// Configure how this mapper performs mappings from or to source Dictionary{string, TValue} instances. @@ -357,14 +359,14 @@ public IGlobalDictionarySettings Dictionaries /// An IGlobalDictionarySettings with which to continue other global aspects of Dictionary mapping. /// public IGlobalDictionarySettings DictionariesWithValueType() - => CreateDictionaryConfigurator(); + => CreateDictionaryConfigurator(Dictionary); /// /// Configure how this mapper performs mappings from source Dictionary instances with /// any Dictionary value type. /// public ISourceDictionaryTargetTypeSelector FromDictionaries - => CreateDictionaryConfigurator(sourceValueType: Constants.AllTypes); + => CreateDictionaryConfigurator(Dictionary, sourceValueType: Constants.AllTypes); /// /// Configure how this mapper performs mappings from source Dictionary{string, TValue} instances. @@ -377,13 +379,22 @@ public ISourceDictionaryTargetTypeSelector FromDictionaries /// configuration will apply. /// public ISourceDictionaryTargetTypeSelector FromDictionariesWithValueType() - => CreateDictionaryConfigurator(); + => CreateDictionaryConfigurator(Dictionary); - private DictionaryMappingConfigurator CreateDictionaryConfigurator(Type sourceValueType = null) + /// + /// Configure how this mapper performs mappings from source ExpandoObject instances. + /// + public ISourceDynamicTargetTypeSelector FromDynamics + => CreateDictionaryConfigurator(ExpandoObject, sourceValueType: Constants.AllTypes); + + private DictionaryMappingConfigurator CreateDictionaryConfigurator( + DictionaryType dictionaryType, + Type sourceValueType = null) { var configInfo = _configInfo .ForAllSourceTypes() - .ForSourceValueType(sourceValueType ?? typeof(TValue)); + .ForSourceValueType(sourceValueType ?? typeof(TValue)) + .Set(dictionaryType); return new DictionaryMappingConfigurator(configInfo); } diff --git a/AgileMapper/Configuration/CustomDictionaryKey.cs b/AgileMapper/Configuration/CustomDictionaryKey.cs index d0e2c6143..6b5d4b287 100644 --- a/AgileMapper/Configuration/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/CustomDictionaryKey.cs @@ -50,9 +50,7 @@ public static CustomDictionaryKey ForTargetMember( public string Key { get; } public string GetConflictMessage(ConfiguredDataSourceFactory conflictingDataSource) - { - return $"Configured dictionary key member {TargetMember.GetPath()} has a configured data source"; - } + => $"Configured dictionary key member {TargetMember.GetPath()} has a configured data source"; public bool AppliesTo(Member member, IMemberMapperData mapperData) { @@ -67,12 +65,20 @@ public bool AppliesTo(Member member, IMemberMapperData mapperData) return false; } - if ((ConfigInfo.Get() != DictionaryType.ExpandoObject) && + var applicableDictionaryType = ConfigInfo.Get(); + + if ((applicableDictionaryType != DictionaryType.ExpandoObject) && (mapperData.SourceMember.GetFriendlyTypeName() == nameof(ExpandoObject))) { return false; } + if ((applicableDictionaryType == DictionaryType.ExpandoObject) && + (mapperData.SourceMember.GetFriendlyTypeName() != nameof(ExpandoObject))) + { + return false; + } + if (_sourceMember == null) { return true; diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index eccc4ea1e..7cdaf885f 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -198,13 +198,25 @@ public IBasicMapperData ToMapperData() public MappingConfigInfo Clone() { - return new MappingConfigInfo(MapperContext) + var cloned = new MappingConfigInfo(MapperContext) { SourceType = SourceType, TargetType = TargetType, SourceValueType = SourceValueType, RuleSet = RuleSet }; + + if (_data == null) + { + return cloned; + } + + foreach (var itemByType in _data) + { + cloned.Data.Add(itemByType.Key, itemByType.Value); + } + + return cloned; } private class TypeTestFinder : ExpressionVisitor From 36f70a7323409a24a36ab4047ed47e4c777aa88e Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 15:48:17 +0000 Subject: [PATCH 44/74] Support for configuring creation-mapping-specific custom dynamic source members --- ...ConfiguringDictionaryMappingIncorrectly.cs | 2 +- .../WhenConfiguringSourceDynamicMapping.cs | 27 +++++++++++++++++++ .../DictionaryMappingConfigurator.cs | 5 +++- .../ISourceDynamicTargetTypeSelector.cs | 8 ++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs index 26e93753b..bbdf29d48 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs @@ -166,7 +166,7 @@ public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedGlobally() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .Dictionaries + .FromDictionaries .UseFlattenedTargetMemberNames() .UseMemberNameSeparator("+"); } diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index 846c01e25..4eff36978 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -8,6 +8,33 @@ public class WhenConfiguringSourceDynamicMapping { + [Fact] + public void ShouldUseCustomDynamicSourceMemberName() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .ToANew>() + .MapMember("LaLaLa") + .To(pf => pf.Value); + + dynamic source = new ExpandoObject(); + + source.LaLaLa = 1; + source.Value = 2; + + var result = (PublicField)mapper.Map(source).ToANew>(); + + result.ShouldNotBeNull(); + result.Value.ShouldBe(1); + + mapper.Map(source).Over(result); + + result.Value.ShouldBe(2); + } + } + [Fact] public void ShouldNotApplyDictionaryConfigurationToDynamics() { diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index e936f3578..be8b7c179 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -89,13 +89,16 @@ ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.To ToANew() => CreateConfigurator(Constants.CreateNew); + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.ToANew() + => CreateConfigurator(Constants.CreateNew); + public ISourceDictionaryMappingConfigurator OnTo() => CreateConfigurator(Constants.Merge); public ISourceDictionaryMappingConfigurator Over() => CreateConfigurator(Constants.Overwrite); - private ISourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) + private SourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) => CreateConfigurator(_configInfo.ForRuleSet(ruleSetName)); private static SourceDictionaryMappingConfigurator CreateConfigurator( diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs index 68dc83be6..d89801598 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs @@ -12,5 +12,13 @@ public interface ISourceDynamicTargetTypeSelector /// The target type to which the configuration will apply. /// An ISourceDynamicMappingConfigurator with which to complete the configuration. ISourceDynamicMappingConfigurator To(); + + /// + /// Configure how this mapper performs object creation mappings from ExpandoObjects to the target type + /// specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDynamicMappingConfigurator with which to complete the configuration. + ISourceDynamicMappingConfigurator ToANew(); } } From 814c0f22818b2124da1d5d1850da5072a94cd400 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 15:54:45 +0000 Subject: [PATCH 45/74] Test coverage for mapping onto a nested dynamic from a nested Dictionary --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenConfiguringSourceDynamicMapping.cs | 2 +- .../Dynamics/WhenMappingOnToDynamicMembers.cs | 37 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 458bb9e35..22b0466d7 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -111,6 +111,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index 4eff36978..bd0a130ad 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -9,7 +9,7 @@ public class WhenConfiguringSourceDynamicMapping { [Fact] - public void ShouldUseCustomDynamicSourceMemberName() + public void ShouldUseACustomDynamicSourceMemberName() { using (var mapper = Mapper.CreateNew()) { diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs new file mode 100644 index 000000000..c34257d01 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs @@ -0,0 +1,37 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System; + using System.Collections.Generic; + using System.Dynamic; + using TestClasses; + using Xunit; + + public class WhenMappingOnToDynamicMembers + { + [Fact] + public void ShouldMapFromANestedSimpleTypedDictionary() + { + var guidOne = Guid.NewGuid(); + var guidTwo = Guid.NewGuid(); + + var source = new PublicProperty> + { + Value = new Dictionary { ["ONEah-ah-ah"] = guidOne, ["TWOah-ah-ah"] = guidTwo } + }; + + dynamic targetDynamic = new ExpandoObject(); + + targetDynamic.ONEah_ah_ah = guidOne; + targetDynamic.TWOah_ah_ah = guidTwo; + targetDynamic.THREEah_ah_ah = "gibblets"; + + var target = new PublicField { Value = targetDynamic }; + + Mapper.Map(source).Over(target); + + ((Guid?)target.Value.ONEah_ah_ah).ShouldBe(guidOne); + ((Guid?)target.Value.TWOah_ah_ah).ShouldBe(guidTwo); + ((string)target.Value.THREEah_ah_ah).ShouldBe("gibblets"); + } + } +} From ddc43fcaeff37ecbf92d8d6f3585d7e9cf12665a Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 16:39:32 +0000 Subject: [PATCH 46/74] Support for mapping from complex type enumerables onto nested target dynamics / Fixing infinite loop when mapping from a derived type in a collection to a dynamic (or dictionary) --- .../Dynamics/WhenMappingOnToDynamicMembers.cs | 43 ++++++++++++++++++- .../Configuration/DictionarySettings.cs | 5 ++- .../Configuration/ElementKeyPartFactory.cs | 12 +++++- .../Configuration/JoiningNameFactory.cs | 2 +- .../DictionaryPopulationBuilder.cs | 3 +- .../EnumerablePopulationBuilder.cs | 2 + .../Enumerables/IPopulationLoopData.cs | 2 +- 7 files changed, 62 insertions(+), 7 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs index c34257d01..a38c20a0e 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOnToDynamicMembers.cs @@ -3,13 +3,15 @@ using System; using System.Collections.Generic; using System.Dynamic; + using Microsoft.CSharp.RuntimeBinder; + using Shouldly; using TestClasses; using Xunit; public class WhenMappingOnToDynamicMembers { [Fact] - public void ShouldMapFromANestedSimpleTypedDictionary() + public void ShouldMergeFromANestedSimpleTypedDictionary() { var guidOne = Guid.NewGuid(); var guidTwo = Guid.NewGuid(); @@ -33,5 +35,44 @@ public void ShouldMapFromANestedSimpleTypedDictionary() ((Guid?)target.Value.TWOah_ah_ah).ShouldBe(guidTwo); ((string)target.Value.THREEah_ah_ah).ShouldBe("gibblets"); } + + [Fact] + public void ShouldMapFromANestedComplexTypeEnumerableOnToFlattenedMembers() + { + var source = new PublicField + { + Value = new[] + { + new ProductDto { ProductId = "p-1", Price = 10.00m }, + new ProductDtoMega { ProductId = "p-m", Price = 100.00m, HowMega = "OH SO" }, + new ProductDto { ProductId = "p-2", Price = 1.99m } + } + }; + + dynamic targetDynamic = new ExpandoObject(); + + targetDynamic._0__ProductId = default(string); + targetDynamic._0__Price = default(double?); + targetDynamic._0__HowMega = "UBER"; + + targetDynamic._1__ProductId = "p-m1"; + targetDynamic._1__Price = default(int?); + + var target = new PublicField { Value = targetDynamic }; + + Mapper.Map(source).OnTo(target); + + ((string)targetDynamic._0__ProductId).ShouldBe("p-1"); + ((decimal)targetDynamic._0__Price).ShouldBe(10.00m); + ((string)targetDynamic._0__HowMega).ShouldBe("UBER"); + + ((string)targetDynamic._1__ProductId).ShouldBe("p-m1"); + ((decimal)targetDynamic._1__Price).ShouldBe(100.00m); + ((string)targetDynamic._1__HowMega).ShouldBe("OH SO"); + + ((string)targetDynamic._2__ProductId).ShouldBe("p-2"); + ((decimal)targetDynamic._2__Price).ShouldBe(1.99m); + Should.Throw(() => targetDynamic.Value_2_HowMega); + } } } diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/DictionarySettings.cs index 860190a96..158b69985 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/DictionarySettings.cs @@ -21,13 +21,14 @@ public DictionarySettings(MapperContext mapperContext) _joiningNameFactories = new List { - JoiningNameFactory.UnderscoredForDynamics(mapperContext), + JoiningNameFactory.UnderscoredForTargetDynamics(mapperContext), JoiningNameFactory.Dotted(mapperContext) }; _elementKeyPartFactories = new List { - ElementKeyPartFactory.UnderscoredIndexForDynamics(mapperContext), + ElementKeyPartFactory.UnderscoredIndexForSourceDynamics(mapperContext), + ElementKeyPartFactory.UnderscoredIndexForTargetDynamics(mapperContext), ElementKeyPartFactory.SquareBracketedIndex(mapperContext) }; } diff --git a/AgileMapper/Configuration/ElementKeyPartFactory.cs b/AgileMapper/Configuration/ElementKeyPartFactory.cs index b7eb7fe05..151023501 100644 --- a/AgileMapper/Configuration/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/ElementKeyPartFactory.cs @@ -31,7 +31,17 @@ private ElementKeyPartFactory( #region Factory Methods - public static ElementKeyPartFactory UnderscoredIndexForDynamics(MapperContext mapperContext) + public static ElementKeyPartFactory UnderscoredIndexForSourceDynamics(MapperContext mapperContext) + { + var sourceExpandoObject = new MappingConfigInfo(mapperContext) + .ForAllRuleSets() + .ForAllSourceTypes() + .ForTargetType(); + + return new ElementKeyPartFactory("_", "_", sourceExpandoObject); + } + + public static ElementKeyPartFactory UnderscoredIndexForTargetDynamics(MapperContext mapperContext) { var sourceExpandoObject = new MappingConfigInfo(mapperContext) .ForAllRuleSets() diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/JoiningNameFactory.cs index 1048298d0..968fc99f9 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/JoiningNameFactory.cs @@ -31,7 +31,7 @@ private JoiningNameFactory( #region Factory Methods - public static JoiningNameFactory UnderscoredForDynamics(MapperContext mapperContext) + public static JoiningNameFactory UnderscoredForTargetDynamics(MapperContext mapperContext) { var targetExpandoObject = new MappingConfigInfo(mapperContext) .ForAllRuleSets() diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 15a133a9e..4bf71bb47 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -138,8 +138,9 @@ private Expression AssignDictionaryEntry( var derivedTypeMapping = GetDerivedTypeMapping(derivedSourceCheck, mappingData); var derivedTypePopulation = GetPopulation(derivedTypeMapping, dictionaryEntryMember, mappingData); + var incrementCounter = _wrappedBuilder.GetCounterIncrement(); var mapNextElement = Expression.Continue(loopData.ContinueLoopTarget); - var derivedMappingBlock = Expression.Block(derivedTypePopulation, mapNextElement); + var derivedMappingBlock = Expression.Block(derivedTypePopulation, incrementCounter, mapNextElement); var ifDerivedTypeReturn = Expression.IfThen(derivedSourceCheck.TypeCheck, derivedMappingBlock); mappingExpressions.Add(ifDerivedTypeReturn); diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 81de79d65..423f3791a 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -78,6 +78,8 @@ public static implicit operator BlockExpression(EnumerablePopulationBuilder buil #endregion + public Expression GetCounterIncrement() => Expression.PreIncrementAssign(Counter); + public ParameterExpression Counter => _counterVariable ?? (_counterVariable = GetCounterVariable()); private ParameterExpression GetCounterVariable() diff --git a/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs index cdcb6ea98..277ddcf91 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs @@ -36,7 +36,7 @@ public static Expression BuildPopulationLoop( var loopBody = Expression.Block( Expression.IfThen(loopData.LoopExitCheck, breakLoop), elementPopulation, - Expression.PreIncrementAssign(builder.Counter)); + builder.GetCounterIncrement()); var populationLoop = loopData.NeedsContinueTarget ? Expression.Loop(loopBody, breakLoop.Target, loopData.ContinueLoopTarget) From 70197434f833d4681c48ffdb93eb464ec3322bc4 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 16:51:48 +0000 Subject: [PATCH 47/74] Support for configuring custom member names for source dynamic mappings --- .../WhenConfiguringSourceDynamicMapping.cs | 28 +++++++++++++++++++ .../DictionaryMappingConfigurator.cs | 3 ++ .../SourceDictionaryMappingConfigurator.cs | 11 +++----- ...stomDynamicMappingTargetMemberSpecifier.cs | 24 ++++++++++++++++ .../ISourceDynamicMappingConfigurator.cs | 26 ++++++----------- .../ISourceDynamicTargetTypeSelector.cs | 8 ++++++ 6 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 AgileMapper/Api/Configuration/Dynamics/ICustomDynamicMappingTargetMemberSpecifier.cs diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index bd0a130ad..42185887d 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -35,6 +35,34 @@ public void ShouldUseACustomDynamicSourceMemberName() } } + [Fact] + public void ShouldUseCustomDynamicMemberNameForRootMembers() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .Over
() + .MapMemberName("HouseNumber") + .To(a => a.Line1) + .And + .MapMemberName("Street") + .To(a => a.Line2); + + dynamic source = new ExpandoObject(); + + source.HouseNumber = 10; + source.Street = "Street Road"; + + var target = new Address { Line1 = "??", Line2 = "??" }; + + mapper.Map(source).Over(target); + + target.Line1.ShouldBe("10"); + target.Line2.ShouldBe("Street Road"); + } + } + [Fact] public void ShouldNotApplyDictionaryConfigurationToDynamics() { diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index be8b7c179..844380ab8 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -98,6 +98,9 @@ public ISourceDictionaryMappingConfigurator OnTo() public ISourceDictionaryMappingConfigurator Over() => CreateConfigurator(Constants.Overwrite); + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.Over() + => CreateConfigurator(Constants.Overwrite); + private SourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) => CreateConfigurator(_configInfo.ForRuleSet(ruleSetName)); diff --git a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs index b4353c69e..b669f4089 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs @@ -2,7 +2,6 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using System; using System.Collections.Generic; - using System.Linq.Expressions; using AgileMapper.Configuration; using Dynamics; @@ -52,12 +51,10 @@ public CustomDictionaryMappingTargetMemberSpecifier MapMemberNa } public ICustomDynamicMappingTargetMemberSpecifier MapMember(string sourceMemberName) - { - return CreateTargetMemberSpecifier( - sourceMemberName, - "member name", - (settings, customKey) => settings.AddFullKey(customKey)); - } + => MapFullKey(sourceMemberName); + + public ICustomDynamicMappingTargetMemberSpecifier MapMemberName(string memberNamePart) + => MapMemberNameKey(memberNamePart); private CustomDictionaryMappingTargetMemberSpecifier CreateTargetMemberSpecifier( string key, diff --git a/AgileMapper/Api/Configuration/Dynamics/ICustomDynamicMappingTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/Dynamics/ICustomDynamicMappingTargetMemberSpecifier.cs new file mode 100644 index 000000000..280fa4c93 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ICustomDynamicMappingTargetMemberSpecifier.cs @@ -0,0 +1,24 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + using System; + using System.Linq.Expressions; + + /// + /// Provides options for specifying a target member to which an ExpandoObject configuration should apply. + /// + /// The target type to which the configuration should apply. + public interface ICustomDynamicMappingTargetMemberSpecifier + { + /// + /// Apply the configuration to the given . + /// + /// The target member's type. + /// The target member to which to apply the configuration. + /// + /// An ISourceDynamicMappingConfigContinuation to enable further configuration of mappings from + /// Dynamics to the target type being configured. + /// + ISourceDynamicMappingConfigContinuation To( + Expression> targetMember); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs index f985c0ea9..b114d77e6 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs @@ -1,8 +1,5 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics { - using System; - using System.Linq.Expressions; - /// /// Provides options for configuring mappings from an ExpandoObject to a given . /// @@ -21,24 +18,19 @@ public interface ISourceDynamicMappingConfigurator /// member with the given should be used. /// ICustomDynamicMappingTargetMemberSpecifier MapMember(string sourceMemberName); - } - /// - /// Provides options for specifying a target member to which an ExpandoObject configuration should apply. - /// - /// The target type to which the configuration should apply. - public interface ICustomDynamicMappingTargetMemberSpecifier - { /// - /// Apply the configuration to the given . + /// Configure a custom member name to use in a key for a particular target member when mapping from an + /// ExpandoObject to the target type being configured. For example, to map the member "Address.HouseName" + /// to a 'Line1' member of an 'Address' member, use MapMemberName("HouseName").To(a => a.Line1). /// - /// The target member's type. - /// The target member to which to apply the configuration. + /// + /// The custom member name to use in a key with which to retrieve the value to map to the configured target member. + /// /// - /// An ISourceDynamicMappingConfigContinuation to enable further configuration of mappings from - /// Dynamics to the target type being configured. + /// A CustomDictionaryMappingTargetMemberSpecifier with which to specify the target member for which the custom + /// member name should be used. /// - ISourceDynamicMappingConfigContinuation To( - Expression> targetMember); + ICustomDynamicMappingTargetMemberSpecifier MapMemberName(string memberNamePart); } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs index d89801598..02d529183 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs @@ -20,5 +20,13 @@ public interface ISourceDynamicTargetTypeSelector /// The target type to which the configuration will apply. /// An ISourceDynamicMappingConfigurator with which to complete the configuration. ISourceDynamicMappingConfigurator ToANew(); + + /// + /// Configure how this mapper performs Over (overwrite) mappings from ExpandoObjects to the target + /// type specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDynamicMappingConfigurator with which to complete the configuration. + ISourceDynamicMappingConfigurator Over(); } } From 7b8430100c41dec40aba1c8ef3b96a215fa6118c Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 16 Dec 2017 22:56:47 +0000 Subject: [PATCH 48/74] Support for mapping from dynamics with configured member name parts / Properly handling flattened member names in StringExtensions.KeyMatches overloads --- .../WhenConfiguringSourceDynamicMapping.cs | 36 ++++++++ .../DictionaryMappingConfigurator.cs | 3 + .../ISourceDynamicTargetTypeSelector.cs | 8 ++ .../Configuration/CustomDictionaryKey.cs | 19 +++- .../Configuration/DictionarySettings.cs | 11 ++- .../Configuration/ElementKeyPartFactory.cs | 24 +++-- .../Configuration/JoiningNameFactory.cs | 14 +++ .../DictionaryEntryVariablePair.cs | 13 ++- .../Internal/StringExpressionExtensions.cs | 45 +++++++++- .../Extensions/Internal/StringExtensions.cs | 89 +++++++++++++++++-- .../DictionaryMemberMapperDataExtensions.cs | 20 ++++- 11 files changed, 263 insertions(+), 19 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index 42185887d..d856ed2c9 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using System.Dynamic; + using System.Linq; using Shouldly; using TestClasses; using Xunit; @@ -63,6 +64,41 @@ public void ShouldUseCustomDynamicMemberNameForRootMembers() } } + [Fact] + public void ShouldApplyACustomMemberNamePartsToASpecificTargetType() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .OnTo
() + .MapMemberName("StreetName") + .To(a => a.Line1) + .And + .MapMemberName("CityName") + .To(a => a.Line2); + + dynamic source = new ExpandoObject(); + + source.Value_0__StreetName = "Street Zero"; + source.Value_0__CityName = "City Zero"; + source.Value_1__StreetName = "Street One"; + source.Value_1__CityName = "City One"; + + var target = new PublicField> { Value = new List
() }; + + mapper.Map(source).OnTo(target); + + target.Value.Count.ShouldBe(2); + + target.Value.First().Line1.ShouldBe("Street Zero"); + target.Value.First().Line2.ShouldBe("City Zero"); + + target.Value.Second().Line1.ShouldBe("Street One"); + target.Value.Second().Line2.ShouldBe("City One"); + } + } + [Fact] public void ShouldNotApplyDictionaryConfigurationToDynamics() { diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 844380ab8..9b14f2321 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -95,6 +95,9 @@ ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.ToAN public ISourceDictionaryMappingConfigurator OnTo() => CreateConfigurator(Constants.Merge); + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.OnTo() + => CreateConfigurator(Constants.Merge); + public ISourceDictionaryMappingConfigurator Over() => CreateConfigurator(Constants.Overwrite); diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs index 02d529183..8ad361c25 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs @@ -21,6 +21,14 @@ public interface ISourceDynamicTargetTypeSelector /// An ISourceDynamicMappingConfigurator with which to complete the configuration. ISourceDynamicMappingConfigurator ToANew(); + /// + /// Configure how this mapper performs OnTo (merge) mappings from ExpandoObjects to the target + /// type specified by the type argument. + /// + /// The target type to which the configuration will apply. + /// An ISourceDynamicMappingConfigurator with which to complete the configuration. + ISourceDynamicMappingConfigurator OnTo(); + /// /// Configure how this mapper performs Over (overwrite) mappings from ExpandoObjects to the target /// type specified by the type argument. diff --git a/AgileMapper/Configuration/CustomDictionaryKey.cs b/AgileMapper/Configuration/CustomDictionaryKey.cs index 6b5d4b287..609964ed2 100644 --- a/AgileMapper/Configuration/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/CustomDictionaryKey.cs @@ -68,13 +68,13 @@ public bool AppliesTo(Member member, IMemberMapperData mapperData) var applicableDictionaryType = ConfigInfo.Get(); if ((applicableDictionaryType != DictionaryType.ExpandoObject) && - (mapperData.SourceMember.GetFriendlyTypeName() == nameof(ExpandoObject))) + IsPartOfExpandoObjectMapping(mapperData)) { return false; } if ((applicableDictionaryType == DictionaryType.ExpandoObject) && - (mapperData.SourceMember.GetFriendlyTypeName() != nameof(ExpandoObject))) + !IsPartOfExpandoObjectMapping(mapperData)) { return false; } @@ -89,6 +89,21 @@ public bool AppliesTo(Member member, IMemberMapperData mapperData) return _sourceMember.Matches(targetMember); } + private static bool IsPartOfExpandoObjectMapping(IMemberMapperData mapperData) + { + while (mapperData != null) + { + if (mapperData.SourceMember.GetFriendlyTypeName() == nameof(ExpandoObject)) + { + return true; + } + + mapperData = mapperData.Parent; + } + + return false; + } + private QualifiedMember GetTargetMember(Member member, IBasicMapperData mapperData) { if (mapperData.TargetMember.LeafMember == member) diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/DictionarySettings.cs index 158b69985..d0965994c 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/DictionarySettings.cs @@ -21,6 +21,7 @@ public DictionarySettings(MapperContext mapperContext) _joiningNameFactories = new List { + JoiningNameFactory.UnderscoredForSourceDynamics(mapperContext), JoiningNameFactory.UnderscoredForTargetDynamics(mapperContext), JoiningNameFactory.Dotted(mapperContext) }; @@ -102,6 +103,9 @@ private void ThrowIfConflictingJoiningNameFactoryExists(JoiningNameFactory joini conflictingJoiningName.SeparatorDescription)); } + public Expression GetSeparator(IMemberMapperData mapperData) + => _joiningNameFactories.FindMatch(mapperData).Separator; + public Expression GetJoiningName(Member member, IMemberMapperData mapperData) => _joiningNameFactories.FindMatch(mapperData).GetJoiningName(member, mapperData); @@ -110,11 +114,14 @@ public void Add(ElementKeyPartFactory keyPartFactory) _elementKeyPartFactories.Insert(0, keyPartFactory); } + public Expression GetElementKeyPartMatcher(IBasicMapperData mapperData) + => _elementKeyPartFactories.FindMatch(mapperData).GetElementKeyPartMatcher(); + public Expression GetElementKeyPrefixOrNull(IBasicMapperData mapperData) => _elementKeyPartFactories.FindMatch(mapperData).GetElementKeyPrefixOrNull(); - public IEnumerable GetElementKeyParts(Expression index, IBasicMapperData mapperData) - => _elementKeyPartFactories.FindMatch(mapperData).GetElementKeyParts(index); + public IList GetElementKeyParts(Expression index, IBasicMapperData mapperData) + => _elementKeyPartFactories.FindMatch(mapperData).GetElementKeyParts(index).ToArray(); public void CloneTo(DictionarySettings dictionaries) { diff --git a/AgileMapper/Configuration/ElementKeyPartFactory.cs b/AgileMapper/Configuration/ElementKeyPartFactory.cs index 151023501..1a4992a78 100644 --- a/AgileMapper/Configuration/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/ElementKeyPartFactory.cs @@ -9,8 +9,9 @@ internal class ElementKeyPartFactory : UserConfiguredItemBase { - private readonly Expression _prefix; - private readonly Expression _suffix; + private readonly ConstantExpression _prefix; + private readonly ConstantExpression _suffix; + private Expression _keyPartMatcher; private ElementKeyPartFactory( string prefix, @@ -64,14 +65,14 @@ public static ElementKeyPartFactory For(string pattern, MappingConfigInfo config { if (string.IsNullOrWhiteSpace(pattern)) { - throw NewInvalidPatternException(); + throw InvalidPattern(); } var patternMatch = _patternMatcher.Match(pattern); if (!patternMatch.Success) { - throw NewInvalidPatternException(); + throw InvalidPattern(); } var prefix = patternMatch.Groups["Prefix"].Value; @@ -94,7 +95,7 @@ public static ElementKeyPartFactory For(string pattern, MappingConfigInfo config return new ElementKeyPartFactory(prefix, suffix, configInfo); } - private static MappingConfigurationException NewInvalidPatternException() + private static MappingConfigurationException InvalidPattern() { return new MappingConfigurationException( "An enumerable element key pattern must contain a single 'i' character " + @@ -103,6 +104,19 @@ private static MappingConfigurationException NewInvalidPatternException() #endregion + public Expression GetElementKeyPartMatcher() + => _keyPartMatcher ?? (_keyPartMatcher = CreateKeyPartRegex().ToConstantExpression()); + + private Regex CreateKeyPartRegex() + { + return new Regex( + _prefix?.Value + "[0-9]+" + _suffix?.Value +#if !NET_STANDARD + , RegexOptions.Compiled +#endif + ); + } + public Expression GetElementKeyPrefixOrNull() => _prefix; public IEnumerable GetElementKeyParts(Expression index) diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/JoiningNameFactory.cs index 968fc99f9..8fd415cf6 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/JoiningNameFactory.cs @@ -15,6 +15,7 @@ internal class JoiningNameFactory : UserConfiguredItemBase private readonly Type _targetType; private readonly bool _isDefault; private readonly bool _isGlobal; + private Expression _separatorConstant; private JoiningNameFactory( string separator, @@ -31,6 +32,16 @@ private JoiningNameFactory( #region Factory Methods + public static JoiningNameFactory UnderscoredForSourceDynamics(MapperContext mapperContext) + { + var sourceExpandoObject = new MappingConfigInfo(mapperContext) + .ForAllRuleSets() + .ForSourceType() + .ForAllTargetTypes(); + + return For("_", sourceExpandoObject); + } + public static JoiningNameFactory UnderscoredForTargetDynamics(MapperContext mapperContext) { var targetExpandoObject = new MappingConfigInfo(mapperContext) @@ -75,6 +86,9 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) return base.ConflictsWith(otherConfiguredItem); } + public Expression Separator + => _separatorConstant ?? (_separatorConstant = _separator.ToConstantExpression()); + public Expression GetJoiningName(Member member, IMemberMapperData mapperData) => _joinedNameFactory.Invoke(_separator, member, mapperData); diff --git a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs index a569b9eb7..1f405768f 100644 --- a/AgileMapper/DataSources/DictionaryEntryVariablePair.cs +++ b/AgileMapper/DataSources/DictionaryEntryVariablePair.cs @@ -100,7 +100,18 @@ public Expression GetMatchingKeyAssignment(Expression targetMemberKey) var firstMatchingKeyOrNull = GetKeyMatchingQuery( HasConstantTargetMemberKey ? targetMemberKey : Key, - (keyParameter, targetKey) => keyParameter.GetMatchesKeyCall(targetKey), + (keyParameter, targetKey) => + { + var separator = MapperData.Parent.IsRoot + ? null + : MapperData.GetDictionaryKeyPartSeparator(); + + var elementKeyPartMatcher = MapperData.Parent.TargetMemberIsEnumerableElement() + ? MapperData.GetDictionaryElementKeyPartMatcher() + : null; + + return keyParameter.GetMatchesKeyCall(targetKey, separator, elementKeyPartMatcher); + }, Expression.Equal, _linqFirstOrDefaultMethod); diff --git a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs index e3f1724d9..a46f088da 100644 --- a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; + using System.Text.RegularExpressions; using NetStandardPolyfills; internal static class StringExpressionExtensions @@ -92,12 +93,50 @@ public static Expression GetFirstOrDefaultCall(this Expression stringAccess) stringAccess); } - public static Expression GetMatchesKeyCall(this Expression stringAccess, Expression keyValue) + public static Expression GetMatchesKeyCall( + this Expression stringAccess, + Expression keyValue, + Expression separator, + Expression elementKeyPartMatcher) + { + if (separator == null) + { + return Expression.Call( + typeof(StringExtensions).GetPublicStaticMethod("MatchesKey", parameterCount: 2), + stringAccess, + keyValue); + } + + if (elementKeyPartMatcher == null) + { + return GetMatchesKeyWithSeparatorCall(stringAccess, keyValue, separator); + } + + var matcherPattern = ((Regex)((ConstantExpression)elementKeyPartMatcher).Value).ToString(); + + if (matcherPattern == "[0-9]+") + { + // No prefix or suffix specified - removing the separator won't + // affect the element key parts: + return GetMatchesKeyWithSeparatorCall(stringAccess, keyValue, separator); + } + + return Expression.Call( + typeof(StringExtensions).GetPublicStaticMethod("MatchesKey", parameterCount: 4), + stringAccess, + keyValue, + separator, + elementKeyPartMatcher); + } + + private static Expression GetMatchesKeyWithSeparatorCall(Expression stringAccess, Expression keyValue, + Expression separator) { return Expression.Call( - typeof(StringExtensions).GetPublicStaticMethod("MatchesKey"), + typeof(StringExtensions).GetPublicStaticMethod("MatchesKey", parameterCount: 3), stringAccess, - keyValue); + keyValue, + separator); } } } \ No newline at end of file diff --git a/AgileMapper/Extensions/Internal/StringExtensions.cs b/AgileMapper/Extensions/Internal/StringExtensions.cs index d34dcda73..ea090cd50 100644 --- a/AgileMapper/Extensions/Internal/StringExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExtensions.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Extensions.Internal { - using System; + using System.Text.RegularExpressions; + using static System.StringComparison; internal static class StringExtensions { @@ -21,12 +22,16 @@ public static string FirstOrDefault(this string value) } public static bool EqualsIgnoreCase(this string value, string otherValue) - => value.Equals(otherValue, StringComparison.OrdinalIgnoreCase); + => value.Equals(otherValue, OrdinalIgnoreCase); public static bool StartsWithIgnoreCase(this string value, string substring) - => value.StartsWith(substring, StringComparison.OrdinalIgnoreCase); + => value.StartsWith(substring, OrdinalIgnoreCase); - public static bool MatchesKey(this string subjectKey, string queryKey) + public static bool MatchesKey( + this string subjectKey, + string queryKey, + string separator, + Regex elementKeyPartMatcher) { if (queryKey == null) { @@ -42,7 +47,81 @@ public static bool MatchesKey(this string subjectKey, string queryKey) return true; } - return (queryKey.IndexOf('.') != -1) && subjectKey.EqualsIgnoreCase(queryKey.Replace(".", null)); + var elementKeyParts = elementKeyPartMatcher.Matches(queryKey); + + var searchEndIndex = queryKey.Length; + + for (var i = elementKeyParts.Count; i > 0; --i) + { + var elementKeyPart = elementKeyParts[i - 1]; + var matchStartIndex = elementKeyPart.Index; + var matchEndIndex = matchStartIndex + elementKeyPart.Length; + + ReplaceSeparatorsInSubstring(matchStartIndex, matchEndIndex, ref queryKey, separator, ref searchEndIndex); + } + + ReplaceSeparatorsInSubstring(searchEndIndex, 0, ref queryKey, separator, ref searchEndIndex); + + return subjectKey.EqualsIgnoreCase(queryKey); + } + + private static void ReplaceSeparatorsInSubstring( + int matchStartIndex, + int matchEndIndex, + ref string queryKey, + string separator, + ref int searchEndIndex) + { + var querySubstring = queryKey.Substring(matchEndIndex, searchEndIndex - matchEndIndex); + + if (querySubstring.IndexOf(separator, Ordinal) == -1) + { + searchEndIndex = matchStartIndex; + return; + } + + var flattenedQuerySubstring = querySubstring.Replace(separator, null); + + queryKey = queryKey + .Remove(matchEndIndex, searchEndIndex - matchEndIndex) + .Insert(matchEndIndex, flattenedQuerySubstring); + + searchEndIndex = matchStartIndex; + } + + public static bool MatchesKey(this string subjectKey, string queryKey, string separator) + { + if (queryKey == null) + { + // This can happen when mapping to types with multiple, nested + // recursive relationships, e.g: + // Dictionary<,> -> Order -> OrderItems -> Order -> OrderItems + // ...it's basically not supported + return false; + } + + return subjectKey.EqualsIgnoreCase(queryKey) || + subjectKey.MatchesFlattenedKey(queryKey, separator); + } + + private static bool MatchesFlattenedKey(this string subjectKey, string queryKey, string separator) + { + return (queryKey.IndexOf(separator, Ordinal) != -1) && + subjectKey.EqualsIgnoreCase(queryKey.Replace(separator, null)); + } + + public static bool MatchesKey(this string subjectKey, string queryKey) + { + if (queryKey == null) + { + // This can happen when mapping to types with multiple, nested + // recursive relationships, e.g: + // Dictionary<,> -> Order -> OrderItems -> Order -> OrderItems + // ...it's basically not supported + return false; + } + + return subjectKey.EqualsIgnoreCase(queryKey); } } } \ No newline at end of file diff --git a/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs b/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs index 25aa10e5d..d874796f5 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryMemberMapperDataExtensions.cs @@ -8,6 +8,24 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries internal static class DictionaryMemberMapperDataExtensions { + public static Expression GetDictionaryKeyPartSeparator(this IMemberMapperData mapperData) + { + return mapperData + .MapperContext + .UserConfigurations + .Dictionaries + .GetSeparator(mapperData); + } + + public static Expression GetDictionaryElementKeyPartMatcher(this IMemberMapperData mapperData) + { + return mapperData + .MapperContext + .UserConfigurations + .Dictionaries + .GetElementKeyPartMatcher(mapperData); + } + public static Expression GetTargetMemberDictionaryKey(this IMemberMapperData mapperData) { var configuredKey = mapperData.MapperContext @@ -144,7 +162,7 @@ private static void AddEnumerableMemberNamePart( memberPartExpressions.InsertRange(0, elementKeyParts); } - public static IEnumerable GetTargetMemberDictionaryElementKeyParts( + public static IList GetTargetMemberDictionaryElementKeyParts( this IMemberMapperData mapperData, Expression index) { From bff4b2ba3bf2c0c089f439ae2da2491806a5b131 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 17:33:27 +0000 Subject: [PATCH 49/74] Support for using custom member name separators for source dynamics / Tidying JoiningNameFactory / Tidying documentation --- .../WhenConfiguringSourceDynamicMapping.cs | 38 +++++++++++ .../DictionaryMappingConfigurator.cs | 62 ++++++++++------- .../DictionaryMappingConfiguratorBase.cs | 12 ++-- .../Dictionaries/DictionaryType.cs | 2 +- .../Dictionaries/IGlobalDictionarySettings.cs | 23 +++---- .../Dictionaries/ISourceDictionarySettings.cs | 34 +++++----- .../Dynamics/ISourceDynamicSettings.cs | 62 +++++++++++++++++ .../ISourceDynamicTargetTypeSelector.cs | 2 +- .../MappingConfigStartingPoint.cs | 17 +++-- .../Configuration/ConfiguredItemExtensions.cs | 4 +- .../Configuration/CustomDictionaryKey.cs | 4 +- .../Configuration/ElementKeyPartFactory.cs | 19 ++++++ .../Configuration/JoiningNameFactory.cs | 66 +++++++++++-------- .../DictionaryEntrySourceMember.cs | 2 +- AgileMapper/Members/TypePairExtensions.cs | 7 +- 15 files changed, 257 insertions(+), 97 deletions(-) create mode 100644 AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index d856ed2c9..ffbe70cd8 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -145,5 +145,43 @@ public void ShouldNotApplyDynamicConfigurationToDictionaries() result.Value.ShouldBe("2"); } } + + [Fact] + public void ShouldNotConflictDynamicAndDictionaryConfiguration() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDictionariesWithValueType() + .UseMemberNameSeparator("-"); + + mapper.WhenMapping + .FromDynamics + .UseMemberNameSeparator("+"); + + var dictionarySource = new Dictionary + { + ["Value-Line1"] = "Line 1!", + ["Value-Line2"] = "Line 2!" + }; + + var dictionaryResult = mapper.Map(dictionarySource).ToANew>(); + + dictionaryResult.Value.ShouldNotBeNull(); + dictionaryResult.Value.Line1.ShouldBe("Line 1!"); + dictionaryResult.Value.Line2.ShouldBe("Line 2!"); + + dynamic dynamicSource = new ExpandoObject(); + + ((IDictionary)dynamicSource)["Value+Line1"] = "Line 1?!"; + ((IDictionary)dynamicSource)["Value+Line2"] = "Line 2?!"; + + var dynamicResult = (PublicField
)mapper.Map(dynamicSource).ToANew>(); + + dynamicResult.Value.ShouldNotBeNull(); + dynamicResult.Value.Line1.ShouldBe("Line 1?!"); + dynamicResult.Value.Line2.ShouldBe("Line 2?!"); + } + } } } diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 9b14f2321..00a10760e 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -4,6 +4,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using Dynamics; internal class DictionaryMappingConfigurator : + DictionaryMappingConfiguratorBase, IGlobalDictionarySettings, ISourceDictionaryTargetTypeSelector, ISourceDynamicTargetTypeSelector @@ -11,73 +12,90 @@ internal class DictionaryMappingConfigurator : private readonly MappingConfigInfo _configInfo; internal DictionaryMappingConfigurator(MappingConfigInfo configInfo) + : base(configInfo) { _configInfo = configInfo; - - if (_configInfo.SourceValueType == null) - { - _configInfo.ForSourceValueType(); - } } - #region Dictionary Mapping Settings - - private MappingConfigInfo GetConfigInfo() - => (_configInfo.TargetType != null) ? _configInfo : GlobalConfigInfo; + #region Mapping Settings - private MappingConfigInfo GlobalConfigInfo => _configInfo.Clone().ForAllRuleSets().ForAllTargetTypes(); + #region UseFlattenedTargetMemberNames IGlobalDictionarySettings IGlobalDictionarySettings.UseFlattenedTargetMemberNames() - => RegisterFlattenedTargetMemberNames(GlobalConfigInfo); + => RegisterFlattenedTargetMemberNames(GetGlobalConfigInfo()); public ISourceDictionarySettings UseFlattenedTargetMemberNames() => RegisterFlattenedTargetMemberNames(GetConfigInfo()); + ISourceDynamicSettings ISourceDynamicSettings.UseFlattenedTargetMemberNames() + => RegisterFlattenedTargetMemberNames(GetConfigInfo()); + private DictionaryMappingConfigurator RegisterFlattenedTargetMemberNames(MappingConfigInfo configInfo) { - var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(configInfo); - - _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(flattenedJoiningNameFactory); + SetupFlattenedTargetMemberNames(configInfo); return this; } + #endregion + + #region UseMemberNameSeparator + IGlobalDictionarySettings IGlobalDictionarySettings.UseMemberNameSeparator(string separator) - => RegisterMemberNameSeparator(separator, GlobalConfigInfo); + => RegisterMemberNameSeparator(separator, GetGlobalConfigInfo()); public ISourceDictionarySettings UseMemberNameSeparator(string separator) => RegisterMemberNameSeparator(separator, GetConfigInfo()); + ISourceDynamicSettings ISourceDynamicSettings.UseMemberNameSeparator(string separator) + => RegisterMemberNameSeparator(separator, GetConfigInfo()); + private DictionaryMappingConfigurator RegisterMemberNameSeparator( string separator, MappingConfigInfo configInfo) { - var joiningNameFactory = JoiningNameFactory.For(separator, configInfo); - - _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(joiningNameFactory); + SetupMemberNameSeparator(separator, configInfo); return this; } + #endregion + + #region UseElementKeyPattern + IGlobalDictionarySettings IGlobalDictionarySettings.UseElementKeyPattern(string pattern) - => RegisterElementKeyPattern(pattern, GlobalConfigInfo); + => RegisterElementKeyPattern(pattern, GetGlobalConfigInfo()); public ISourceDictionarySettings UseElementKeyPattern(string pattern) => RegisterElementKeyPattern(pattern, GetConfigInfo()); + ISourceDynamicSettings ISourceDynamicSettings.UseElementKeyPattern(string pattern) + => RegisterElementKeyPattern(pattern, GetConfigInfo()); + private DictionaryMappingConfigurator RegisterElementKeyPattern( string pattern, MappingConfigInfo configInfo) { - var keyPartFactory = ElementKeyPartFactory.For(pattern, configInfo); - - _configInfo.MapperContext.UserConfigurations.Dictionaries.Add(keyPartFactory); + SetupElementKeyPattern(pattern, configInfo); return this; } + #endregion + + private MappingConfigInfo GetConfigInfo() + => (_configInfo.TargetType != typeof(object)) ? _configInfo.Clone() : GetGlobalConfigInfo(); + + private MappingConfigInfo GetGlobalConfigInfo() => _configInfo.Clone().ForAllRuleSets().ForAllTargetTypes(); + + #region AndWhenMapping + MappingConfigStartingPoint IGlobalDictionarySettings.AndWhenMapping => new MappingConfigStartingPoint(_configInfo.MapperContext); public ISourceDictionaryTargetTypeSelector AndWhenMapping => this; + ISourceDynamicTargetTypeSelector ISourceDynamicSettings.AndWhenMapping => this; + + #endregion + #endregion public ISourceDictionaryMappingConfigurator To() diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs index 2cd4539f1..e68ac7507 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs @@ -10,23 +10,23 @@ protected DictionaryMappingConfiguratorBase(MappingConfigInfo configInfo) { } - protected void SetupFlattenedTargetMemberNames() + protected void SetupFlattenedTargetMemberNames(MappingConfigInfo configInfo = null) { - var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(ConfigInfo); + var flattenedJoiningNameFactory = JoiningNameFactory.Flattened(configInfo ?? ConfigInfo); ConfigInfo.MapperContext.UserConfigurations.Dictionaries.Add(flattenedJoiningNameFactory); } - protected void SetupMemberNameSeparator(string separator) + protected void SetupMemberNameSeparator(string separator, MappingConfigInfo configInfo = null) { - var joiningNameFactory = JoiningNameFactory.For(separator, ConfigInfo); + var joiningNameFactory = JoiningNameFactory.For(separator, configInfo ?? ConfigInfo); ConfigInfo.MapperContext.UserConfigurations.Dictionaries.Add(joiningNameFactory); } - protected void SetupElementKeyPattern(string pattern) + protected void SetupElementKeyPattern(string pattern, MappingConfigInfo configInfo = null) { - var keyPartFactory = ElementKeyPartFactory.For(pattern, ConfigInfo); + var keyPartFactory = ElementKeyPartFactory.For(pattern, configInfo ?? ConfigInfo); ConfigInfo.MapperContext.UserConfigurations.Dictionaries.Add(keyPartFactory); } diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs index b30589abe..55900b83d 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs @@ -3,6 +3,6 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries internal enum DictionaryType { Dictionary, - ExpandoObject + Expando } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs index 70e34c07d..c32f8a57f 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs @@ -20,14 +20,14 @@ public interface IGlobalDictionarySettings IGlobalDictionarySettings UseFlattenedTargetMemberNames(); /// - /// Use the given to construct source and target Dictionary keys, and - /// to separate member names when mapping to nested complex type members of any target type. For - /// example, calling UseMemberName("_") will require a Dictionary entry with the key 'Address_Line1' - /// to map to an Address.Line1 member. + /// Use the given to construct expected source and target Dictionary + /// keys, and to separate member names when mapping to nested complex type members of any target + /// type. For example, calling UseMemberName("_") will require a Dictionary entry with the key + /// 'Address_Line1' to map to an Address.Line1 member. /// /// - /// The separator to use to separate member names when constructing Dictionary keys for nested - /// members. + /// The separator to use to separate member names when constructing expected Dictionary keys for + /// nested members. /// /// /// An with which to globally configure other @@ -36,13 +36,14 @@ public interface IGlobalDictionarySettings IGlobalDictionarySettings UseMemberNameSeparator(string separator); /// - /// Use the given to create the part of a Dictionary key representing an - /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the - /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a Dictionary - /// to a collection of ints will generate searches for keys '(0)', '(1)', '(2)', etc. + /// Use the given to create the part of an expected Dictionary key + /// representing an enumerable element. The pattern must contain a single 'i' character as a + /// placeholder for the enumerable index. For example, calling UseElementKeyPattern("(i)") and + /// mapping from a Dictionary to a collection of ints will generate searches for keys '(0)', + /// '(1)', '(2)', etc. /// /// - /// The pattern to use to create a Dictionary key part representing an enumerable element. + /// The pattern to use to create an expected Dictionary key part representing an enumerable element. /// /// /// An with which to globally configure other diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs index 6071182cb..6204e39ba 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs @@ -1,7 +1,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { /// - /// Provides options for configuring how this mapper will perform mappings from and to dictionaries. + /// Provides options for configuring how this mapper will perform mappings from Dictionaries. /// /// /// The type of values stored in the dictionary to which the configurations will apply. @@ -14,39 +14,41 @@ public interface ISourceDictionarySettings /// 'AddressStreetName'. /// /// - /// The with which to globally configure other - /// Dictionary mapping aspects. + /// The with which to configure other aspects + /// of source Dictionary mapping. /// ISourceDictionarySettings UseFlattenedTargetMemberNames(); /// - /// Use the given to construct source and target Dictionary keys, + /// Use the given to construct expected source Dictionary keys, /// and to separate member names when mapping to nested complex type members of any target type. - /// For example, calling UseMemberName("_") will require a Dictionary entry with the key + /// For example, calling UseMemberName("_") will require a source Dictionary entry with the key /// 'Address_Line1' to map to an Address.Line1 member. /// /// - /// The separator to use to separate member names when constructing Dictionary keys for nested - /// members. + /// The separator to use to separate member names when constructing expected source Dictionary + /// keys for nested members. /// /// - /// The with which to globally configure other - /// Dictionary mapping aspects. + /// The with which to configure other aspects + /// of source Dictionary mapping. /// ISourceDictionarySettings UseMemberNameSeparator(string separator); /// - /// Use the given to create the part of a Dictionary key representing an - /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the - /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a Dictionary - /// to a collection of ints will generate searches for keys '(0)', '(1)', '(2)', etc. + /// Use the given to create the part of an expected source Dictionary + /// key representing an enumerable element - the default is '[i]'. The pattern must contain a + /// single 'i' character as a placeholder for the enumerable index. For example, calling + /// UseElementKeyPattern("(i)") and mapping from a Dictionary to a collection of ints will generate + /// searches for keys '(0)', '(1)', '(2)', etc. /// /// - /// The pattern to use to create a Dictionary key part representing an enumerable element. + /// The pattern to use to create an expected source Dictionary key part representing an enumerable + /// element. /// /// - /// The with which to globally configure other - /// Dictionary mapping aspects. + /// The with which to configure other aspects + /// of source Dictionary mapping. /// ISourceDictionarySettings UseElementKeyPattern(string pattern); diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs new file mode 100644 index 000000000..5f1995c5e --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs @@ -0,0 +1,62 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Provides options for configuring how this mapper will perform mappings from source ExpandoObjects. + /// + public interface ISourceDynamicSettings + { + /// + /// Construct keys for target Dynamic members using flattened member names. For example, a + /// Person.Address.StreetName member would be mapped to a Dynamic member with the name + /// 'AddressStreetName'. + /// + /// + /// The with which to configure other aspects of source + /// ExpandoObject mapping. + /// + ISourceDynamicSettings UseFlattenedTargetMemberNames(); + + /// + /// Use the given to construct expected source ExpandoObject + /// member names, and to separate member names when mapping to nested complex type members of + /// any target type. For example, calling UseMemberName("_") will require a source ExpandoObject + /// member with the name 'Address_Line1' to map to an Address.Line1 member. Any string can be + /// specified as a separator - even if it would create illegal member names like 'Address-Line1' - + /// because ExpandoObjects are mapped as IDictionary{string, Object}s. + /// + /// + /// The separator to use to separate member names when constructing expected source Dynamic + /// member names for nested members. + /// + /// + /// The with which to configure other aspects of source + /// ExpandoObject mapping. + /// + ISourceDynamicSettings UseMemberNameSeparator(string separator); + + /// + /// Use the given to create the part of an expected Dynamic member name + /// representing an enumerable element - the default is '_i_'. The pattern must contain a single 'i' + /// character as a placeholder for the enumerable index. Any pattern can be specified as an element + /// key - even if it would create illegal member names like '0-OrderItemId' - because ExpandoObjects + /// are mapped as IDictionary{string, Object}s. For example, calling UseElementKeyPattern("-i-") and + /// mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', '-1-', + /// '-2-', etc. + /// + /// + /// The pattern to use to create an expected source Dynamic member name part representing an enumerable + /// element. + /// + /// + /// The with which to configure other aspects of source ExpandoObject + /// mapping. + /// + ISourceDynamicSettings UseElementKeyPattern(string pattern); + + /// + /// Gets a link back to the full , + /// for api fluency. + /// + ISourceDynamicTargetTypeSelector AndWhenMapping { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs index 8ad361c25..ce0dadb62 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicTargetTypeSelector.cs @@ -3,7 +3,7 @@ /// /// Provides options for specifying the type of ExpandoObject mapping to perform. /// - public interface ISourceDynamicTargetTypeSelector + public interface ISourceDynamicTargetTypeSelector : ISourceDynamicSettings { /// /// Configure how this mapper performs mappings from ExpandoObjects in all MappingRuleSets diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index fe970bf62..5584d83f6 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration { using System; + using System.Dynamic; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -9,6 +10,7 @@ using Dynamics; using Extensions.Internal; using Members; + using static Constants; using static Dictionaries.DictionaryType; /// @@ -347,7 +349,7 @@ public InstanceConfigurator InstancesOf() where TObject : clas /// with any Dictionary value type. /// public IGlobalDictionarySettings Dictionaries - => CreateDictionaryConfigurator(Dictionary, sourceValueType: Constants.AllTypes); + => CreateDictionaryConfigurator(Dictionary, sourceValueType: AllTypes); /// /// Configure how this mapper performs mappings from or to source Dictionary{string, TValue} instances. @@ -366,7 +368,7 @@ public IGlobalDictionarySettings DictionariesWithValueType() /// any Dictionary value type. /// public ISourceDictionaryTargetTypeSelector FromDictionaries - => CreateDictionaryConfigurator(Dictionary, sourceValueType: Constants.AllTypes); + => CreateDictionaryConfigurator(Dictionary, sourceValueType: AllTypes); /// /// Configure how this mapper performs mappings from source Dictionary{string, TValue} instances. @@ -385,14 +387,15 @@ public ISourceDictionaryTargetTypeSelector FromDictionariesWithValueType /// Configure how this mapper performs mappings from source ExpandoObject instances. /// public ISourceDynamicTargetTypeSelector FromDynamics - => CreateDictionaryConfigurator(ExpandoObject, sourceValueType: Constants.AllTypes); + => CreateDictionaryConfigurator(Expando, typeof(ExpandoObject), sourceValueType: AllTypes); private DictionaryMappingConfigurator CreateDictionaryConfigurator( DictionaryType dictionaryType, + Type sourceType = null, Type sourceValueType = null) { var configInfo = _configInfo - .ForAllSourceTypes() + .ForSourceType(sourceType ?? AllTypes) .ForSourceValueType(sourceValueType ?? typeof(TValue)) .Set(dictionaryType); @@ -434,7 +437,7 @@ public IFullMappingConfigurator To() /// The target type to which the configuration will apply. /// An IFullMappingConfigurator with which to complete the configuration. public IFullMappingConfigurator ToANew() - => GetAllSourcesTargetTypeSpecifier(ci => ci.ForRuleSet(Constants.CreateNew)).ToANew(); + => GetAllSourcesTargetTypeSpecifier(ci => ci.ForRuleSet(CreateNew)).ToANew(); /// /// Configure how this mapper performs OnTo (merge) mappings from any source type to the target type @@ -443,7 +446,7 @@ public IFullMappingConfigurator ToANew() /// The target type to which the configuration will apply. /// An IFullMappingConfigurator with which to complete the configuration. public IFullMappingConfigurator OnTo() - => GetAllSourcesTargetTypeSpecifier(ci => ci.ForRuleSet(Constants.Merge)).OnTo(); + => GetAllSourcesTargetTypeSpecifier(ci => ci.ForRuleSet(Merge)).OnTo(); /// /// Configure how this mapper performs Over (overwrite) mappings from any source type to the target type @@ -452,7 +455,7 @@ public IFullMappingConfigurator OnTo() /// The target type to which the configuration will apply. /// An IFullMappingConfigurator with which to complete the configuration. public IFullMappingConfigurator Over() - => GetAllSourcesTargetTypeSpecifier(ci => ci.ForRuleSet(Constants.Overwrite)).Over(); + => GetAllSourcesTargetTypeSpecifier(ci => ci.ForRuleSet(Overwrite)).Over(); private TargetTypeSpecifier GetAllSourcesTargetTypeSpecifier( Func configInfoConfigurator) diff --git a/AgileMapper/Configuration/ConfiguredItemExtensions.cs b/AgileMapper/Configuration/ConfiguredItemExtensions.cs index 34c993eaa..6618bb59b 100644 --- a/AgileMapper/Configuration/ConfiguredItemExtensions.cs +++ b/AgileMapper/Configuration/ConfiguredItemExtensions.cs @@ -9,13 +9,13 @@ internal static class ConfiguredItemExtensions public static TItem FindMatch(this IEnumerable items, IBasicMapperData mapperData) where TItem : UserConfiguredItemBase { - return items?.FirstOrDefault(im => im.AppliesTo(mapperData)); + return items?.FirstOrDefault(item => item.AppliesTo(mapperData)); } public static IEnumerable FindMatches(this IEnumerable items, IBasicMapperData mapperData) where TItem : UserConfiguredItemBase { - return items?.Where(item => item.AppliesTo(mapperData)).OrderBy(im => im) ?? Enumerable.Empty; + return items?.Where(item => item.AppliesTo(mapperData)).OrderBy(item => item) ?? Enumerable.Empty; } } } \ No newline at end of file diff --git a/AgileMapper/Configuration/CustomDictionaryKey.cs b/AgileMapper/Configuration/CustomDictionaryKey.cs index 609964ed2..53662473a 100644 --- a/AgileMapper/Configuration/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/CustomDictionaryKey.cs @@ -67,13 +67,13 @@ public bool AppliesTo(Member member, IMemberMapperData mapperData) var applicableDictionaryType = ConfigInfo.Get(); - if ((applicableDictionaryType != DictionaryType.ExpandoObject) && + if ((applicableDictionaryType != DictionaryType.Expando) && IsPartOfExpandoObjectMapping(mapperData)) { return false; } - if ((applicableDictionaryType == DictionaryType.ExpandoObject) && + if ((applicableDictionaryType == DictionaryType.Expando) && !IsPartOfExpandoObjectMapping(mapperData)) { return false; diff --git a/AgileMapper/Configuration/ElementKeyPartFactory.cs b/AgileMapper/Configuration/ElementKeyPartFactory.cs index 1a4992a78..3c394eebb 100644 --- a/AgileMapper/Configuration/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/ElementKeyPartFactory.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Extensions.Internal; using Members; + using ReadableExpressions.Extensions; internal class ElementKeyPartFactory : UserConfiguredItemBase { @@ -133,5 +134,23 @@ public IEnumerable GetElementKeyParts(Expression index) yield return _suffix; } } + + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion + public override string ToString() + { + var sourceType = ConfigInfo.IsForAllSourceTypes() + ? "All sources" + : ConfigInfo.SourceType.GetFriendlyName(); + + var targetTypeName = ConfigInfo.TargetType == typeof(object) + ? "All targets" + : TargetTypeName; + + return $"{sourceType} -> {targetTypeName}: {_prefix.Value}i{_suffix.Value}"; + } } } \ No newline at end of file diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/JoiningNameFactory.cs index 8fd415cf6..91b29ed8a 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/JoiningNameFactory.cs @@ -3,6 +3,7 @@ using System; using System.Dynamic; using System.Linq.Expressions; + using Api.Configuration.Dictionaries; using Extensions.Internal; using Members; using Members.Dictionaries; @@ -11,23 +12,28 @@ internal class JoiningNameFactory : UserConfiguredItemBase { private readonly string _separator; - private readonly Func _joinedNameFactory; private readonly Type _targetType; private readonly bool _isDefault; private readonly bool _isGlobal; + private readonly Func _joinedNameFactory; private Expression _separatorConstant; - private JoiningNameFactory( - string separator, - Func joinedNameFactory, - MappingConfigInfo configInfo) + private JoiningNameFactory(string separator, MappingConfigInfo configInfo, bool isDefault) : base(configInfo) { _separator = separator; - _joinedNameFactory = joinedNameFactory; _targetType = configInfo.TargetType; - _isDefault = HasDefault(separator); + _isDefault = isDefault; _isGlobal = _targetType == typeof(object); + + if (IsFlattened) + { + _joinedNameFactory = Flatten; + } + else + { + _joinedNameFactory = HandleLeadingSeparator; + } } #region Factory Methods @@ -39,7 +45,7 @@ public static JoiningNameFactory UnderscoredForSourceDynamics(MapperContext mapp .ForSourceType() .ForAllTargetTypes(); - return For("_", sourceExpandoObject); + return ForDefault("_", sourceExpandoObject); } public static JoiningNameFactory UnderscoredForTargetDynamics(MapperContext mapperContext) @@ -49,27 +55,33 @@ public static JoiningNameFactory UnderscoredForTargetDynamics(MapperContext mapp .ForAllSourceTypes() .ForTargetType(); - return For("_", targetExpandoObject); + return ForDefault("_", targetExpandoObject); } public static JoiningNameFactory Dotted(MapperContext mapperContext) - => For(".", MappingConfigInfo.AllRuleSetsSourceTypesAndTargetTypes(mapperContext)); + => ForDefault(".", MappingConfigInfo.AllRuleSetsSourceTypesAndTargetTypes(mapperContext)); + + public static JoiningNameFactory Flattened(MappingConfigInfo configInfo) + => For(string.Empty, configInfo); public static JoiningNameFactory For(string separator, MappingConfigInfo configInfo) - => new JoiningNameFactory(separator, HandleLeadingSeparator, configInfo); + => new JoiningNameFactory(separator, configInfo, isDefault: false); - public static JoiningNameFactory Flattened(MappingConfigInfo configInfo) - => new JoiningNameFactory(string.Empty, Flatten, configInfo); + public static JoiningNameFactory ForDefault(string separator, MappingConfigInfo configInfo) + => new JoiningNameFactory(separator, configInfo, isDefault: true); #endregion + public Expression Separator + => _separatorConstant ?? (_separatorConstant = _separator.ToConstantExpression()); + public string TargetScopeDescription => _isGlobal ? "globally" : "for target type " + _targetType.GetFriendlyName(); public string SeparatorDescription => IsFlattened ? "flattened" : "separated with '" + _separator + "'"; - private bool IsFlattened => !_isDefault && (_separator == string.Empty); + private bool IsFlattened => _separator == string.Empty; public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) { @@ -83,34 +95,36 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) return false; } + if (ConfigInfo.Get() != otherConfiguredItem.ConfigInfo.Get()) + { + return false; + } + return base.ConflictsWith(otherConfiguredItem); } - public Expression Separator - => _separatorConstant ?? (_separatorConstant = _separator.ToConstantExpression()); - public Expression GetJoiningName(Member member, IMemberMapperData mapperData) - => _joinedNameFactory.Invoke(_separator, member, mapperData); + => _joinedNameFactory.Invoke(member, mapperData); - private static Expression HandleLeadingSeparator(string separator, Member member, IMemberMapperData mapperData) + private Expression HandleLeadingSeparator(Member member, IMemberMapperData mapperData) { var memberName = GetJoiningNamePart(member, mapperData); - if (!HasDefault(separator)) + if (_separator != ".") { - memberName = memberName.Replace(".", separator); + memberName = memberName.Replace(".", _separator); } - if (memberName.StartsWith(separator, StringComparison.Ordinal)) + if (memberName.StartsWith(_separator, StringComparison.Ordinal)) { if (IsRootMember(member, mapperData)) { - memberName = memberName.Substring(separator.Length); + memberName = memberName.Substring(_separator.Length); } } else if (!IsRootMember(member, mapperData)) { - memberName = separator + memberName; + memberName = _separator + memberName; } return memberName.ToConstantExpression(); @@ -157,9 +171,7 @@ private static bool IsRootMember(Member member, IMemberMapperData mapperData) return memberIndex == 1; } - private static bool HasDefault(string separator) => separator == "."; - - private static Expression Flatten(string separator, Member member, IMemberMapperData mapperData) + private static Expression Flatten(Member member, IMemberMapperData mapperData) { var memberName = GetJoiningNamePart(member, mapperData); diff --git a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs index 2ec9bef87..bb093c5e6 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryEntrySourceMember.cs @@ -107,7 +107,7 @@ public IQualifiedMember WithType(Type runtimeType) return dictionaryEntry; } - public bool HasCompatibleType(Type type) => false; + public bool HasCompatibleType(Type type) => Parent.HasCompatibleType(type); public bool CouldMatch(QualifiedMember otherMember) => _matchedTargetMember.CouldMatch(otherMember); diff --git a/AgileMapper/Members/TypePairExtensions.cs b/AgileMapper/Members/TypePairExtensions.cs index 841dc9082..02ebc8954 100644 --- a/AgileMapper/Members/TypePairExtensions.cs +++ b/AgileMapper/Members/TypePairExtensions.cs @@ -27,11 +27,16 @@ public static bool HasCompatibleTypes( typePair.IsForSourceType(otherTypePair.SourceType) || (sourceTypeMatcher?.Invoke() == true); + if (!sourceTypesMatch) + { + return false; + } + var targetTypesMatch = targetTypeMatcher?.Invoke() ?? otherTypePair.TargetType.IsAssignableTo(typePair.TargetType); - return sourceTypesMatch && targetTypesMatch; + return targetTypesMatch; } } } \ No newline at end of file From 698d70eeb0cfe00825e25c99392f99cf7d86f001 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 18:09:50 +0000 Subject: [PATCH 50/74] Erroring if redundant element key patterns are configured / Organising dictionary configuration classes --- ...ConfiguringDictionaryMappingIncorrectly.cs | 34 ++++++++++++++ .../CustomDictionaryKeySpecifierBase.cs | 1 + ...mDictionaryMappingTargetMemberSpecifier.cs | 1 + .../CustomTargetDictionaryKeySpecifier.cs | 1 + .../DictionaryMappingConfiguratorBase.cs | 1 + .../SourceDictionaryMappingConfigurator.cs | 1 + .../{ => Dictionaries}/CustomDictionaryKey.cs | 0 .../DictionaryKeyPartFactoryBase.cs | 21 +++++++++ .../{ => Dictionaries}/DictionarySettings.cs | 47 ++++++++++--------- .../ElementKeyPartFactory.cs | 28 +++++++++-- .../{ => Dictionaries}/JoiningNameFactory.cs | 21 ++++----- .../Configuration/UserConfigurationSet.cs | 1 + 12 files changed, 118 insertions(+), 39 deletions(-) rename AgileMapper/Configuration/{ => Dictionaries}/CustomDictionaryKey.cs (100%) create mode 100644 AgileMapper/Configuration/Dictionaries/DictionaryKeyPartFactoryBase.cs rename AgileMapper/Configuration/{ => Dictionaries}/DictionarySettings.cs (85%) rename AgileMapper/Configuration/{ => Dictionaries}/ElementKeyPartFactory.cs (81%) rename AgileMapper/Configuration/{ => Dictionaries}/JoiningNameFactory.cs (90%) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs index bbdf29d48..c6f7be3de 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs @@ -158,6 +158,23 @@ public void ShouldErrorIfAnUnreadableSourceMemberIsSpecified() configEx.Message.ShouldContain("is not readable"); } + [Fact] + public void ShouldErrorIfRedundantSourceSeparatorIsConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDictionaries + .UseMemberNameSeparator("."); + } + }); + + configEx.Message.ShouldContain("already"); + configEx.Message.ShouldContain("global"); + } + [Fact] public void ShouldErrorIfMemberNamesAreFlattenedAndSeparatedGlobally() { @@ -261,6 +278,23 @@ public void ShouldErrorIfAnElementKeyPartHasMultipleIndexPlaceholders() "pattern must contain a single 'i' character as a placeholder for the enumerable index"); } + [Fact] + public void ShouldErrorIfRedundantGlobalElementKeyPartIsConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .Dictionaries + .UseElementKeyPattern("[i]"); + } + }); + + configEx.Message.ShouldContain("already"); + configEx.Message.ShouldContain("global"); + } + [Fact] public void ShouldErrorIfCustomTargetMemberKeyIsNotAConstant() { diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs index 2c1986b29..857e143f2 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using System; using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; /// /// Provides base dictionary key configuration functionality for customising mappings diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs index 7319f7074..ea636d44b 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryMappingTargetMemberSpecifier.cs @@ -3,6 +3,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using System; using System.Linq.Expressions; using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; using DataSources; using Dynamics; diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs index aafd75132..00e3fbc25 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using System; using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; using Members; /// diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs index e68ac7507..aa1089702 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; internal abstract class DictionaryMappingConfiguratorBase : MappingConfigurator diff --git a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs index b669f4089..dca310fc9 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs @@ -3,6 +3,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using System; using System.Collections.Generic; using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; using Dynamics; internal class SourceDictionaryMappingConfigurator : diff --git a/AgileMapper/Configuration/CustomDictionaryKey.cs b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs similarity index 100% rename from AgileMapper/Configuration/CustomDictionaryKey.cs rename to AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs diff --git a/AgileMapper/Configuration/Dictionaries/DictionaryKeyPartFactoryBase.cs b/AgileMapper/Configuration/Dictionaries/DictionaryKeyPartFactoryBase.cs new file mode 100644 index 000000000..7574364eb --- /dev/null +++ b/AgileMapper/Configuration/Dictionaries/DictionaryKeyPartFactoryBase.cs @@ -0,0 +1,21 @@ +namespace AgileObjects.AgileMapper.Configuration.Dictionaries +{ + using ReadableExpressions.Extensions; + + internal abstract class DictionaryKeyPartFactoryBase : UserConfiguredItemBase + { + + protected DictionaryKeyPartFactoryBase(MappingConfigInfo configInfo) + : base(configInfo) + { + IsForAllTargetTypes = configInfo.TargetType == typeof(object); + } + + protected bool IsForAllTargetTypes { get; } + + public abstract string GetConflictMessage(); + + protected string TargetScopeDescription + => IsForAllTargetTypes ? "globally" : "for target type " + ConfigInfo.TargetType.GetFriendlyName(); + } +} \ No newline at end of file diff --git a/AgileMapper/Configuration/DictionarySettings.cs b/AgileMapper/Configuration/Dictionaries/DictionarySettings.cs similarity index 85% rename from AgileMapper/Configuration/DictionarySettings.cs rename to AgileMapper/Configuration/Dictionaries/DictionarySettings.cs index d0965994c..4dffbd930 100644 --- a/AgileMapper/Configuration/DictionarySettings.cs +++ b/AgileMapper/Configuration/Dictionaries/DictionarySettings.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Configuration +namespace AgileObjects.AgileMapper.Configuration.Dictionaries { using System.Collections.Generic; using System.Globalization; @@ -76,42 +76,43 @@ private static CustomDictionaryKey FindKeyOrNull( public void Add(JoiningNameFactory joiningNameFactory) { - ThrowIfConflictingJoiningNameFactoryExists(joiningNameFactory); + ThrowIfConflictingKeyPartFactoryExists(joiningNameFactory, _joiningNameFactories); _joiningNameFactories.Insert(0, joiningNameFactory); } - private void ThrowIfConflictingJoiningNameFactoryExists(JoiningNameFactory joiningNameFactory) + public Expression GetSeparator(IMemberMapperData mapperData) + => _joiningNameFactories.FindMatch(mapperData).Separator; + + public Expression GetJoiningName(Member member, IMemberMapperData mapperData) + => _joiningNameFactories.FindMatch(mapperData).GetJoiningName(member, mapperData); + + public void Add(ElementKeyPartFactory keyPartFactory) + { + ThrowIfConflictingKeyPartFactoryExists(keyPartFactory, _elementKeyPartFactories); + + _elementKeyPartFactories.Insert(0, keyPartFactory); + } + + private void ThrowIfConflictingKeyPartFactoryExists( + TKeyPartFactory factory, + IList existingFactories) + where TKeyPartFactory : DictionaryKeyPartFactoryBase { - if (_joiningNameFactories.HasOne()) + if (existingFactories.HasOne()) { return; } - var conflictingJoiningName = _joiningNameFactories - .FirstOrDefault(jnf => jnf.ConflictsWith(joiningNameFactory)); + var conflictingFactory = existingFactories + .FirstOrDefault(kpf => kpf.ConflictsWith(factory)); - if (conflictingJoiningName == null) + if (conflictingFactory == null) { return; } - throw new MappingConfigurationException(string.Format( - CultureInfo.InvariantCulture, - "Member names are already configured {0} to be {1}", - conflictingJoiningName.TargetScopeDescription, - conflictingJoiningName.SeparatorDescription)); - } - - public Expression GetSeparator(IMemberMapperData mapperData) - => _joiningNameFactories.FindMatch(mapperData).Separator; - - public Expression GetJoiningName(Member member, IMemberMapperData mapperData) - => _joiningNameFactories.FindMatch(mapperData).GetJoiningName(member, mapperData); - - public void Add(ElementKeyPartFactory keyPartFactory) - { - _elementKeyPartFactories.Insert(0, keyPartFactory); + throw new MappingConfigurationException(conflictingFactory.GetConflictMessage()); } public Expression GetElementKeyPartMatcher(IBasicMapperData mapperData) diff --git a/AgileMapper/Configuration/ElementKeyPartFactory.cs b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs similarity index 81% rename from AgileMapper/Configuration/ElementKeyPartFactory.cs rename to AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs index 3c394eebb..215ac9e8a 100644 --- a/AgileMapper/Configuration/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Configuration +namespace AgileObjects.AgileMapper.Configuration.Dictionaries { using System.Collections.Generic; using System.Dynamic; @@ -8,9 +8,11 @@ using Members; using ReadableExpressions.Extensions; - internal class ElementKeyPartFactory : UserConfiguredItemBase + internal class ElementKeyPartFactory : DictionaryKeyPartFactoryBase { + private readonly string _prefixString; private readonly ConstantExpression _prefix; + private readonly string _suffixString; private readonly ConstantExpression _suffix; private Expression _keyPartMatcher; @@ -22,11 +24,13 @@ private ElementKeyPartFactory( { if (!string.IsNullOrEmpty(prefix)) { + _prefixString = prefix; _prefix = prefix.ToConstantExpression(); } if (!string.IsNullOrEmpty(suffix)) { + _suffixString = suffix; _suffix = suffix.ToConstantExpression(); } } @@ -111,7 +115,7 @@ public Expression GetElementKeyPartMatcher() private Regex CreateKeyPartRegex() { return new Regex( - _prefix?.Value + "[0-9]+" + _suffix?.Value + _prefixString + "[0-9]+" + _suffixString #if !NET_STANDARD , RegexOptions.Compiled #endif @@ -120,6 +124,22 @@ private Regex CreateKeyPartRegex() public Expression GetElementKeyPrefixOrNull() => _prefix; + public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) + { + var otherFactory = ((ElementKeyPartFactory)otherConfiguredItem); + + if ((_prefixString != otherFactory._prefixString) || (_suffixString != otherFactory._suffixString)) + { + return false; + } + + return base.ConflictsWith(otherConfiguredItem); + } + public override string GetConflictMessage() + => $"Element keys are already configured {TargetScopeDescription} to be {Pattern}"; + + private string Pattern => _suffixString + "i" + _suffixString; + public IEnumerable GetElementKeyParts(Expression index) { if (_prefix != null) @@ -150,7 +170,7 @@ public override string ToString() ? "All targets" : TargetTypeName; - return $"{sourceType} -> {targetTypeName}: {_prefix.Value}i{_suffix.Value}"; + return $"{sourceType} -> {targetTypeName}: {Pattern}"; } } } \ No newline at end of file diff --git a/AgileMapper/Configuration/JoiningNameFactory.cs b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs similarity index 90% rename from AgileMapper/Configuration/JoiningNameFactory.cs rename to AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs index 91b29ed8a..fa8717bf3 100644 --- a/AgileMapper/Configuration/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Configuration +namespace AgileObjects.AgileMapper.Configuration.Dictionaries { using System; using System.Dynamic; @@ -7,14 +7,11 @@ using Extensions.Internal; using Members; using Members.Dictionaries; - using ReadableExpressions.Extensions; - internal class JoiningNameFactory : UserConfiguredItemBase + internal class JoiningNameFactory : DictionaryKeyPartFactoryBase { private readonly string _separator; - private readonly Type _targetType; private readonly bool _isDefault; - private readonly bool _isGlobal; private readonly Func _joinedNameFactory; private Expression _separatorConstant; @@ -22,9 +19,7 @@ private JoiningNameFactory(string separator, MappingConfigInfo configInfo, bool : base(configInfo) { _separator = separator; - _targetType = configInfo.TargetType; _isDefault = isDefault; - _isGlobal = _targetType == typeof(object); if (IsFlattened) { @@ -75,9 +70,6 @@ public static JoiningNameFactory ForDefault(string separator, MappingConfigInfo public Expression Separator => _separatorConstant ?? (_separatorConstant = _separator.ToConstantExpression()); - public string TargetScopeDescription - => _isGlobal ? "globally" : "for target type " + _targetType.GetFriendlyName(); - public string SeparatorDescription => IsFlattened ? "flattened" : "separated with '" + _separator + "'"; @@ -85,12 +77,14 @@ public string SeparatorDescription public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) { - if (_isDefault) + var otherFactory = ((JoiningNameFactory)otherConfiguredItem); + + if (IsForAllTargetTypes != otherFactory.IsForAllTargetTypes) { return false; } - if (_isGlobal != ((JoiningNameFactory)otherConfiguredItem)._isGlobal) + if (_isDefault && (_separator != otherFactory._separator)) { return false; } @@ -103,6 +97,9 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) return base.ConflictsWith(otherConfiguredItem); } + public override string GetConflictMessage() + => $"Member names are already configured {TargetScopeDescription} to be {SeparatorDescription}"; + public Expression GetJoiningName(Member member, IMemberMapperData mapperData) => _joinedNameFactory.Invoke(member, mapperData); diff --git a/AgileMapper/Configuration/UserConfigurationSet.cs b/AgileMapper/Configuration/UserConfigurationSet.cs index b859f9da6..90e1f87c8 100644 --- a/AgileMapper/Configuration/UserConfigurationSet.cs +++ b/AgileMapper/Configuration/UserConfigurationSet.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using DataSources; + using Dictionaries; using Extensions.Internal; using Members; using ObjectPopulation; From 8f27b7110686d00b2c1b7c2c618527449dc91599 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 18:12:47 +0000 Subject: [PATCH 51/74] Test coverage for configuration error when configuring a redundant element key pattern for source dynamic mapping --- .../AgileMapper.UnitTests.csproj | 1 + ...ConfiguringDictionaryMappingIncorrectly.cs | 1 + ...henConfiguringDynamicMappingIncorrectly.cs | 27 +++++++++++++++++++ .../Dictionaries/ElementKeyPartFactory.cs | 2 +- 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 22b0466d7..efc4d66c1 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -102,6 +102,7 @@ + diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs index c6f7be3de..daf2ab050 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs @@ -293,6 +293,7 @@ public void ShouldErrorIfRedundantGlobalElementKeyPartIsConfigured() configEx.Message.ShouldContain("already"); configEx.Message.ShouldContain("global"); + configEx.Message.ShouldContain("[i]"); } [Fact] diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs new file mode 100644 index 000000000..99d59eff7 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs @@ -0,0 +1,27 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics.Configuration +{ + using AgileMapper.Configuration; + using Shouldly; + using Xunit; + + public class WhenConfiguringDynamicMappingIncorrectly + { + [Fact] + public void ShouldErrorIfRedundantSourceElementKeyPartIsConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .UseElementKeyPattern("_i_"); + } + }); + + configEx.Message.ShouldContain("already"); + configEx.Message.ShouldContain("global"); + configEx.Message.ShouldContain("_i_"); + } + } +} diff --git a/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs index 215ac9e8a..655025dec 100644 --- a/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs @@ -138,7 +138,7 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) public override string GetConflictMessage() => $"Element keys are already configured {TargetScopeDescription} to be {Pattern}"; - private string Pattern => _suffixString + "i" + _suffixString; + private string Pattern => _prefixString + "i" + _suffixString; public IEnumerable GetElementKeyParts(Expression index) { From b93a7ed5787ae507b76ee25bae9bd31ca190bcd3 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 18:21:20 +0000 Subject: [PATCH 52/74] Erroring is redundant member name separator globally configured for dynamics --- ...ConfiguringDictionaryMappingIncorrectly.cs | 1 + ...henConfiguringDynamicMappingIncorrectly.cs | 20 +++++++++++++++ .../MappingConfigStartingPoint.cs | 6 +++++ .../Dictionaries/JoiningNameFactory.cs | 25 +++++++++++++++++-- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs index daf2ab050..182d2e3ef 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringDictionaryMappingIncorrectly.cs @@ -173,6 +173,7 @@ public void ShouldErrorIfRedundantSourceSeparatorIsConfigured() configEx.Message.ShouldContain("already"); configEx.Message.ShouldContain("global"); + configEx.Message.ShouldContain("'.'"); } [Fact] diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs index 99d59eff7..fcd8a5d27 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs @@ -6,6 +6,26 @@ public class WhenConfiguringDynamicMappingIncorrectly { + + + [Fact] + public void ShouldErrorIfRedundantGlobalSeparatorIsConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .Dynamics + .UseMemberNameSeparator("_"); + } + }); + + configEx.Message.ShouldContain("already"); + configEx.Message.ShouldContain("global"); + configEx.Message.ShouldContain("'_'"); + } + [Fact] public void ShouldErrorIfRedundantSourceElementKeyPartIsConfigured() { diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index 5584d83f6..d8da69f15 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -383,6 +383,12 @@ public ISourceDictionaryTargetTypeSelector FromDictionaries public ISourceDictionaryTargetTypeSelector FromDictionariesWithValueType() => CreateDictionaryConfigurator(Dictionary); + /// + /// Configure how this mapper performs mappings from or to ExpandoObject instances. + /// + public ISourceDynamicTargetTypeSelector Dynamics + => CreateDictionaryConfigurator(Expando, typeof(ExpandoObject), sourceValueType: AllTypes); + /// /// Configure how this mapper performs mappings from source ExpandoObject instances. /// diff --git a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs index fa8717bf3..5f15e1e15 100644 --- a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs @@ -7,6 +7,7 @@ using Extensions.Internal; using Members; using Members.Dictionaries; + using ReadableExpressions.Extensions; internal class JoiningNameFactory : DictionaryKeyPartFactoryBase { @@ -38,7 +39,8 @@ public static JoiningNameFactory UnderscoredForSourceDynamics(MapperContext mapp var sourceExpandoObject = new MappingConfigInfo(mapperContext) .ForAllRuleSets() .ForSourceType() - .ForAllTargetTypes(); + .ForAllTargetTypes() + .Set(DictionaryType.Expando); return ForDefault("_", sourceExpandoObject); } @@ -48,7 +50,8 @@ public static JoiningNameFactory UnderscoredForTargetDynamics(MapperContext mapp var targetExpandoObject = new MappingConfigInfo(mapperContext) .ForAllRuleSets() .ForAllSourceTypes() - .ForTargetType(); + .ForTargetType() + .Set(DictionaryType.Expando); return ForDefault("_", targetExpandoObject); } @@ -179,5 +182,23 @@ private static Expression Flatten(Member member, IMemberMapperData mapperData) return memberName.ToConstantExpression(); } + + #region ExcludeFromCodeCoverage +#if DEBUG + [ExcludeFromCodeCoverage] +#endif + #endregion + public override string ToString() + { + var sourceType = ConfigInfo.IsForAllSourceTypes() + ? "All sources" + : ConfigInfo.SourceType.GetFriendlyName(); + + var targetTypeName = ConfigInfo.TargetType == typeof(object) + ? "All targets" + : TargetTypeName; + + return $"{sourceType} -> {targetTypeName}: {SeparatorDescription}"; + } } } \ No newline at end of file From 2d409a74c8b1c3e522afe3cfbff4fb1aa101b067 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 19:42:19 +0000 Subject: [PATCH 53/74] Differentiating between global and source-only dictionary (and dynamic) configuration --- .../WhenConfiguringSourceDictionaryMapping.cs | 18 +++++++++ .../WhenConfiguringTargetDictionaryMapping.cs | 40 ++++++++++--------- .../DictionaryMappingConfigurator.cs | 31 ++++++++------ .../MappingConfigStartingPoint.cs | 3 +- .../Dictionaries/CustomDictionaryKey.cs | 3 +- .../Dictionaries/DictionaryContext.cs | 8 ++++ .../Dictionaries/DictionaryType.cs | 2 +- .../Dictionaries/ElementKeyPartFactory.cs | 5 ++- .../Dictionaries/JoiningNameFactory.cs | 30 +++++++++++++- 9 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 AgileMapper/Configuration/Dictionaries/DictionaryContext.cs rename AgileMapper/{Api => }/Configuration/Dictionaries/DictionaryType.cs (56%) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs index 76b983e0c..a29ba1fc4 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringSourceDictionaryMapping.cs @@ -358,6 +358,24 @@ public void ShouldApplyACustomEnumerableElementPatternToASpecificTargetType() } } + [Fact] + public void ShouldApplyACustomConfiguredMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDictionaries + .ToANew>() + .Map(ctx => ctx.Source.Count) + .To(pf => pf.Value); + + var source = new Dictionary { ["One"] = 1, ["Two"] = 2 }; + var result = mapper.Map(source).ToANew>(); + + result.Value.ShouldBe(2); + } + } + [Fact] public void ShouldConditionallyMapToDerivedTypes() { diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index cadd1133b..c6d4d244a 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -39,6 +39,28 @@ public void ShouldApplyFlattenedMemberNamesGlobally() } } + [Fact] + public void ShouldNotApplySourceOnlyConfigurationToTargetDictionaries() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDictionaries + .UseFlattenedTargetMemberNames(); + + var source = new Customer + { + Name = "Paul", + Address = new Address { Line1 = "Abbey Road", Line2 = "Penny Lane" } + }; + var result = mapper.Map(source).ToANew>(); + + result["Name"].ShouldBe("Paul"); + result["Address.Line1"].ShouldBe("Abbey Road"); + result["Address.Line2"].ShouldBe("Penny Lane"); + } + } + [Fact] public void ShouldApplyFlattenedMemberNamesToASpecificSourceType() { @@ -251,24 +273,6 @@ public void ShouldAllowACustomTargetEntryKey() } } - [Fact] - public void ShouldApplyACustomConfiguredMember() - { - using (var mapper = Mapper.CreateNew()) - { - mapper.WhenMapping - .FromDictionaries - .ToANew>() - .Map(ctx => ctx.Source.Count) - .To(pf => pf.Value); - - var source = new Dictionary { ["One"] = 1, ["Two"] = 2 }; - var result = mapper.Map(source).ToANew>(); - - result.Value.ShouldBe(2); - } - } - [Fact] public void ShouldApplyACustomConfiguredMemberConditionally() { diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 00a10760e..5657b4762 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -1,7 +1,9 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; using Dynamics; + using static AgileMapper.Configuration.Dictionaries.DictionaryContext; internal class DictionaryMappingConfigurator : DictionaryMappingConfiguratorBase, @@ -22,13 +24,13 @@ internal DictionaryMappingConfigurator(MappingConfigInfo configInfo) #region UseFlattenedTargetMemberNames IGlobalDictionarySettings IGlobalDictionarySettings.UseFlattenedTargetMemberNames() - => RegisterFlattenedTargetMemberNames(GetGlobalConfigInfo()); + => RegisterFlattenedTargetMemberNames(GetGlobalConfigInfo(All)); public ISourceDictionarySettings UseFlattenedTargetMemberNames() - => RegisterFlattenedTargetMemberNames(GetConfigInfo()); + => RegisterFlattenedTargetMemberNames(GetConfigInfo(SourceOnly)); ISourceDynamicSettings ISourceDynamicSettings.UseFlattenedTargetMemberNames() - => RegisterFlattenedTargetMemberNames(GetConfigInfo()); + => RegisterFlattenedTargetMemberNames(GetConfigInfo(SourceOnly)); private DictionaryMappingConfigurator RegisterFlattenedTargetMemberNames(MappingConfigInfo configInfo) { @@ -41,13 +43,13 @@ private DictionaryMappingConfigurator RegisterFlattenedTargetMemberNames #region UseMemberNameSeparator IGlobalDictionarySettings IGlobalDictionarySettings.UseMemberNameSeparator(string separator) - => RegisterMemberNameSeparator(separator, GetGlobalConfigInfo()); + => RegisterMemberNameSeparator(separator, GetGlobalConfigInfo(All)); public ISourceDictionarySettings UseMemberNameSeparator(string separator) - => RegisterMemberNameSeparator(separator, GetConfigInfo()); + => RegisterMemberNameSeparator(separator, GetConfigInfo(SourceOnly)); ISourceDynamicSettings ISourceDynamicSettings.UseMemberNameSeparator(string separator) - => RegisterMemberNameSeparator(separator, GetConfigInfo()); + => RegisterMemberNameSeparator(separator, GetConfigInfo(SourceOnly)); private DictionaryMappingConfigurator RegisterMemberNameSeparator( string separator, @@ -62,13 +64,13 @@ private DictionaryMappingConfigurator RegisterMemberNameSeparator( #region UseElementKeyPattern IGlobalDictionarySettings IGlobalDictionarySettings.UseElementKeyPattern(string pattern) - => RegisterElementKeyPattern(pattern, GetGlobalConfigInfo()); + => RegisterElementKeyPattern(pattern, GetGlobalConfigInfo(All)); public ISourceDictionarySettings UseElementKeyPattern(string pattern) - => RegisterElementKeyPattern(pattern, GetConfigInfo()); + => RegisterElementKeyPattern(pattern, GetConfigInfo(SourceOnly)); ISourceDynamicSettings ISourceDynamicSettings.UseElementKeyPattern(string pattern) - => RegisterElementKeyPattern(pattern, GetConfigInfo()); + => RegisterElementKeyPattern(pattern, GetConfigInfo(SourceOnly)); private DictionaryMappingConfigurator RegisterElementKeyPattern( string pattern, @@ -80,10 +82,15 @@ private DictionaryMappingConfigurator RegisterElementKeyPattern( #endregion - private MappingConfigInfo GetConfigInfo() - => (_configInfo.TargetType != typeof(object)) ? _configInfo.Clone() : GetGlobalConfigInfo(); + private MappingConfigInfo GetConfigInfo(DictionaryContext context) + { + return (_configInfo.TargetType != typeof(object)) + ? _configInfo.Clone().Set(context) + : GetGlobalConfigInfo(context); + } - private MappingConfigInfo GetGlobalConfigInfo() => _configInfo.Clone().ForAllRuleSets().ForAllTargetTypes(); + private MappingConfigInfo GetGlobalConfigInfo(DictionaryContext context) + => _configInfo.Clone().ForAllRuleSets().ForAllTargetTypes().Set(context); #region AndWhenMapping diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index d8da69f15..efe648fba 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -6,12 +6,13 @@ using System.Linq.Expressions; using System.Reflection; using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; using Dictionaries; using Dynamics; using Extensions.Internal; using Members; using static Constants; - using static Dictionaries.DictionaryType; + using static AgileMapper.Configuration.Dictionaries.DictionaryType; /// /// Provides options for configuring how a mapper performs a mapping. diff --git a/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs index 53662473a..8b4ea5673 100644 --- a/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs @@ -1,9 +1,8 @@ -namespace AgileObjects.AgileMapper.Configuration +namespace AgileObjects.AgileMapper.Configuration.Dictionaries { using System; using System.Dynamic; using System.Linq.Expressions; - using Api.Configuration.Dictionaries; using DataSources; using Extensions.Internal; using Members; diff --git a/AgileMapper/Configuration/Dictionaries/DictionaryContext.cs b/AgileMapper/Configuration/Dictionaries/DictionaryContext.cs new file mode 100644 index 000000000..c71d3dc63 --- /dev/null +++ b/AgileMapper/Configuration/Dictionaries/DictionaryContext.cs @@ -0,0 +1,8 @@ +namespace AgileObjects.AgileMapper.Configuration.Dictionaries +{ + internal enum DictionaryContext + { + All, + SourceOnly + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs b/AgileMapper/Configuration/Dictionaries/DictionaryType.cs similarity index 56% rename from AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs rename to AgileMapper/Configuration/Dictionaries/DictionaryType.cs index 55900b83d..b5a179ede 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryType.cs +++ b/AgileMapper/Configuration/Dictionaries/DictionaryType.cs @@ -1,4 +1,4 @@ -namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries +namespace AgileObjects.AgileMapper.Configuration.Dictionaries { internal enum DictionaryType { diff --git a/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs index 655025dec..be42da469 100644 --- a/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs @@ -109,6 +109,8 @@ private static MappingConfigurationException InvalidPattern() #endregion + private string Pattern => _prefixString + "i" + _suffixString; + public Expression GetElementKeyPartMatcher() => _keyPartMatcher ?? (_keyPartMatcher = CreateKeyPartRegex().ToConstantExpression()); @@ -135,11 +137,10 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) return base.ConflictsWith(otherConfiguredItem); } + public override string GetConflictMessage() => $"Element keys are already configured {TargetScopeDescription} to be {Pattern}"; - private string Pattern => _prefixString + "i" + _suffixString; - public IEnumerable GetElementKeyParts(Expression index) { if (_prefix != null) diff --git a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs index 5f15e1e15..947de964b 100644 --- a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs @@ -3,7 +3,6 @@ using System; using System.Dynamic; using System.Linq.Expressions; - using Api.Configuration.Dictionaries; using Extensions.Internal; using Members; using Members.Dictionaries; @@ -73,11 +72,38 @@ public static JoiningNameFactory ForDefault(string separator, MappingConfigInfo public Expression Separator => _separatorConstant ?? (_separatorConstant = _separator.ToConstantExpression()); - public string SeparatorDescription + private string SeparatorDescription => IsFlattened ? "flattened" : "separated with '" + _separator + "'"; private bool IsFlattened => _separator == string.Empty; + public override bool AppliesTo(IBasicMapperData mapperData) + { + if (!base.AppliesTo(mapperData)) + { + return false; + } + + var applicableDictionarycontext = ConfigInfo.Get(); + + if (applicableDictionarycontext == DictionaryContext.All) + { + return true; + } + + while (mapperData != null) + { + if (mapperData.TargetMember.IsDictionary) + { + return false; + } + + mapperData = mapperData.Parent; + } + + return true; + } + public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) { var otherFactory = ((JoiningNameFactory)otherConfiguredItem); From 43416bed99ed42bc5eb6017ff53f0715102036ec Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 19:56:49 +0000 Subject: [PATCH 54/74] Differentiating between source-only and global dynamic configuration --- ...henConfiguringDynamicMappingIncorrectly.cs | 19 ++++++ .../DictionaryMappingConfigurator.cs | 14 +++++ .../Dictionaries/IGlobalDictionarySettings.cs | 12 ++-- .../Dictionaries/ISourceDictionarySettings.cs | 6 +- .../Dynamics/IGlobalDynamicSettings.cs | 62 +++++++++++++++++++ .../Dynamics/ISourceDynamicSettings.cs | 15 ++--- .../MappingConfigStartingPoint.cs | 2 +- 7 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs index fcd8a5d27..698813b70 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringDynamicMappingIncorrectly.cs @@ -6,7 +6,26 @@ public class WhenConfiguringDynamicMappingIncorrectly { + [Fact] + public void ShouldErrorIfRedundantSourceSeparatorIsConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .Dynamics + .UseMemberNameSeparator("-") + .AndWhenMapping + .FromDynamics + .UseMemberNameSeparator("-"); + } + }); + configEx.Message.ShouldContain("already"); + configEx.Message.ShouldContain("global"); + configEx.Message.ShouldContain("'-'"); + } [Fact] public void ShouldErrorIfRedundantGlobalSeparatorIsConfigured() diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 5657b4762..15c98e066 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -9,6 +9,7 @@ internal class DictionaryMappingConfigurator : DictionaryMappingConfiguratorBase, IGlobalDictionarySettings, ISourceDictionaryTargetTypeSelector, + IGlobalDynamicSettings, ISourceDynamicTargetTypeSelector { private readonly MappingConfigInfo _configInfo; @@ -29,6 +30,10 @@ IGlobalDictionarySettings IGlobalDictionarySettings.UseFlattened public ISourceDictionarySettings UseFlattenedTargetMemberNames() => RegisterFlattenedTargetMemberNames(GetConfigInfo(SourceOnly)); + + IGlobalDynamicSettings IGlobalDynamicSettings.UseFlattenedTargetMemberNames() + => RegisterFlattenedTargetMemberNames(GetGlobalConfigInfo(All)); + ISourceDynamicSettings ISourceDynamicSettings.UseFlattenedTargetMemberNames() => RegisterFlattenedTargetMemberNames(GetConfigInfo(SourceOnly)); @@ -48,6 +53,9 @@ IGlobalDictionarySettings IGlobalDictionarySettings.UseMemberNam public ISourceDictionarySettings UseMemberNameSeparator(string separator) => RegisterMemberNameSeparator(separator, GetConfigInfo(SourceOnly)); + IGlobalDynamicSettings IGlobalDynamicSettings.UseMemberNameSeparator(string separator) + => RegisterMemberNameSeparator(separator, GetGlobalConfigInfo(All)); + ISourceDynamicSettings ISourceDynamicSettings.UseMemberNameSeparator(string separator) => RegisterMemberNameSeparator(separator, GetConfigInfo(SourceOnly)); @@ -69,6 +77,9 @@ IGlobalDictionarySettings IGlobalDictionarySettings.UseElementKe public ISourceDictionarySettings UseElementKeyPattern(string pattern) => RegisterElementKeyPattern(pattern, GetConfigInfo(SourceOnly)); + IGlobalDynamicSettings IGlobalDynamicSettings.UseElementKeyPattern(string pattern) + => RegisterElementKeyPattern(pattern, GetGlobalConfigInfo(All)); + ISourceDynamicSettings ISourceDynamicSettings.UseElementKeyPattern(string pattern) => RegisterElementKeyPattern(pattern, GetConfigInfo(SourceOnly)); @@ -99,6 +110,9 @@ MappingConfigStartingPoint IGlobalDictionarySettings.AndWhenMapping public ISourceDictionaryTargetTypeSelector AndWhenMapping => this; + MappingConfigStartingPoint IGlobalDynamicSettings.AndWhenMapping + => new MappingConfigStartingPoint(_configInfo.MapperContext); + ISourceDynamicTargetTypeSelector ISourceDynamicSettings.AndWhenMapping => this; #endregion diff --git a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs index c32f8a57f..9cbbb3134 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/IGlobalDictionarySettings.cs @@ -22,8 +22,8 @@ public interface IGlobalDictionarySettings /// /// Use the given to construct expected source and target Dictionary /// keys, and to separate member names when mapping to nested complex type members of any target - /// type. For example, calling UseMemberName("_") will require a Dictionary entry with the key - /// 'Address_Line1' to map to an Address.Line1 member. + /// type - the default is '.'. For example, calling UseMemberNameSeparator("_") will require a + /// Dictionary entry with the key 'Address_Line1' to map to an Address.Line1 member. /// /// /// The separator to use to separate member names when constructing expected Dictionary keys for @@ -37,10 +37,10 @@ public interface IGlobalDictionarySettings /// /// Use the given to create the part of an expected Dictionary key - /// representing an enumerable element. The pattern must contain a single 'i' character as a - /// placeholder for the enumerable index. For example, calling UseElementKeyPattern("(i)") and - /// mapping from a Dictionary to a collection of ints will generate searches for keys '(0)', - /// '(1)', '(2)', etc. + /// representing an enumerable element - the default is '[i]'. The pattern must contain a single + /// 'i' character as a placeholder for the enumerable index. For example, calling + /// UseElementKeyPattern("(i)") and mapping from a Dictionary to a collection of ints will generate + /// searches for keys '(0)', '(1)', '(2)', etc. /// /// /// The pattern to use to create an expected Dictionary key part representing an enumerable element. diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs index 6204e39ba..24ceaf716 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionarySettings.cs @@ -21,9 +21,9 @@ public interface ISourceDictionarySettings /// /// Use the given to construct expected source Dictionary keys, - /// and to separate member names when mapping to nested complex type members of any target type. - /// For example, calling UseMemberName("_") will require a source Dictionary entry with the key - /// 'Address_Line1' to map to an Address.Line1 member. + /// and to separate member names when mapping to nested complex type members of any target type - + /// the default is '.'. For example, calling UseMemberNameSeparator("_") will require a source + /// Dictionary entry with the key 'Address_Line1' to map to an Address.Line1 member. /// /// /// The separator to use to separate member names when constructing expected source Dictionary diff --git a/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs b/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs new file mode 100644 index 000000000..723b79641 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs @@ -0,0 +1,62 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Provides options for configuring how this mapper will perform mappings from source ExpandoObjects. + /// + public interface IGlobalDynamicSettings + { + /// + /// Construct flattened member names for source and target Dynamic members. For example, + /// an ExpandoObject.Address.StreetName member would be mapped to a Dynamic member with the + /// name 'AddressStreetName'. + /// + /// + /// The with which to configure other global aspects of + /// ExpandoObject mapping. + /// + IGlobalDynamicSettings UseFlattenedTargetMemberNames(); + + /// + /// Use the given to construct source and target ExpandoObject + /// member names, and to separate member names when mapping to target ExpandoObject nested + /// complex type members - the default is '_'. For example, calling UseMemberNameSeparator("-") + /// will require a source ExpandoObject member with the name 'Address-Line1' to map to an + /// Address.Line1 member. Any string can be specified as a separator - even if it would create + /// illegal member names like 'Address-Line1' - because ExpandoObjects are mapped as + /// IDictionary{string, Object}s. + /// + /// + /// The separator to use to separate member names when constructing expected source Dynamic + /// member names for nested members. + /// + /// + /// The with which to configure other global aspects of + /// ExpandoObject mapping. + /// + IGlobalDynamicSettings UseMemberNameSeparator(string separator); + + /// + /// Use the given to create the part of a sourec or target Dynamic member + /// name representing an enumerable element - the default is '_i_'. The pattern must contain a single + /// 'i' character as a placeholder for the enumerable index. Any pattern can be specified as an element + /// key - even if it would create illegal member names like '0-OrderItemId' - because ExpandoObjects + /// are mapped as IDictionary{string, Object}s. For example, calling UseElementKeyPattern("-i-") and + /// mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', '-1-', + /// '-2-', etc. + /// + /// + /// The pattern to use to create an expected source Dynamic member name part representing an enumerable + /// element. + /// + /// + /// The with which to configure other global aspects of ExpandoObject + /// mapping. + /// + IGlobalDynamicSettings UseElementKeyPattern(string pattern); + + /// + /// Gets a link back to the full , for api fluency. + /// + MappingConfigStartingPoint AndWhenMapping { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs index 5f1995c5e..81bd0f0c2 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs @@ -6,9 +6,9 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics public interface ISourceDynamicSettings { /// - /// Construct keys for target Dynamic members using flattened member names. For example, a - /// Person.Address.StreetName member would be mapped to a Dynamic member with the name - /// 'AddressStreetName'. + /// Construct flattened member names for target Dynamic members. For example, an + /// ExpandoObject.Address.StreetName member would be mapped to a Dynamic member with the + /// name 'AddressStreetName'. /// /// /// The with which to configure other aspects of source @@ -19,10 +19,11 @@ public interface ISourceDynamicSettings /// /// Use the given to construct expected source ExpandoObject /// member names, and to separate member names when mapping to nested complex type members of - /// any target type. For example, calling UseMemberName("_") will require a source ExpandoObject - /// member with the name 'Address_Line1' to map to an Address.Line1 member. Any string can be - /// specified as a separator - even if it would create illegal member names like 'Address-Line1' - - /// because ExpandoObjects are mapped as IDictionary{string, Object}s. + /// any target type - the default is '_'. For example, calling UseMemberNameSeparator("-") + /// will require a source ExpandoObject member with the name 'Address-Line1' to map to an + /// Address.Line1 member. Any string can be specified as a separator - even if it would create + /// illegal member names like 'Address-Line1' - because ExpandoObjects are mapped as + /// IDictionary{string, object}s. /// /// /// The separator to use to separate member names when constructing expected source Dynamic diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index efe648fba..2439dffa8 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -387,7 +387,7 @@ public ISourceDictionaryTargetTypeSelector FromDictionariesWithValueType /// /// Configure how this mapper performs mappings from or to ExpandoObject instances. /// - public ISourceDynamicTargetTypeSelector Dynamics + public IGlobalDynamicSettings Dynamics => CreateDictionaryConfigurator(Expando, typeof(ExpandoObject), sourceValueType: AllTypes); /// From a8955346b7a34c4bc8f83164bcb59dd26c901752 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 20:55:32 +0000 Subject: [PATCH 55/74] Support for differentiating global and source-specific dynamic member name separators --- .../WhenConfiguringSourceDynamicMapping.cs | 42 +++++++++++++++++++ .../MappingConfigStartingPoint.cs | 2 +- .../Dictionaries/JoiningNameFactory.cs | 17 ++++++-- .../DictionaryMappingExpressionFactory.cs | 5 +++ .../MappingExpressionFactoryBase.cs | 24 +++++++---- AgileMapper/TypeConversion/ConverterSet.cs | 2 +- 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index ffbe70cd8..23f69aa3e 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -99,6 +99,48 @@ public void ShouldApplyACustomMemberNamePartsToASpecificTargetType() } } + [Fact] + public void ShouldApplyCustomSeparators() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .Dynamics + .UseMemberNameSeparator("-") + .AndWhenMapping + .FromDynamics + .UseMemberNameSeparator("+"); + + var source = new[] + { + new PublicProperty { Value = 10 }, + new PublicProperty { Value = 20 }, + new PublicProperty { Value = 30 }, + }; + + dynamic targetDynamic = new ExpandoObject(); + + ((IDictionary)targetDynamic)["_0_-Value"] = 1; + ((IDictionary)targetDynamic)["_1_-Value"] = 2; + + var targetResult = (IDictionary)mapper.Map(source).Over(targetDynamic); + + targetResult.Count.ShouldBe(3); + + targetResult["_0_-Value"].ShouldBe(10); + targetResult["_1_-Value"].ShouldBe(20); + targetResult["_2_-Value"].ShouldBe(30); + + dynamic sourceDynamic = new ExpandoObject(); + + ((IDictionary)sourceDynamic)["Value+Value"] = 123; + + var sourceResult = (PublicField>)mapper.Map(sourceDynamic).ToANew>>(); + + sourceResult.Value.Value.ShouldBe(123); + } + } + [Fact] public void ShouldNotApplyDictionaryConfigurationToDynamics() { diff --git a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs index 2439dffa8..fa4e9cc41 100644 --- a/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs +++ b/AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs @@ -388,7 +388,7 @@ public ISourceDictionaryTargetTypeSelector FromDictionariesWithValueType /// Configure how this mapper performs mappings from or to ExpandoObject instances. /// public IGlobalDynamicSettings Dynamics - => CreateDictionaryConfigurator(Expando, typeof(ExpandoObject), sourceValueType: AllTypes); + => CreateDictionaryConfigurator(Expando, sourceValueType: AllTypes); /// /// Configure how this mapper performs mappings from source ExpandoObject instances. diff --git a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs index 947de964b..52627c536 100644 --- a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs @@ -7,6 +7,7 @@ using Members; using Members.Dictionaries; using ReadableExpressions.Extensions; + using static DictionaryContext; internal class JoiningNameFactory : DictionaryKeyPartFactoryBase { @@ -86,7 +87,7 @@ public override bool AppliesTo(IBasicMapperData mapperData) var applicableDictionarycontext = ConfigInfo.Get(); - if (applicableDictionarycontext == DictionaryContext.All) + if (applicableDictionarycontext == All) { return true; } @@ -113,16 +114,26 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) return false; } - if (_isDefault && (_separator != otherFactory._separator)) + var separatorsAreTheSame = _separator == otherFactory._separator; + + if (_isDefault && !separatorsAreTheSame) { return false; } - if (ConfigInfo.Get() != otherConfiguredItem.ConfigInfo.Get()) + if (ConfigInfo.Get() != otherFactory.ConfigInfo.Get()) { return false; } + var thisContext = ConfigInfo.Get(); + var otherContext = otherFactory.ConfigInfo.Get(); + + if ((thisContext == All) && (otherContext == SourceOnly)) + { + return separatorsAreTheSame; + } + return base.ConflictsWith(otherConfiguredItem); } diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 8c305b70f..b157b15b9 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -171,6 +171,11 @@ private static IEnumerable EnumerateTargetMembers( var targetEntryMemberName = targetMemberNameFactory.Invoke(sourceMember); var targetEntryMember = targetDictionaryMember.Append(sourceMember.DeclaringType, targetEntryMemberName); + if (targetDictionaryMember.HasObjectEntries) + { + targetEntryMember = (DictionaryTargetMember)targetEntryMember.WithType(sourceMember.Type); + } + var entryMapperData = new ChildMemberMapperData(targetEntryMember, mapperData); var configuredKey = GetCustomKeyOrNull(entryMapperData); diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index f916b7838..b30a1ce36 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -10,6 +10,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using Members; using NetStandardPolyfills; using static CallbackPosition; + using static System.Linq.Expressions.ExpressionType; internal abstract class MappingExpressionFactoryBase { @@ -70,7 +71,7 @@ private bool MappingAlwaysBranchesToDerivedType(IObjectMappingData mappingData, { derivedTypeMappings = GetDerivedTypeMappings(mappingData); - if (derivedTypeMappings.NodeType != ExpressionType.Goto) + if (derivedTypeMappings.NodeType != Goto) { return false; } @@ -101,21 +102,21 @@ private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperDa protected abstract IEnumerable GetObjectPopulation(IObjectMappingData mappingData); - private static bool NothingIsBeingMapped(IList mappingExpressions, ObjectMapperData mapperData) + private static bool NothingIsBeingMapped(IList mappingExpressions, IMemberMapperData mapperData) { if (mappingExpressions.None()) { return true; } - if (mappingExpressions[0].NodeType != ExpressionType.Assign) + if (mappingExpressions[0].NodeType != Assign) { return false; } var assignedValue = ((BinaryExpression)mappingExpressions[0]).Right; - if (assignedValue.NodeType == ExpressionType.Default) + if (assignedValue.NodeType == Default) { return true; } @@ -132,9 +133,14 @@ private Expression GetMappingBlock(IList mappingExpressions, Mapping AdjustForSingleExpressionBlockIfApplicable(ref mappingExpressions); - if (mappingExpressions[0].NodeType != ExpressionType.Block) + if (mappingExpressions.HasOne() && (mappingExpressions[0].NodeType == Constant)) { - if (mappingExpressions[0].NodeType == ExpressionType.MemberAccess) + goto CreateFullMappingBlock; + } + + if (mappingExpressions[0].NodeType != Block) + { + if (mappingExpressions[0].NodeType == MemberAccess) { return GetReturnExpression(mappingExpressions[0], mappingExtras); } @@ -144,9 +150,9 @@ private Expression GetMappingBlock(IList mappingExpressions, Mapping goto CreateFullMappingBlock; } - var firstAssignment = (BinaryExpression)mappingExpressions.First(exp => exp.NodeType == ExpressionType.Assign); + var firstAssignment = (BinaryExpression)mappingExpressions.First(exp => exp.NodeType == Assign); - if ((firstAssignment.Left.NodeType == ExpressionType.Parameter) && + if ((firstAssignment.Left.NodeType == Parameter) && (mappingExpressions.Last() == firstAssignment)) { returnExpression = GetReturnExpression(firstAssignment.Right, mappingExtras); @@ -177,7 +183,7 @@ private Expression GetMappingBlock(IList mappingExpressions, Mapping private static void AdjustForSingleExpressionBlockIfApplicable(ref IList mappingExpressions) { - if (!mappingExpressions.HasOne() || (mappingExpressions[0].NodeType != ExpressionType.Block)) + if (!mappingExpressions.HasOne() || (mappingExpressions[0].NodeType != Block)) { return; } diff --git a/AgileMapper/TypeConversion/ConverterSet.cs b/AgileMapper/TypeConversion/ConverterSet.cs index a1f9c8705..bc27636dd 100644 --- a/AgileMapper/TypeConversion/ConverterSet.cs +++ b/AgileMapper/TypeConversion/ConverterSet.cs @@ -104,7 +104,7 @@ private static bool ConvertSourceValueToTargetType(Expression sourceValue, Type return true; } - if (!sourceValue.Type.IsSimple()) + if (!sourceValue.Type.IsValueType() && !sourceValue.Type.IsSimple()) { return false; } From d1cd5adf0c822ca191c3fa40d2ecac4f65359ad2 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sun, 17 Dec 2017 21:23:15 +0000 Subject: [PATCH 56/74] Fixing dynamic mapping separator selection --- .../Configuration/Dictionaries/JoiningNameFactory.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs index 52627c536..122002f45 100644 --- a/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/JoiningNameFactory.cs @@ -127,11 +127,17 @@ public override bool ConflictsWith(UserConfiguredItemBase otherConfiguredItem) } var thisContext = ConfigInfo.Get(); - var otherContext = otherFactory.ConfigInfo.Get(); - if ((thisContext == All) && (otherContext == SourceOnly)) + if (thisContext == All) { - return separatorsAreTheSame; + if (separatorsAreTheSame) + { + return true; + } + + var otherContext = otherFactory.ConfigInfo.Get(); + + return otherContext == All; } return base.ConflictsWith(otherConfiguredItem); From 2907cf0a69c77d7ca956415781c551e66022cff9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 19:19:15 +0000 Subject: [PATCH 57/74] Support for mapping from a collection of structs to a target dynamic or dictionary --- .../Dynamics/WhenMappingOverDynamics.cs | 25 +++++++++ .../Dictionaries/DictionaryTargetMember.cs | 51 +++++++++++++------ 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs index 7c97bedec..7b6a2c72e 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics { + using System.Collections.Generic; using System.Dynamic; using TestClasses; using Xunit; @@ -36,5 +37,29 @@ public void ShouldOverwriteAnEnumProperty() ((TitleShortlist)target.Value).ShouldBe(TitleShortlist.Mrs); } + + [Fact] + public void ShouldOverwriteFromAStructCollection() + { + var source = new[] + { + new PublicPropertyStruct { Value = 1 }, + new PublicPropertyStruct { Value = 2 }, + new PublicPropertyStruct { Value = 3 }, + }; + + dynamic target = new ExpandoObject(); + + target._0__Value = 10; + target._2__Value = 30; + + Mapper.Map(source).Over(target); + + ((IDictionary)target).Count.ShouldBe(3); + + ((int)target._0__Value).ShouldBe(1); + ((int)target._1__Value).ShouldBe(2); + ((int)target._2__Value).ShouldBe(3); + } } } diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 3280359e5..42b5c7d38 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -3,10 +3,12 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries using System; using System.Collections.Generic; using System.Dynamic; + using System.Linq; using System.Linq.Expressions; using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; + using static System.Linq.Expressions.ExpressionType; internal class DictionaryTargetMember : QualifiedMember { @@ -194,7 +196,7 @@ private Expression GetDictionaryAccess(IMemberMapperData mapperData) { var parentContextAccess = mapperData.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type); - if (parentContextAccess.NodeType != ExpressionType.Parameter) + if (parentContextAccess.NodeType != Parameter) { return MemberMapperDataExtensions.GetTargetAccess(parentContextAccess, _rootDictionaryMember.Type); } @@ -285,31 +287,51 @@ private bool ValueIsFlattening(Expression value, out Expression flattening) return false; } - ICollection blockParameters; + if (value.NodeType == Try) + { + value = ((TryExpression)value).Body; + } - if (value.NodeType == ExpressionType.Block) + if ((value.NodeType != Block)) { - flattening = value; - var flatteningBlock = (BlockExpression)flattening; - blockParameters = flatteningBlock.Variables; - value = flatteningBlock.Expressions[0]; + 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 { - blockParameters = Enumerable.EmptyArray; + flatteningBlock = (BlockExpression)value; } - if (value.NodeType != ExpressionType.Try) + if (flatteningBlock.Expressions.HasOne()) { flattening = null; return false; } - flattening = (BlockExpression)((TryExpression)value).Body; + flattening = flatteningBlock; + var flatteningExpressions = GetMappingExpressions(flattening); - flattening = blockParameters.Any() - ? Expression.Block(blockParameters, flatteningExpressions) + 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); @@ -321,12 +343,11 @@ private static IList GetMappingExpressions(Expression mapping) { var expressions = new List(); - while (mapping.NodeType == ExpressionType.Block) + while (mapping.NodeType == Block) { var mappingBlock = (BlockExpression)mapping; - expressions.AddRange(mappingBlock.Expressions); - expressions.Remove(mappingBlock.Result); + expressions.AddRange(mappingBlock.Expressions.Except(new[] { mappingBlock.Result })); mapping = mappingBlock.Result; } From 8d020a32c424c2f99b35920d91638dda26e2b1fd Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 19:24:38 +0000 Subject: [PATCH 58/74] Test coverage for mapping over a dynamic from an unmappable struct type collection --- .../Dynamics/WhenMappingOverDynamics.cs | 22 +++++++++++++++++++ .../Dictionaries/DictionarySettings.cs | 5 ++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs index 7b6a2c72e..d9df33b5e 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingOverDynamics.cs @@ -61,5 +61,27 @@ public void ShouldOverwriteFromAStructCollection() ((int)target._1__Value).ShouldBe(2); ((int)target._2__Value).ShouldBe(3); } + + [Fact] + public void ShouldHandleAnUnmappableStructCollection() + { + var source = new[] + { + new PublicPropertyStruct { Value = new ProductDto { ProductId = "1" } }, + new PublicPropertyStruct { Value = new ProductDto { ProductId = "2" } } + }; + + dynamic target = new ExpandoObject(); + + target._0__Value_ProductId = "0"; + target._1__Value_ProductId = "0"; + + Mapper.Map(source).Over(target); + + ((IDictionary)target).Count.ShouldBe(2); + + ((string)target._0__Value_ProductId).ShouldBe("0"); + ((string)target._1__Value_ProductId).ShouldBe("0"); + } } } diff --git a/AgileMapper/Configuration/Dictionaries/DictionarySettings.cs b/AgileMapper/Configuration/Dictionaries/DictionarySettings.cs index 4dffbd930..f7f4e4b83 100644 --- a/AgileMapper/Configuration/Dictionaries/DictionarySettings.cs +++ b/AgileMapper/Configuration/Dictionaries/DictionarySettings.cs @@ -1,7 +1,6 @@ namespace AgileObjects.AgileMapper.Configuration.Dictionaries { using System.Collections.Generic; - using System.Globalization; using System.Linq; using System.Linq.Expressions; using Extensions.Internal; @@ -94,9 +93,9 @@ public void Add(ElementKeyPartFactory keyPartFactory) _elementKeyPartFactories.Insert(0, keyPartFactory); } - private void ThrowIfConflictingKeyPartFactoryExists( + private static void ThrowIfConflictingKeyPartFactoryExists( TKeyPartFactory factory, - IList existingFactories) + ICollection existingFactories) where TKeyPartFactory : DictionaryKeyPartFactoryBase { if (existingFactories.HasOne()) From 744f1d87104c1547487fb24b7704c85431577177 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 19:30:25 +0000 Subject: [PATCH 59/74] Start of dynamic over enumerable mapping --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenMappingFromDynamicsOverEnumerables.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index efc4d66c1..8c0cd892a 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -107,6 +107,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs new file mode 100644 index 000000000..1c909545b --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs @@ -0,0 +1,27 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics +{ + using System.Collections.Generic; + using System.Dynamic; + using TestClasses; + using Xunit; + + public class WhenMappingFromDynamicsOverEnumerables + { + [Fact] + public void ShouldMapToASimpleTypeCollectionFromASourceArray() + { + dynamic source = new ExpandoObject(); + + source.Value = new long[] { 4, 5, 6 }; + + var target = new PublicProperty> + { + Value = new List { 2, 3 } + }; + + Mapper.Map(source).Over(target); + + target.Value.ShouldBe(4L, 5L, 6L); + } + } +} From 69f643c868796354320b4cb0ccec97f21d0c83e2 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 19:34:38 +0000 Subject: [PATCH 60/74] Extending dynamic over enumerables test coverage --- .../WhenMappingFromDynamicsOverEnumerables.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs index 1c909545b..bf64855e1 100644 --- a/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs +++ b/AgileMapper.UnitTests/Dynamics/WhenMappingFromDynamicsOverEnumerables.cs @@ -2,6 +2,9 @@ { using System.Collections.Generic; using System.Dynamic; + using System.Linq; + using AgileMapper.Extensions.Internal; + using Shouldly; using TestClasses; using Xunit; @@ -23,5 +26,42 @@ public void ShouldMapToASimpleTypeCollectionFromASourceArray() target.Value.ShouldBe(4L, 5L, 6L); } + + [Fact] + public void ShouldMapToAComplexTypeArrayFromAConvertibleTypedSourceEnumerable() + { + dynamic source = new ExpandoObject(); + + source.Value = new[] + { + new Person { Name = "Mr Pants"}, + new Customer { Name = "Mrs Blouse" } + }; + + var target = new PublicProperty(); + + Mapper.Map(source).Over(target); + + target.Value.Length.ShouldBe(2); + target.Value.First().Name.ShouldBe("Mr Pants"); + target.Value.Second().Name.ShouldBe("Mrs Blouse"); + } + + [Fact] + public void ShouldMapToAComplexTypeEnumerableFromFlattenedEntries() + { + dynamic source = new ExpandoObject(); + + source._0_ProductId = "Hose"; + source._0_Price = "1.99"; + + IEnumerable target = new List(); + + Mapper.Map(source).Over(target); + + target.ShouldHaveSingleItem(); + target.First().ProductId.ShouldBe("Hose"); + target.First().Price.ShouldBe(1.99); + } } } From 19873fda637ca272009ee80821f5126daa9ddb54 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 20:01:15 +0000 Subject: [PATCH 61/74] Separating dictionary and dynamic configurator classes / Derived dynamic configurator from FullMappingConfigurator with source type as IDictionary --- .../WhenConfiguringSourceDynamicMapping.cs | 28 +++++++++- .../DictionaryMappingConfigContinuation.cs | 2 +- .../DictionaryMappingConfigurator.cs | 53 ++++++++++++------- .../DictionaryMappingConfiguratorBase.cs | 33 ++++++++++++ .../ISourceDictionaryConfigSettings.cs | 39 ++++++++------ .../SourceDictionaryMappingConfigurator.cs | 42 ++------------- .../Dynamics/ISourceDynamicConfigSettings.cs | 53 +++++++++++++++++++ .../ISourceDynamicMappingConfigurator.cs | 9 +++- .../SourceDynamicMappingConfigurator.cs | 40 ++++++++++++++ 9 files changed, 220 insertions(+), 79 deletions(-) create mode 100644 AgileMapper/Api/Configuration/Dynamics/ISourceDynamicConfigSettings.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/SourceDynamicMappingConfigurator.cs diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index 23f69aa3e..948caa4c9 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -17,7 +17,7 @@ public void ShouldUseACustomDynamicSourceMemberName() mapper.WhenMapping .FromDynamics .ToANew>() - .MapMember("LaLaLa") + .MapFullMemberName("LaLaLa") .To(pf => pf.Value); dynamic source = new ExpandoObject(); @@ -141,6 +141,30 @@ public void ShouldApplyCustomSeparators() } } + [Fact] + public void ShouldApplyACustomConfiguredMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .Over>() + .Map((d, pf) => d.Count) + .To(pf => pf.Value); + + dynamic source = new ExpandoObject(); + + source.One = 1; + source.Two = 2; + + var target = new PublicField(); + + mapper.Map(source).Over(target); + + target.Value.ShouldBe(2); + } + } + [Fact] public void ShouldNotApplyDictionaryConfigurationToDynamics() { @@ -172,7 +196,7 @@ public void ShouldNotApplyDynamicConfigurationToDictionaries() mapper.WhenMapping .FromDynamics .To>() - .MapMember("LaLaLa") + .MapFullMemberName("LaLaLa") .To(pf => pf.Value); var source = new Dictionary diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs index ba440a571..f027aaf2f 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs @@ -22,6 +22,6 @@ ITargetDictionaryMappingConfigurator ITargetDictionaryMappingCo => new TargetDictionaryMappingConfigurator(_configInfo.Clone()); ISourceDynamicMappingConfigurator ISourceDynamicMappingConfigContinuation.And - => new SourceDictionaryMappingConfigurator(_configInfo.Clone()); + => new SourceDynamicMappingConfigurator(_configInfo.Clone()); } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 15c98e066..f864583c1 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -119,35 +119,52 @@ MappingConfigStartingPoint IGlobalDynamicSettings.AndWhenMapping #endregion - public ISourceDictionaryMappingConfigurator To() - => CreateConfigurator(_configInfo.ForAllRuleSets()); + #region Dictionaries - ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.To() - => CreateConfigurator(_configInfo.ForAllRuleSets()); + public ISourceDictionaryMappingConfigurator To() + => CreateDictionaryConfigurator(_configInfo.ForAllRuleSets()); public ISourceDictionaryMappingConfigurator ToANew() - => CreateConfigurator(Constants.CreateNew); - - ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.ToANew() - => CreateConfigurator(Constants.CreateNew); + => CreateDictionaryConfigurator(Constants.CreateNew); public ISourceDictionaryMappingConfigurator OnTo() - => CreateConfigurator(Constants.Merge); - - ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.OnTo() - => CreateConfigurator(Constants.Merge); + => CreateDictionaryConfigurator(Constants.Merge); public ISourceDictionaryMappingConfigurator Over() - => CreateConfigurator(Constants.Overwrite); + => CreateDictionaryConfigurator(Constants.Overwrite); + + private SourceDictionaryMappingConfigurator CreateDictionaryConfigurator( + string ruleSetName) + => CreateDictionaryConfigurator(_configInfo.ForRuleSet(ruleSetName)); + + private static SourceDictionaryMappingConfigurator CreateDictionaryConfigurator( + MappingConfigInfo configInfo) + => new SourceDictionaryMappingConfigurator(configInfo); + + #endregion + + #region Dynamics + + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.To() + => CreateDynamicConfigurator(_configInfo.ForAllRuleSets()); + + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.ToANew() + => CreateDynamicConfigurator(Constants.CreateNew); + + ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.OnTo() + => CreateDynamicConfigurator(Constants.Merge); ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.Over() - => CreateConfigurator(Constants.Overwrite); + => CreateDynamicConfigurator(Constants.Overwrite); - private SourceDictionaryMappingConfigurator CreateConfigurator(string ruleSetName) - => CreateConfigurator(_configInfo.ForRuleSet(ruleSetName)); + private SourceDynamicMappingConfigurator CreateDynamicConfigurator( + string ruleSetName) + => CreateDynamicConfigurator(_configInfo.ForRuleSet(ruleSetName)); - private static SourceDictionaryMappingConfigurator CreateConfigurator( + private static SourceDynamicMappingConfigurator CreateDynamicConfigurator( MappingConfigInfo configInfo) - => new SourceDictionaryMappingConfigurator(configInfo); + => new SourceDynamicMappingConfigurator(configInfo); + + #endregion } } diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs index aa1089702..3e06fb743 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfiguratorBase.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { + using System; using AgileMapper.Configuration; using AgileMapper.Configuration.Dictionaries; @@ -31,5 +32,37 @@ protected void SetupElementKeyPattern(string pattern, MappingConfigInfo configIn ConfigInfo.MapperContext.UserConfigurations.Dictionaries.Add(keyPartFactory); } + + protected CustomDictionaryMappingTargetMemberSpecifier MapFullKey(string fullMemberNameKey) + { + return CreateTargetMemberSpecifier( + fullMemberNameKey, + "keys", + (settings, customKey) => settings.AddFullKey(customKey)); + } + + protected CustomDictionaryMappingTargetMemberSpecifier MapMemberNameKey(string memberNameKeyPart) + { + return CreateTargetMemberSpecifier( + memberNameKeyPart, + "member name", + (settings, customKey) => settings.AddMemberKey(customKey)); + } + + protected CustomDictionaryMappingTargetMemberSpecifier CreateTargetMemberSpecifier( + string key, + string keyName, + Action dictionarySettingsAction) + { + if (key == null) + { + throw new MappingConfigurationException(keyName + " cannot be null"); + } + + return new CustomDictionaryMappingTargetMemberSpecifier( + ConfigInfo, + key, + dictionarySettingsAction); + } } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs index 13c2d9935..51cbae0e1 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ISourceDictionaryConfigSettings.cs @@ -1,46 +1,51 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { /// - /// Provides options for configuring how mappers will perform mappings from dictionaries. + /// Provides options for configuring how mappers will perform mappings from Dictionaries to the + /// given . /// /// - /// The type of values stored in the dictionary to which the configurations will apply. + /// The type of values stored in the Dictionary to which the configurations will apply. /// /// The target type to which the configuration should apply. public interface ISourceDictionaryConfigSettings { /// - /// Use the given to separate member names when mapping to nested - /// complex type members. For example, calling UseMemberName("-") will require a dictionary entry - /// with the key 'Address-Line1' to map to an Address.Line1 member. + /// Use the given to construct expected source Dictionary keys, + /// and to separate member names when mapping to nested complex type members of any target type - + /// the default is '.'. For example, calling UseMemberNameSeparator("_") will require a source + /// Dictionary entry with the key 'Address_Line1' to map to an Address.Line1 member. /// /// - /// The separator to use to separate member names when constructing dictionary keys for nested - /// members. + /// The separator to use to separate member names when constructing expected source Dictionary + /// keys for nested members. /// /// - /// An ISourceDictionaryConfigSettings to enable further configuration of mappings from dictionaries - /// to the target type being configured. + /// The with which to configure + /// other aspects of source Dictionary mapping. /// ISourceDictionaryConfigSettings UseMemberNameSeparator(string separator); /// - /// Use the given to create the part of a dictionary key representing an - /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the - /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a dictionary - /// to a collection of ints will generate searches for keys '(0)', '(1)', '(2)', etc. + /// Use the given to create the part of an expected source Dictionary + /// key representing an enumerable element - the default is '[i]'. The pattern must contain a + /// single 'i' character as a placeholder for the enumerable index. For example, calling + /// UseElementKeyPattern("(i)") and mapping from a Dictionary to a collection of ints will generate + /// searches for keys '(0)', '(1)', '(2)', etc. /// /// - /// The pattern to use to create a dictionary key part representing an enumerable element. + /// The pattern to use to create an expected source Dictionary key part representing an enumerable + /// element. /// /// - /// An ISourceDictionaryConfigSettings to enable further configuration of mappings from dictionaries - /// to the target type being configured. + /// The with which to configure + /// other aspects of source Dictionary mapping. /// ISourceDictionaryConfigSettings UseElementKeyPattern(string pattern); /// - /// Gets a link back to the full ISourceDictionaryMappingConfigurator, for api fluency. + /// Gets a link back to the full , + /// for api fluency. /// ISourceDictionaryMappingConfigurator And { get; } } diff --git a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs index dca310fc9..bf754da33 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/SourceDictionaryMappingConfigurator.cs @@ -1,15 +1,11 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { - using System; using System.Collections.Generic; using AgileMapper.Configuration; - using AgileMapper.Configuration.Dictionaries; - using Dynamics; internal class SourceDictionaryMappingConfigurator : DictionaryMappingConfiguratorBase, TTarget>, - ISourceDictionaryMappingConfigurator, - ISourceDynamicMappingConfigurator + ISourceDictionaryMappingConfigurator { public SourceDictionaryMappingConfigurator(MappingConfigInfo configInfo) : base(configInfo) @@ -36,41 +32,9 @@ ISourceDictionaryMappingConfigurator ISourceDictionaryConfigSet #endregion public CustomDictionaryMappingTargetMemberSpecifier MapFullKey(string fullMemberNameKey) - { - return CreateTargetMemberSpecifier( - fullMemberNameKey, - "keys", - (settings, customKey) => settings.AddFullKey(customKey)); - } + => MapFullKey(fullMemberNameKey); public CustomDictionaryMappingTargetMemberSpecifier MapMemberNameKey(string memberNameKeyPart) - { - return CreateTargetMemberSpecifier( - memberNameKeyPart, - "member name", - (settings, customKey) => settings.AddMemberKey(customKey)); - } - - public ICustomDynamicMappingTargetMemberSpecifier MapMember(string sourceMemberName) - => MapFullKey(sourceMemberName); - - public ICustomDynamicMappingTargetMemberSpecifier MapMemberName(string memberNamePart) - => MapMemberNameKey(memberNamePart); - - private CustomDictionaryMappingTargetMemberSpecifier CreateTargetMemberSpecifier( - string key, - string keyName, - Action dictionarySettingsAction) - { - if (key == null) - { - throw new MappingConfigurationException(keyName + " cannot be null"); - } - - return new CustomDictionaryMappingTargetMemberSpecifier( - ConfigInfo, - key, - dictionarySettingsAction); - } + => MapMemberNameKey(memberNameKeyPart); } } diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicConfigSettings.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicConfigSettings.cs new file mode 100644 index 000000000..7faa92559 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicConfigSettings.cs @@ -0,0 +1,53 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Provides options for configuring how mappers will perform mappings from Dynamics to the given + /// . + /// + /// The target type to which the configuration should apply. + public interface ISourceDynamicConfigSettings + { + /// + /// Use the given to construct expected source ExpandoObject + /// member names, and to separate member names when mapping to nested complex type members of + /// any target type - the default is '_'. For example, calling UseMemberNameSeparator("-") + /// will require a source ExpandoObject member with the name 'Address-Line1' to map to an + /// Address.Line1 member. Any string can be specified as a separator - even if it would create + /// illegal member names like 'Address-Line1' - because ExpandoObjects are mapped as + /// IDictionary{string, object}s. + /// + /// + /// The separator to use to separate member names when constructing expected source Dynamic + /// member names for nested members. + /// + /// + /// The with which to configure other + /// aspects of source ExpandoObject mapping. + /// + ISourceDynamicConfigSettings UseMemberNameSeparator(string separator); + + /// + /// Use the given to create the part of an expected Dynamic member name + /// representing an enumerable element - the default is '_i_'. The pattern must contain a single 'i' + /// character as a placeholder for the enumerable index. Any pattern can be specified as an element + /// key - even if it would create illegal member names like '0-OrderItemId' - because ExpandoObjects + /// are mapped as IDictionary{string, Object}s. For example, calling UseElementKeyPattern("-i-") and + /// mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', '-1-', + /// '-2-', etc. + /// + /// + /// The pattern to use to create an expected source Dynamic member name part representing an enumerable + /// element. + /// + /// + /// The with which to configure other + /// aspects of source ExpandoObject mapping. + /// + ISourceDynamicConfigSettings UseElementKeyPattern(string pattern); + + /// + /// Gets a link back to the full ISourceDynamicMappingConfigurator, for api fluency. + /// + ISourceDynamicMappingConfigurator And { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs index b114d77e6..9359a0319 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicMappingConfigurator.cs @@ -1,10 +1,15 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics { + using System.Collections.Generic; + /// /// Provides options for configuring mappings from an ExpandoObject to a given . /// /// The target type to which the configuration should apply. - public interface ISourceDynamicMappingConfigurator + // ReSharper disable once PossibleInterfaceMemberAmbiguity + public interface ISourceDynamicMappingConfigurator : + IFullMappingConfigurator, TTarget>, + ISourceDynamicConfigSettings { /// /// Configure a custom source member for a particular target member when mapping from an ExpandoObject @@ -17,7 +22,7 @@ public interface ISourceDynamicMappingConfigurator /// An ICustomDynamicMappingTargetMemberSpecifier with which to specify the target member for which the /// member with the given should be used. /// - ICustomDynamicMappingTargetMemberSpecifier MapMember(string sourceMemberName); + ICustomDynamicMappingTargetMemberSpecifier MapFullMemberName(string sourceMemberName); /// /// Configure a custom member name to use in a key for a particular target member when mapping from an diff --git a/AgileMapper/Api/Configuration/Dynamics/SourceDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/SourceDynamicMappingConfigurator.cs new file mode 100644 index 000000000..22e311607 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/SourceDynamicMappingConfigurator.cs @@ -0,0 +1,40 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + using System.Collections.Generic; + using AgileMapper.Configuration; + using Dictionaries; + + internal class SourceDynamicMappingConfigurator : + DictionaryMappingConfiguratorBase, TTarget>, + ISourceDynamicMappingConfigurator + { + public SourceDynamicMappingConfigurator(MappingConfigInfo configInfo) + : base(configInfo) + { + } + + #region ISourceDynamicConfigSettings Members + + public ISourceDynamicConfigSettings UseMemberNameSeparator(string separator) + { + SetupMemberNameSeparator(separator); + return this; + } + + public ISourceDynamicConfigSettings UseElementKeyPattern(string pattern) + { + SetupElementKeyPattern(pattern); + return this; + } + + ISourceDynamicMappingConfigurator ISourceDynamicConfigSettings.And => this; + + #endregion + + public ICustomDynamicMappingTargetMemberSpecifier MapFullMemberName(string sourceMemberName) + => MapFullKey(sourceMemberName); + + public ICustomDynamicMappingTargetMemberSpecifier MapMemberName(string memberNamePart) + => MapMemberNameKey(memberNamePart); + } +} \ No newline at end of file From c52a53a61f4b9bb441813d2a382ade6a631bcc49 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 20:04:07 +0000 Subject: [PATCH 62/74] Test coverage for mapping from dynamics to derived types based on the presence of particular dynamic members --- .../WhenConfiguringSourceDynamicMapping.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs index 948caa4c9..1db11a87a 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringSourceDynamicMapping.cs @@ -165,6 +165,45 @@ public void ShouldApplyACustomConfiguredMember() } } + [Fact] + public void ShouldConditionallyMapToDerivedTypes() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .ToANew() + .If(s => s.Source.ContainsKey("Discount")) + .MapTo() + .And + .If(s => s.Source.ContainsKey("Report")) + .MapTo(); + + dynamic source = new ExpandoObject(); + + source.Name = "Person"; + + var personResult = (PersonViewModel)mapper.Map(source).ToANew(); + + personResult.ShouldBeOfType(); + personResult.Name.ShouldBe("Person"); + + source.Discount = 0.05; + + var customerResult = (PersonViewModel)mapper.Map(source).ToANew(); + + customerResult.ShouldBeOfType(); + ((CustomerViewModel)customerResult).Discount.ShouldBe(0.05); + + source.Report = "Very good!"; + + var mysteryCustomerResult = (PersonViewModel)mapper.Map(source).ToANew(); + + mysteryCustomerResult.ShouldBeOfType(); + ((MysteryCustomerViewModel)mysteryCustomerResult).Report.ShouldBe("Very good!"); + } + } + [Fact] public void ShouldNotApplyDictionaryConfigurationToDynamics() { From 84898d6237e822efee7ca719fd5422aeb37779f2 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 20:20:53 +0000 Subject: [PATCH 63/74] Start of target dynamic mapping configuration tests / Support for Mapper.Map(source).ToANew(), defaulting the target to ExpandoObject --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenConfiguringTargetDynamicMapping.cs | 38 +++++++++++++++++++ .../ObjectMappingDataFactory.cs | 23 ++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 8c0cd892a..af79a4380 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -104,6 +104,7 @@ + diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs new file mode 100644 index 000000000..3ea885fc3 --- /dev/null +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -0,0 +1,38 @@ +namespace AgileObjects.AgileMapper.UnitTests.Dynamics.Configuration +{ + using System; + using TestClasses; + using Xunit; + + public class WhenConfiguringTargetDynamicMapping + { + [Fact] + public void ShouldApplyFlattenedMemberNamesGlobally() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .Dynamics + .UseFlattenedTargetMemberNames(); + + var source = new MysteryCustomer + { + Id = Guid.NewGuid(), + Title = Title.Mr, + Name = "Paul", + Discount = 0.25m, + Report = "Naah nah nah na-na-na NAAAAAAAHHHH", + Address = new Address { Line1 = "Abbey Road", Line2 = "Penny Lane" } + }; + var result = mapper.Map(source).ToANew(); + + ((Guid)result.Id).ShouldBe(source.Id); + ((Title)result.Title).ShouldBe(Title.Mr); + ((string)result.Name).ShouldBe("Paul"); + ((decimal)result.Discount).ShouldBe(0.25m); + ((string)result.AddressLine1).ShouldBe("Abbey Road"); + ((string)result.AddressLine2).ShouldBe("Penny Lane"); + } + } + } +} diff --git a/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs b/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs index c69c78cb0..ebae431b7 100644 --- a/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs +++ b/AgileMapper/ObjectPopulation/ObjectMappingDataFactory.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { using System; + using System.Dynamic; using System.Linq; using System.Linq.Expressions; using Enumerables; @@ -32,7 +33,27 @@ public static IObjectMappingData ForRoot( TTarget target, IMappingContext mappingContext) { - var mapperKey = new RootObjectMapperKey(MappingTypes.For(source, target), mappingContext); + RootObjectMapperKey mapperKey; + + if ((target == null) && (typeof(TTarget) == typeof(object))) + { + // This is a 'create new' mapping where the target type has come + // through as 'object'. This happens when you use .ToANew(), + // and I can't see how to differentiate that from .ToANew(). + // Given that the former is more likely and that people asking for + // .ToANew() are doing something weird, default the target + // type to ExpandoObject: + mapperKey = new RootObjectMapperKey(MappingTypes.For(source, default(ExpandoObject)), mappingContext); + + return Create( + source, + default(ExpandoObject), + null, + mapperKey, + mappingContext); + } + + mapperKey = new RootObjectMapperKey(MappingTypes.For(source, target), mappingContext); return Create( source, From 3cf4cb1b7cdc794badbc39e78a4dc51ac54af72d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 18 Dec 2017 21:14:27 +0000 Subject: [PATCH 64/74] Adding target dynamic configuration API classes / Start of target dynamic configuration tests --- .../WhenConfiguringTargetDynamicMapping.cs | 65 ++++++++++++++++ .../CustomDictionaryKeySpecifierBase.cs | 17 ++++- .../CustomTargetDictionaryKeySpecifier.cs | 76 +++++++++---------- .../DictionaryMappingConfigContinuation.cs | 6 +- .../ICustomTargetDictionaryKeySpecifier.cs | 39 ++++++++++ .../ITargetDictionaryConfigSettings.cs | 33 ++++---- .../ITargetDictionaryMappingConfigurator.cs | 8 +- .../TargetDictionaryMappingConfigurator.cs | 2 +- .../CustomTargetDynamicMemberNameSpecifier.cs | 60 +++++++++++++++ ...ICustomTargetDynamicMemberNameSpecifier.cs | 40 ++++++++++ .../Dynamics/IGlobalDynamicSettings.cs | 9 +-- .../Dynamics/ISourceDynamicSettings.cs | 9 +-- .../Dynamics/ITargetDynamicConfigSettings.cs | 59 ++++++++++++++ ...ITargetDynamicMappingConfigContinuation.cs | 15 ++++ .../ITargetDynamicMappingConfigurator.cs | 28 +++++++ .../TargetDynamicMappingConfigurator.cs | 65 ++++++++++++++++ .../Api/Configuration/TargetTypeSpecifier.cs | 14 +++- 17 files changed, 467 insertions(+), 78 deletions(-) create mode 100644 AgileMapper/Api/Configuration/Dictionaries/ICustomTargetDictionaryKeySpecifier.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/CustomTargetDynamicMemberNameSpecifier.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/ICustomTargetDynamicMemberNameSpecifier.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/ITargetDynamicConfigSettings.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigContinuation.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigurator.cs create mode 100644 AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index 3ea885fc3..5ece5ede3 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -1,6 +1,10 @@ namespace AgileObjects.AgileMapper.UnitTests.Dynamics.Configuration { using System; + using System.Collections.Generic; + using System.Dynamic; + using System.Linq; + using Shouldly; using TestClasses; using Xunit; @@ -34,5 +38,66 @@ public void ShouldApplyFlattenedMemberNamesGlobally() ((string)result.AddressLine2).ShouldBe("Penny Lane"); } } + + [Fact] + public void ShouldNotApplySourceOnlyConfigurationToTargetDynamics() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .FromDynamics + .UseFlattenedTargetMemberNames(); + + var source = new Customer + { + Name = "Paul", + Address = new Address { Line1 = "Abbey Road", Line2 = "Penny Lane" } + }; + + dynamic target = new ExpandoObject(); + + target.Name = "Ringo"; + + mapper.Map(source).OnTo(target); + + ((string)target.Name).ShouldBe("Ringo"); + ((string)target.Address_Line1).ShouldBe("Abbey Road"); + ((string)target.Address_Line2).ShouldBe("Penny Lane"); + } + } + + [Fact] + public void ShouldApplyFlattenedMemberNamesToASpecificSourceType() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDynamics + .UseFlattenedMemberNames() + .And + .MapMember(pf => pf.Value) + .ToMemberName("Data"); + + var matchingSource = new PublicField
+ { + Value = new Address { Line1 = "As a pancake" } + }; + var matchingResult = mapper.Map(matchingSource).ToANew(); + + ((IDictionary)matchingResult).Keys.Any(k => k.StartsWith("Value")).ShouldBeFalse(); + ((string)matchingResult.DataLine1).ShouldBe("As a pancake"); + ((string)matchingResult.DataLine2).ShouldBeNull(); + + var nonMatchingSource = new PublicProperty
+ { + Value = new Address { Line1 = "Like a flatfish" } + }; + var nonMatchingResult = mapper.Map(nonMatchingSource).ToANew(); + + ((string)nonMatchingResult.Value_Line1).ShouldBe("Like a flatfish"); + ((string)nonMatchingResult.Value_Line2).ShouldBeNull(); + } + } } } diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs index 857e143f2..784f673c8 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomDictionaryKeySpecifierBase.cs @@ -3,6 +3,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using System; using AgileMapper.Configuration; using AgileMapper.Configuration.Dictionaries; + using Members; /// /// Provides base dictionary key configuration functionality for customising mappings @@ -12,15 +13,29 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries /// The second type argument necessary in the dictionary configuration. public abstract class CustomDictionaryKeySpecifierBase { - internal CustomDictionaryKeySpecifierBase(MappingConfigInfo configInfo) + private readonly QualifiedMember _sourceMember; + + internal CustomDictionaryKeySpecifierBase( + MappingConfigInfo configInfo, + QualifiedMember sourceMember = null) { ConfigInfo = configInfo; + _sourceMember = sourceMember; } internal MappingConfigInfo ConfigInfo { get; } internal UserConfigurationSet UserConfigurations => ConfigInfo.MapperContext.UserConfigurations; + internal DictionaryMappingConfigContinuation RegisterCustomKey( + string key, + Action dictionarySettingsAction) + { + var configuredKey = CustomDictionaryKey.ForSourceMember(key, _sourceMember, ConfigInfo); + + return RegisterCustomKey(configuredKey, dictionarySettingsAction); + } + internal DictionaryMappingConfigContinuation RegisterCustomKey( CustomDictionaryKey configuredKey, Action dictionarySettingsAction) diff --git a/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs b/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs index 00e3fbc25..f9b10c6ab 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/CustomTargetDictionaryKeySpecifier.cs @@ -3,62 +3,54 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries using System; using AgileMapper.Configuration; using AgileMapper.Configuration.Dictionaries; + using Dynamics; using Members; - /// - /// Provides options for specifying custom target dictionary keys to which configured - /// source members should be mapped. - /// - public class CustomTargetDictionaryKeySpecifier - : CustomDictionaryKeySpecifierBase + internal class CustomTargetDictionaryKeySpecifier : + CustomDictionaryKeySpecifierBase, + ICustomTargetDictionaryKeySpecifier, + ICustomTargetDynamicMemberNameSpecifier { - private readonly QualifiedMember _sourceMember; - internal CustomTargetDictionaryKeySpecifier(MappingConfigInfo configInfo, QualifiedMember sourceMember) - : base(configInfo) + : base(configInfo, sourceMember) { - _sourceMember = sourceMember; } - /// - /// Configure a custom full dictionary key to use in place of the configured source member's name - /// when constructing a target dictionary key. For example, calling - /// Map(address => address.Line1).ToFullKey("StreetName") will generate the key 'StreetName' - /// when mapping an Address.Line1 property to a dictionary, instead of the default 'Address.Line1'. - /// - /// - /// The dictionary key to which to map the value of the configured source member. - /// - /// - /// An ITargetDictionaryMappingConfigContinuation to enable further configuration of mappings between - /// the source and target dictionary types being configured. - /// + #region Full Keys + public ITargetDictionaryMappingConfigContinuation ToFullKey(string fullMemberNameKey) - => RegisterMemberKey(fullMemberNameKey, (settings, customKey) => settings.AddFullKey(customKey)); + => RegisterFullMemberNameKey(fullMemberNameKey); + + public ITargetDynamicMappingConfigContinuation ToFullMemberName(string fullMemberName) + => RegisterFullMemberNameKey(fullMemberName); + + private DictionaryMappingConfigContinuation RegisterFullMemberNameKey(string fullMemberNameKey) + { + return RegisterMemberKey(fullMemberNameKey, (settings, customKey) => settings.AddFullKey(customKey)); + } + + #endregion + + #region Part Keys - /// - /// Use the given in place of the configured source member's name - /// when constructing a target dictionary key. For example, calling - /// Map(address => address.Line1).ToMemberKey("StreetName") will generate the key 'Address.StreetName' - /// when mapping an Address.Line1 property to a dictionary, instead of the default 'Address.Line1'. - /// - /// - /// The member key part to use in place of the configured source member's name. - /// - /// - /// An ITargetDictionaryMappingConfigContinuation to enable further configuration of mappings between - /// the source and target dictionary types being configured. - /// public ITargetDictionaryMappingConfigContinuation ToMemberNameKey(string memberNameKeyPart) - => RegisterMemberKey(memberNameKeyPart, (settings, customKey) => settings.AddMemberKey(customKey)); + => RegisterMemberNamePartKey(memberNameKeyPart); + + public ITargetDynamicMappingConfigContinuation ToMemberName(string memberName) + => RegisterMemberNamePartKey(memberName); - private ITargetDictionaryMappingConfigContinuation RegisterMemberKey( + private DictionaryMappingConfigContinuation RegisterMemberNamePartKey(string memberNameKeyPart) + { + return RegisterMemberKey(memberNameKeyPart, (settings, customKey) => settings.AddMemberKey(customKey)); + } + + #endregion + + private DictionaryMappingConfigContinuation RegisterMemberKey( string key, Action dictionarySettingsAction) { - var configuredKey = CustomDictionaryKey.ForSourceMember(key, _sourceMember, ConfigInfo); - - return RegisterCustomKey(configuredKey, dictionarySettingsAction); + return RegisterCustomKey(key, dictionarySettingsAction); } } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs index f027aaf2f..9f4a22172 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigContinuation.cs @@ -6,7 +6,8 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries internal class DictionaryMappingConfigContinuation : ISourceDictionaryMappingConfigContinuation, ITargetDictionaryMappingConfigContinuation, - ISourceDynamicMappingConfigContinuation + ISourceDynamicMappingConfigContinuation, + ITargetDynamicMappingConfigContinuation { private readonly MappingConfigInfo _configInfo; @@ -23,5 +24,8 @@ ITargetDictionaryMappingConfigurator ITargetDictionaryMappingCo ISourceDynamicMappingConfigurator ISourceDynamicMappingConfigContinuation.And => new SourceDynamicMappingConfigurator(_configInfo.Clone()); + + ITargetDynamicMappingConfigurator ITargetDynamicMappingConfigContinuation.And + => new TargetDynamicMappingConfigurator(_configInfo.Clone()); } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/ICustomTargetDictionaryKeySpecifier.cs b/AgileMapper/Api/Configuration/Dictionaries/ICustomTargetDictionaryKeySpecifier.cs new file mode 100644 index 000000000..533bf5fd2 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dictionaries/ICustomTargetDictionaryKeySpecifier.cs @@ -0,0 +1,39 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries +{ + /// + /// Provides options for specifying custom target Dictionary keys to which configured + /// source members should be mapped. + /// + public interface ICustomTargetDictionaryKeySpecifier + { + /// + /// Configure a custom full Dictionary key to use in place of the configured source member's name + /// when constructing a target Dictionary key. For example, calling + /// Map(address => address.Line1).ToFullKey("StreetName") will generate the key 'StreetName' + /// when mapping an Address.Line1 property to a Dictionary, instead of the default 'Address.Line1'. + /// + /// + /// The Dictionary key to which to map the value of the configured source member. + /// + /// + /// An ITargetDictionaryMappingConfigContinuation to enable further configuration of mappings between + /// the source and target Dictionary types being configured. + /// + ITargetDictionaryMappingConfigContinuation ToFullKey(string fullMemberNameKey); + + /// + /// Use the given in place of the configured source member's name + /// when constructing a target Dictionary key. For example, calling + /// Map(address => address.Line1).ToMemberKey("StreetName") will generate the key 'Address.StreetName' + /// when mapping an Address.Line1 property to a Dictionary, instead of the default 'Address.Line1'. + /// + /// + /// The member key part to use in place of the configured source member's name. + /// + /// + /// An ITargetDictionaryMappingConfigContinuation to enable further configuration of mappings between + /// the source and target Dictionary types being configured. + /// + ITargetDictionaryMappingConfigContinuation ToMemberNameKey(string memberNameKeyPart); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryConfigSettings.cs b/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryConfigSettings.cs index a90bf9819..73d0a999b 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryConfigSettings.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryConfigSettings.cs @@ -1,52 +1,51 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries { /// - /// Provides options for configuring how mappers will perform mappings to dictionaries. + /// Provides options for configuring how mappers will perform mappings to Dictionaries. /// /// The source type to which the configuration should apply. /// - /// The type of values stored in the dictionary to which the configurations will apply. + /// The type of values stored in the Dictionary to which the configurations will apply. /// public interface ITargetDictionaryConfigSettings { /// - /// Construct dictionary keys for nested members using flattened member names. For example, a - /// Person.Address.StreetName member would be mapped to a dictionary entry with key - /// 'AddressStreetName' when mapping from a root Person object. + /// Construct Dictionary keys for nested members using flattened member names - the default is to + /// separate member names with '.'. For example, a Person.Address.StreetName member would be mapped to + /// a Dictionary entry with key 'AddressStreetName' when mapping from a root Person object. /// /// /// An ITargetDictionaryConfigSettings to enable further configuration of mappings from the source type - /// being configured to dictionaries. + /// being configured to Dictionaries. /// ITargetDictionaryConfigSettings UseFlattenedMemberNames(); /// /// Use the given to separate member names when mapping from nested complex - /// type members to dictionaries. For example, calling UseMemberName("_") will create a dictionary entry - /// with the key 'Address_Line1' when mapped from an Address.Line1 member. + /// type members to Dictionaries - the default is '.'. For example, calling UseMemberNameSeparator("_") + /// will create a Dictionary entry with the key 'Address_Line1' when mapping from an Address.Line1 member. /// /// - /// The separator to use to separate member names when constructing dictionary keys for nested - /// members. + /// The separator to use to separate member names when constructing Dictionary keys for nested members. /// /// /// An ITargetDictionaryConfigSettings to enable further configuration of mappings from the source type - /// being configured to dictionaries. + /// being configured to Dictionaries. /// ITargetDictionaryConfigSettings UseMemberNameSeparator(string separator); /// - /// Use the given to create the part of a dictionary key representing an - /// enumerable element. The pattern must contain a single 'i' character as a placeholder for the - /// enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping from a collection - /// of ints to a dictionary will generate keys '(0)', '(1)', '(2)', etc. + /// Use the given to create the part of a Dictionary key representing an + /// enumerable element - the default is '[i]. The pattern must contain a single 'i' character as a + /// placeholder for the enumerable index. For example, calling UseElementKeyPattern("(i)") and mapping + /// from a collection of ints to a Dictionary will generate keys '(0)', '(1)', '(2)', etc. /// /// - /// The pattern to use to create a dictionary key part representing an enumerable element. + /// The pattern to use to create a Dictionary key part representing an enumerable element. /// /// /// An ITargetDictionaryConfigSettings to enable further configuration of mappings from the source - /// type being configured to dictionaries. + /// type being configured to Dictionaries. /// ITargetDictionaryConfigSettings UseElementKeyPattern(string pattern); diff --git a/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryMappingConfigurator.cs index 79158c27a..3c6d0fa38 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/ITargetDictionaryMappingConfigurator.cs @@ -10,7 +10,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dictionaries /// /// The source type to which the configuration should apply. /// - /// The type of values stored in the dictionary to which the configurations will apply. + /// The type of values stored in the Dictionary to which the configurations will apply. /// // ReSharper disable once PossibleInterfaceMemberAmbiguity public interface ITargetDictionaryMappingConfigurator : @@ -18,15 +18,15 @@ public interface ITargetDictionaryMappingConfigurator : ITargetDictionaryConfigSettings { /// - /// Map the given member using a custom dictionary key. + /// Map the given member using a custom Dictionary key. /// /// The source member's type. /// The source member to which to apply the configuration. /// - /// A CustomTargetDictionaryKeySpecifier with which to specify the custom key to use when mapping + /// A ICustomTargetDictionaryKeySpecifier with which to specify the custom key to use when mapping /// the given . /// - CustomTargetDictionaryKeySpecifier MapMember( + ICustomTargetDictionaryKeySpecifier MapMember( Expression> sourceMember); } } \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs index be726eaa5..94a267549 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs @@ -41,7 +41,7 @@ ITargetDictionaryMappingConfigurator ITargetDictionaryConfigSet #endregion - public CustomTargetDictionaryKeySpecifier MapMember( + public ICustomTargetDictionaryKeySpecifier MapMember( Expression> sourceMember) { var sourceQualifiedMember = GetSourceMemberOrThrow(sourceMember); diff --git a/AgileMapper/Api/Configuration/Dynamics/CustomTargetDynamicMemberNameSpecifier.cs b/AgileMapper/Api/Configuration/Dynamics/CustomTargetDynamicMemberNameSpecifier.cs new file mode 100644 index 000000000..7538de22b --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/CustomTargetDynamicMemberNameSpecifier.cs @@ -0,0 +1,60 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + using System; + using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; + using Dictionaries; + using Members; + + /// + /// Provides options for specifying custom target ExpandoObject member names to which + /// configured source members should be mapped. + /// + public class CustomTargetDynamicMemberNameSpecifier : + CustomDictionaryKeySpecifierBase + { + internal CustomTargetDynamicMemberNameSpecifier(MappingConfigInfo configInfo, QualifiedMember sourceMember) + : base(configInfo, sourceMember) + { + } + + /// + /// Configure a custom full Dictionary key to use in place of the configured source member's name + /// when constructing a target Dictionary key. For example, calling + /// Map(address => address.Line1).ToFullKey("StreetName") will generate the key 'StreetName' + /// when mapping an Address.Line1 property to a Dictionary, instead of the default 'Address.Line1'. + /// + /// + /// The Dictionary key to which to map the value of the configured source member. + /// + /// + /// An ITargetDictionaryMappingConfigContinuation to enable further configuration of mappings between + /// the source and target Dictionary types being configured. + /// + public ITargetDynamicMappingConfigContinuation ToFullKey(string fullMemberNameKey) + => RegisterMemberKey(fullMemberNameKey, (settings, customKey) => settings.AddFullKey(customKey)); + + /// + /// Use the given in place of the configured source member's name + /// when constructing a target Dictionary key. For example, calling + /// Map(address => address.Line1).ToMemberKey("StreetName") will generate the key 'Address.StreetName' + /// when mapping an Address.Line1 property to a Dictionary, instead of the default 'Address.Line1'. + /// + /// + /// The member key part to use in place of the configured source member's name. + /// + /// + /// An ITargetDictionaryMappingConfigContinuation to enable further configuration of mappings between + /// the source and target Dictionary types being configured. + /// + public ITargetDynamicMappingConfigContinuation ToMemberNameKey(string memberNameKeyPart) + => RegisterMemberKey(memberNameKeyPart, (settings, customKey) => settings.AddMemberKey(customKey)); + + private ITargetDynamicMappingConfigContinuation RegisterMemberKey( + string key, + Action dictionarySettingsAction) + { + return RegisterCustomKey(key, dictionarySettingsAction); + } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ICustomTargetDynamicMemberNameSpecifier.cs b/AgileMapper/Api/Configuration/Dynamics/ICustomTargetDynamicMemberNameSpecifier.cs new file mode 100644 index 000000000..f012c85a0 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ICustomTargetDynamicMemberNameSpecifier.cs @@ -0,0 +1,40 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Provides options for specifying custom target ExpandoObject member names to which configured + /// source members should be mapped. + /// + public interface ICustomTargetDynamicMemberNameSpecifier + { + /// + /// Configure a custom full ExpandoObject member name to use in place of the configured source + /// member's name when constructing a target ExpandoObject member name. For example, calling + /// Map(address => address.Line1).ToFullMemberName("StreetName") will generate the key 'StreetName' + /// when mapping an Address.Line1 property to an ExpandoObject, instead of the default 'Address_Line1'. + /// + /// + /// The member name to which to map the value of the configured source member. + /// + /// + /// An ITargetDynamicMappingConfigContinuation to enable further configuration of mappings between the + /// source and target types being configured. + /// + ITargetDynamicMappingConfigContinuation ToFullMemberName(string fullMemberName); + + /// + /// Use the given in place of the configured source member's name + /// when constructing a target ExpandoObject member name. For example, calling + /// Map(address => address.Line1).ToMemberName("StreetName") will generate the member name + /// 'Address_StreetName' when mapping an Address.Line1 property to an ExpandoObject, instead of + /// the default 'Address_Line1'. + /// + /// + /// The member name to use in place of the configured source member's name. + /// + /// + /// An ITargetDynamicMappingConfigContinuation to enable further configuration of mappings between the + /// source and target types being configured. + /// + ITargetDynamicMappingConfigContinuation ToMemberName(string memberName); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs b/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs index 723b79641..11ca0bcfd 100644 --- a/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs +++ b/AgileMapper/Api/Configuration/Dynamics/IGlobalDynamicSettings.cs @@ -38,11 +38,10 @@ public interface IGlobalDynamicSettings /// /// Use the given to create the part of a sourec or target Dynamic member /// name representing an enumerable element - the default is '_i_'. The pattern must contain a single - /// 'i' character as a placeholder for the enumerable index. Any pattern can be specified as an element - /// key - even if it would create illegal member names like '0-OrderItemId' - because ExpandoObjects - /// are mapped as IDictionary{string, Object}s. For example, calling UseElementKeyPattern("-i-") and - /// mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', '-1-', - /// '-2-', etc. + /// 'i' character as a placeholder for the enumerable index. For example, calling UseElementKeyPattern("-i-") + /// and mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', + /// '-1-', '-2-', etc. Any pattern can be specified as an element key - even if it would create illegal + /// member names like '-0-' - because ExpandoObjects are mapped as IDictionary{string, Object}s. /// /// /// The pattern to use to create an expected source Dynamic member name part representing an enumerable diff --git a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs index 81bd0f0c2..bcd99c5da 100644 --- a/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs +++ b/AgileMapper/Api/Configuration/Dynamics/ISourceDynamicSettings.cs @@ -38,11 +38,10 @@ public interface ISourceDynamicSettings /// /// Use the given to create the part of an expected Dynamic member name /// representing an enumerable element - the default is '_i_'. The pattern must contain a single 'i' - /// character as a placeholder for the enumerable index. Any pattern can be specified as an element - /// key - even if it would create illegal member names like '0-OrderItemId' - because ExpandoObjects - /// are mapped as IDictionary{string, Object}s. For example, calling UseElementKeyPattern("-i-") and - /// mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', '-1-', - /// '-2-', etc. + /// character as a placeholder for the enumerable index. For example, calling UseElementKeyPattern("-i-") + /// and mapping from a Dynamic to a collection of ints will generate searches for member names '-0-', '-1-', + /// '-2-', etc. Any pattern can be specified as an element key - even if it would create illegal member + /// names like '-0-' - because ExpandoObjects are mapped as IDictionary{string, Object}s. /// /// /// The pattern to use to create an expected source Dynamic member name part representing an enumerable diff --git a/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicConfigSettings.cs b/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicConfigSettings.cs new file mode 100644 index 000000000..0dc0c1c43 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicConfigSettings.cs @@ -0,0 +1,59 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Provides options for configuring how mappers will perform mappings to dictionaries. + /// + /// The source type to which the configuration should apply. + public interface ITargetDynamicConfigSettings + { + /// + /// Construct ExpandoObject member names for nested members using flattened member names - the default is + /// to separate member names with '_'. For example, a Person.Address.StreetName member would be mapped to + /// an ExpandoObject member with name 'AddressStreetName' when mapping from a root Person object. + /// + /// + /// An ITargetDynamicConfigSettings to enable further configuration of mappings from the source type + /// being configured to ExpandoObjects. + /// + ITargetDynamicConfigSettings UseFlattenedMemberNames(); + + /// + /// Use the given to separate member names when mapping from nested complex + /// type members to ExpandoObjects - the default is '_'. For example, calling UseMemberNameSeparator("-") + /// will create an ExpandoObject member with the name 'Address-Line1' when mapping from an Address.Line1 + /// member. Any string can be specified as a separator - even if it would create illegal member names like + /// 'Address-Line1' - because ExpandoObjects are mapped as IDictionary{string, object}s. + /// + /// + /// The separator to use to separate member names when constructing ExpandoObject member names for nested + /// members. + /// + /// + /// An ITargetDynamicConfigSettings to enable further configuration of mappings from the source type + /// being configured to ExpandoObjects. + /// + ITargetDynamicConfigSettings UseMemberNameSeparator(string separator); + + /// + /// Use the given to create the part of an ExpandoObject member name + /// representing an enumerable element - the default is '_i_. The pattern must contain a single 'i' + /// character as a placeholder for the enumerable index. For example, calling UseElementKeyPattern("(i)") + /// and mapping from a collection of ints to a Dictionary will generate keys '(0)', '(1)', '(2)', + /// etc. Any pattern can be specified as an element key - even if it would create illegal member names + /// like '(0)' - because ExpandoObjects are mapped as IDictionary{string, Object}s. + /// + /// + /// The pattern to use to create a Dictionary key part representing an enumerable element. + /// + /// + /// An ITargetDynamicConfigSettings to enable further configuration of mappings from the source type + /// being configured to ExpandoObjects. + /// + ITargetDynamicConfigSettings UseElementKeyPattern(string pattern); + + /// + /// Gets a link back to the full ITargetDictionaryMappingConfigurator, for api fluency. + /// + ITargetDynamicMappingConfigurator And { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigContinuation.cs b/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigContinuation.cs new file mode 100644 index 000000000..e498f786f --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigContinuation.cs @@ -0,0 +1,15 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + /// + /// Enables chaining of configurations for the same source and target type. + /// + /// The source type to which the configuration should apply. + public interface ITargetDynamicMappingConfigContinuation + { + /// + /// Perform another configuration of how this mapper maps to and from the source and target types + /// being configured. This property exists purely to provide a more fluent configuration interface. + /// + ITargetDynamicMappingConfigurator And { get; } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigurator.cs new file mode 100644 index 000000000..892fe8938 --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/ITargetDynamicMappingConfigurator.cs @@ -0,0 +1,28 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + + /// + /// Provides options for configuring mappings from a to an ExpandoObject. + /// + /// The source type to which the configuration should apply. + // ReSharper disable once PossibleInterfaceMemberAmbiguity + public interface ITargetDynamicMappingConfigurator : + IFullMappingConfigurator>, + ITargetDynamicConfigSettings + { + /// + /// Map the given member using a custom ExpandoObject member name. + /// + /// The source member's type. + /// The source member to which to apply the configuration. + /// + /// A CustomTargetDictionaryKeySpecifier with which to specify the custom key to use when mapping + /// the given . + /// + ICustomTargetDynamicMemberNameSpecifier MapMember( + Expression> sourceMember); + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs new file mode 100644 index 000000000..77469fcdf --- /dev/null +++ b/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs @@ -0,0 +1,65 @@ +namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using AgileMapper.Configuration; + using Dictionaries; + using Members; + using ReadableExpressions; + + internal class TargetDynamicMappingConfigurator : + DictionaryMappingConfiguratorBase>, + ITargetDynamicMappingConfigurator + { + public TargetDynamicMappingConfigurator(MappingConfigInfo configInfo) + : base(configInfo) + { + } + + #region ITargetDynamicConfigSettings Members + + public ITargetDynamicConfigSettings UseFlattenedMemberNames() + { + SetupFlattenedTargetMemberNames(); + return this; + } + + public ITargetDynamicConfigSettings UseMemberNameSeparator(string separator) + { + SetupMemberNameSeparator(separator); + return this; + } + + public ITargetDynamicConfigSettings UseElementKeyPattern(string pattern) + { + SetupElementKeyPattern(pattern); + return this; + } + + ITargetDynamicMappingConfigurator ITargetDynamicConfigSettings.And => this; + + #endregion + + public ICustomTargetDynamicMemberNameSpecifier MapMember( + Expression> sourceMember) + { + var sourceQualifiedMember = GetSourceMemberOrThrow(sourceMember); + + return new CustomTargetDictionaryKeySpecifier(ConfigInfo, sourceQualifiedMember); + } + + private QualifiedMember GetSourceMemberOrThrow(LambdaExpression lambda) + { + var sourceMember = lambda.Body.ToSourceMember(ConfigInfo.MapperContext); + + if (sourceMember != null) + { + return sourceMember; + } + + throw new MappingConfigurationException( + $"Source member {lambda.Body.ToReadableString()} is not readable."); + } + } +} \ No newline at end of file diff --git a/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs b/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs index 70340ec11..c370f5600 100644 --- a/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs +++ b/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs @@ -1,7 +1,9 @@ namespace AgileObjects.AgileMapper.Api.Configuration { using AgileMapper.Configuration; + using AgileMapper.Configuration.Dictionaries; using Dictionaries; + using Dynamics; /// /// Provides options for specifying the target type and mapping rule set to which the configuration should @@ -58,7 +60,7 @@ private MappingConfigurator UsingRuleSet(string name) /// /// Configure how this mapper performs mappings from the source type being configured in all MappingRuleSets - /// (create new, overwrite, etc), to target dictionaries. + /// (create new, overwrite, etc), to target Dictionaries. /// public ITargetDictionaryMappingConfigurator ToDictionaries => ToDictionariesWithValueType(); @@ -71,6 +73,14 @@ private MappingConfigurator UsingRuleSet(string name) /// /// An ITargetDictionaryConfigSettings with which to continue the configuration. public ITargetDictionaryMappingConfigurator ToDictionariesWithValueType() - => new TargetDictionaryMappingConfigurator(_configInfo.ForAllRuleSets()); + => new TargetDictionaryMappingConfigurator(_configInfo.ForAllRuleSets().Set(DictionaryType.Dictionary)); + + /// + /// Configure how this mapper performs mappings from the source type being configured in all MappingRuleSets + /// (create new, overwrite, etc), to target ExpandoObjects. + /// + public ITargetDynamicMappingConfigurator ToDynamics + => new TargetDynamicMappingConfigurator(_configInfo.ForAllRuleSets().Set(DictionaryType.Expando)); + } } \ No newline at end of file From 9481298848785291ef1cd2d360b045514332b229 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 21:24:25 +0000 Subject: [PATCH 65/74] Support for custom, source-type-specific dynamic enumerable element patterns --- .../WhenConfiguringTargetDictionaryMapping.cs | 2 +- .../WhenConfiguringTargetDynamicMapping.cs | 66 +++++++++++++++++++ .../TargetDynamicMappingConfigurator.cs | 3 +- .../Api/Configuration/MappingConfigurator.cs | 7 +- .../Api/Configuration/TargetTypeSpecifier.cs | 1 - .../Dictionaries/CustomDictionaryKey.cs | 3 +- .../Dictionaries/ElementKeyPartFactory.cs | 12 ++-- .../Dictionaries/DictionaryTargetMember.cs | 10 +-- 8 files changed, 90 insertions(+), 14 deletions(-) diff --git a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs index c6d4d244a..7f13c836e 100644 --- a/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs +++ b/AgileMapper.UnitTests/Dictionaries/Configuration/WhenConfiguringTargetDictionaryMapping.cs @@ -185,7 +185,7 @@ public void ShouldApplyACustomEnumerableElementPatternGlobally() } [Fact] - public void ShouldApplyACustomEnumerableElementPatternToASpecificTargetType() + public void ShouldApplyACustomEnumerableElementPatternToASpecificSourceType() { using (var mapper = Mapper.CreateNew()) { diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index 5ece5ede3..a0902cf1a 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -99,5 +99,71 @@ public void ShouldApplyFlattenedMemberNamesToASpecificSourceType() ((string)nonMatchingResult.Value_Line2).ShouldBeNull(); } } + + [Fact] + public void ShouldApplyACustomSeparatorToASpecificSourceType() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToDynamics + .UseMemberNameSeparator("!") + .And + .MapMember(c => c.Address.Line1) + .ToFullMemberName("StreetAddress"); + + var address = new Address { Line1 = "Paddy's", Line2 = "Philly" }; + var matchingSource = new MysteryCustomer { Address = address }; + var matchingResult = (IDictionary)mapper.Map(matchingSource).ToANew(); + + matchingResult["StreetAddress"].ShouldBe("Paddy's"); + matchingResult["Address!Line2"].ShouldBe("Philly"); + matchingResult.ShouldNotContainKey("Address_Line1"); + matchingResult.ShouldNotContainKey("Address!Line1"); + + var nonMatchingSource = new Customer { Address = address }; + var nonMatchingSourceResult = (IDictionary)mapper.Map(nonMatchingSource).ToANew(); + + nonMatchingSourceResult["Address_Line1"].ShouldBe("Paddy's"); + + var nonMatchingTargetResult = (IDictionary)mapper.Map(nonMatchingSource).ToANew(); + + nonMatchingTargetResult["Address_Line1"].ShouldBe("Paddy's"); + } + } + + [Fact] + public void ShouldApplyACustomEnumerableElementPatternToASpecificDerivedSourceType() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToDynamics + .UseElementKeyPattern("(i)"); + + var source = new PublicProperty> + { + Value = new List + { + new Person { Name = "Sandra", Address = new Address { Line1 = "Home" } }, + new Customer { Name = "David", Address = new Address { Line1 = "Home!" } } + } + }; + var originalExpando = new ExpandoObject(); + var target = new PublicField { Value = originalExpando }; + + var result = (IDictionary)mapper.Map(source).OnTo(target).Value; + + result.ShouldBeSameAs(originalExpando); + + result["_0__Name"].ShouldBe("Sandra"); + result["_0__Address_Line1"].ShouldBe("Home"); + + result["(1)_Name"].ShouldBe("David"); + result["(1)_Address_Line1"].ShouldBe("Home!"); + } + } } } diff --git a/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs index 77469fcdf..9343e05f5 100644 --- a/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dynamics/TargetDynamicMappingConfigurator.cs @@ -2,6 +2,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration.Dynamics { using System; using System.Collections.Generic; + using System.Dynamic; using System.Linq.Expressions; using AgileMapper.Configuration; using Dictionaries; @@ -13,7 +14,7 @@ internal class TargetDynamicMappingConfigurator : ITargetDynamicMappingConfigurator { public TargetDynamicMappingConfigurator(MappingConfigInfo configInfo) - : base(configInfo) + : base(configInfo.ForTargetType()) { } diff --git a/AgileMapper/Api/Configuration/MappingConfigurator.cs b/AgileMapper/Api/Configuration/MappingConfigurator.cs index c502b6811..a6a3101b2 100644 --- a/AgileMapper/Api/Configuration/MappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/MappingConfigurator.cs @@ -14,7 +14,12 @@ internal class MappingConfigurator : { public MappingConfigurator(MappingConfigInfo configInfo) { - ConfigInfo = configInfo.ForTargetType(); + ConfigInfo = configInfo; + + if ((ConfigInfo.TargetType ?? typeof(object)) == typeof(object)) + { + ConfigInfo.ForTargetType(); + } } protected MappingConfigInfo ConfigInfo { get; } diff --git a/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs b/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs index c370f5600..b616241dc 100644 --- a/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs +++ b/AgileMapper/Api/Configuration/TargetTypeSpecifier.cs @@ -81,6 +81,5 @@ public ITargetDictionaryMappingConfigurator ToDictionariesWithV /// public ITargetDynamicMappingConfigurator ToDynamics => new TargetDynamicMappingConfigurator(_configInfo.ForAllRuleSets().Set(DictionaryType.Expando)); - } } \ No newline at end of file diff --git a/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs index 8b4ea5673..0770d890d 100644 --- a/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs +++ b/AgileMapper/Configuration/Dictionaries/CustomDictionaryKey.cs @@ -92,7 +92,8 @@ private static bool IsPartOfExpandoObjectMapping(IMemberMapperData mapperData) { while (mapperData != null) { - if (mapperData.SourceMember.GetFriendlyTypeName() == nameof(ExpandoObject)) + if ((mapperData.SourceMember.GetFriendlyTypeName() == nameof(ExpandoObject)) || + (mapperData.TargetMember.GetFriendlyTypeName() == nameof(ExpandoObject))) { return true; } diff --git a/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs index be42da469..39e77c80c 100644 --- a/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs +++ b/AgileMapper/Configuration/Dictionaries/ElementKeyPartFactory.cs @@ -83,18 +83,22 @@ public static ElementKeyPartFactory For(string pattern, MappingConfigInfo config var prefix = patternMatch.Groups["Prefix"].Value; var suffix = patternMatch.Groups["Suffix"].Value; - if (!configInfo.IsForAllSourceTypes() && !configInfo.SourceType.IsEnumerable()) + if (!configInfo.IsForAllSourceTypes() && + (configInfo.SourceType != typeof(ExpandoObject)) && + configInfo.SourceType.IsEnumerable()) { configInfo = configInfo .Clone() - .ForSourceType(typeof(IEnumerable<>).MakeGenericType(configInfo.SourceType)); + .ForSourceType(configInfo.SourceType.GetEnumerableElementType()); } - if ((configInfo.TargetType != typeof(object)) && !configInfo.TargetType.IsEnumerable()) + if ((configInfo.TargetType != typeof(object)) && + (configInfo.TargetType != typeof(ExpandoObject)) && + configInfo.TargetType.IsEnumerable()) { configInfo = configInfo .Clone() - .ForTargetType(typeof(IEnumerable<>).MakeGenericType(configInfo.TargetType)); + .ForTargetType(configInfo.TargetType.GetEnumerableElementType()); } return new ElementKeyPartFactory(prefix, suffix, configInfo); diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 42b5c7d38..f2fdf9e68 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -65,6 +65,11 @@ public override Type GetElementType(Type sourceElementType) public override bool HasCompatibleType(Type type) { + if (type == typeof(ExpandoObject)) + { + return _rootDictionaryMember.Type == typeof(ExpandoObject); + } + if (base.HasCompatibleType(type)) { return true; @@ -75,11 +80,6 @@ public override bool HasCompatibleType(Type type) return false; } - if (type == typeof(ExpandoObject)) - { - return Type == type; - } - return type.IsDictionary(); } From 865e570e69b21bac3f03703d80c7ec22ef156a8f Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 21:38:18 +0000 Subject: [PATCH 66/74] Support for conditional custom mapping to custom ExpandoObject target members --- .../WhenConfiguringTargetDynamicMapping.cs | 53 +++++++++++++++++++ .../CustomDataSourceTargetMemberSpecifier.cs | 9 ++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index a0902cf1a..608d073d5 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -165,5 +165,58 @@ public void ShouldApplyACustomEnumerableElementPatternToASpecificDerivedSourceTy result["(1)_Address_Line1"].ShouldBe("Home!"); } } + + [Fact] + public void ShouldApplyAConfiguredConditionalTargetEntryValue() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToDynamics + .If((mcvm, d) => mcvm.Discount > 0.5) + .Map((mcvm, d) => mcvm.Name + " (Big discount!)") + .To(d => d["Name"]); + + var noDiscountSource = new MysteryCustomerViewModel { Name = "Schumer", Discount = 0.0 }; + var noDiscountResult = (IDictionary)mapper.Map(noDiscountSource).ToANew(); + + noDiscountResult["Name"].ShouldBe("Schumer"); + + var bigDiscountSource = new MysteryCustomerViewModel { Name = "Silverman", Discount = 0.6 }; + var bigDiscountResult = (IDictionary)mapper.Map(bigDiscountSource).ToANew(); + + bigDiscountResult["Name"].ShouldBe("Silverman (Big discount!)"); + } + } + + [Fact] + public void ShouldAllowACustomTargetEntryKey() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From() + .ToDynamics + .MapMember(mcvm => mcvm.Name) + .ToFullMemberName("CustomerName") + .And + .If((mcvm, d) => mcvm.Discount > 0.5) + .Map((mcvm, d) => mcvm.Name + " (Big discount!)") + .To(d => d["Name"]); + + var noDiscountSource = new MysteryCustomerViewModel { Name = "Schumer", Discount = 0.0 }; + var noDiscountResult = (IDictionary)mapper.Map(noDiscountSource).ToANew(); + + noDiscountResult["CustomerName"].ShouldBe("Schumer"); + noDiscountResult.ShouldNotContainKey("Name"); + + var bigDiscountSource = new MysteryCustomerViewModel { Name = "Silverman", Discount = 0.6 }; + var bigDiscountResult = (IDictionary)mapper.Map(bigDiscountSource).ToANew(); + + bigDiscountResult["CustomerName"].ShouldBe("Silverman"); + bigDiscountResult["Name"].ShouldBe("Silverman (Big discount!)"); + } + } } } diff --git a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs index 5148117ed..1017f20b6 100644 --- a/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs +++ b/AgileMapper/Api/Configuration/CustomDataSourceTargetMemberSpecifier.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Api.Configuration { using System; + using System.Dynamic; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -110,9 +111,11 @@ private bool IsDictionaryEntry(LambdaExpression targetMemberLambda, out Dictiona var entryKey = (string)((ConstantExpression)entryKeyExpression).Value; - var rootMember = (DictionaryTargetMember)_configInfo.MapperContext - .QualifiedMemberFactory - .RootTarget(); + var memberFactory = _configInfo.MapperContext.QualifiedMemberFactory; + + var rootMember = (DictionaryTargetMember)(_configInfo.TargetType == typeof(ExpandoObject) + ? memberFactory.RootTarget() + : memberFactory.RootTarget()); entryMember = rootMember.Append(typeof(TSource), entryKey); return true; From f34f87dd13e7ce5d4a8a569bcd8a3151df80bef9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 21:40:00 +0000 Subject: [PATCH 67/74] Test coverage for various conditional mappings to custom dynamic target members --- .../WhenConfiguringTargetDynamicMapping.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index 608d073d5..16fbe4d81 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -218,5 +218,34 @@ public void ShouldAllowACustomTargetEntryKey() bigDiscountResult["Name"].ShouldBe("Silverman (Big discount!)"); } } + + [Fact] + public void ShouldApplyACustomConfiguredMemberConditionally() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From
() + .ToDynamics + .If(ctx => !string.IsNullOrEmpty(ctx.Source.Line2)) + .Map(ctx => "Present") + .To(d => d["Line2State"]) + .But + .If(ctx => string.IsNullOrEmpty(ctx.Source.Line2)) + .Map(ctx => "Missing") + .To(d => d["Line2State"]); + + var line2Source = new Address { Line1 = "Line 1: Yes", Line2 = "Line 2: Yes!" }; + var line2Result = (IDictionary)mapper.Map(line2Source).ToANew(); + + line2Result["Line2State"].ShouldBe("Present"); + line2Result["Line2"].ShouldBe("Line 2: Yes!"); + + var noLine2Source = new Address { Line1 = "Line 1: Yes", Line2 = string.Empty }; + var noLine2Result = (IDictionary)mapper.Map(noLine2Source).ToANew(); + + noLine2Result["Line2State"].ShouldBe("Missing"); + } + } } } From b95736a0ebedd57c69f4b1f65f9a68637bb86e9c Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 21:56:36 +0000 Subject: [PATCH 68/74] Ensuring target dictionary configuration is not applied to target dynamics --- .../WhenConfiguringTargetDynamicMapping.cs | 25 +++++++++++++++++++ .../ConfiguredDataSourceFactory.cs | 4 +-- .../Dictionaries/DictionaryTargetMember.cs | 2 +- .../Members/MemberMapperDataExtensions.cs | 2 +- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index 16fbe4d81..d250ddd9f 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -247,5 +247,30 @@ public void ShouldApplyACustomConfiguredMemberConditionally() noLine2Result["Line2State"].ShouldBe("Missing"); } } + + [Fact] + public void ShouldNotApplyTargetDictionaryConfigurationToDynamics() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDictionaries + .Map((pf, d) => pf.Value) + .To(d => d["Name"]); + + var source = new PublicField { Value = "LaLaLa" }; + + dynamic target = new ExpandoObject(); + + target.Name = "Doodeedoo"; + + mapper.Map(source).Over(target); + + ((object)target).ShouldNotBeNull(); + ((string)target.Value).ShouldBe("LaLaLa"); + ((string)target.Name).ShouldBe("Doodeedoo"); + } + } } } diff --git a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs b/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs index 2c242be47..04052fa40 100644 --- a/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs +++ b/AgileMapper/DataSources/ConfiguredDataSourceFactory.cs @@ -4,9 +4,7 @@ using Configuration; using Members; - internal class ConfiguredDataSourceFactory : - UserConfiguredItemBase, - IPotentialClone + internal class ConfiguredDataSourceFactory : UserConfiguredItemBase, IPotentialClone { private readonly ConfiguredLambdaInfo _dataSourceLambda; diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index f2fdf9e68..6eb6eea49 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -80,7 +80,7 @@ public override bool HasCompatibleType(Type type) return false; } - return type.IsDictionary(); + return (_rootDictionaryMember.Type != typeof(ExpandoObject)) && type.IsDictionary(); } public DictionaryTargetMember Append(ParameterExpression key) diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 5570ad6ca..4ef79b0fe 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -466,7 +466,7 @@ private static Expression GetAccess( var propertyName = new[] { RootSourceMemberName, RootTargetMemberName }[contextTypesIndex]; - var property = contextAccess.Type.GetPublicInstanceProperty(propertyName) ?? + var property = contextAccess.Type.GetPublicInstanceProperty(propertyName) ?? typeof(IMappingData<,>) .MakeGenericType(contextTypes[0], contextTypes[1]) .GetPublicInstanceProperty(propertyName); From fe677b3cee840b452a4619d63f3c583d7201a133 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 21:59:38 +0000 Subject: [PATCH 69/74] Test coverage ensuring target dynamic config isn't applied to target dictionaries --- .../WhenConfiguringTargetDynamicMapping.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index d250ddd9f..f61a9ed19 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -272,5 +272,34 @@ public void ShouldNotApplyTargetDictionaryConfigurationToDynamics() ((string)target.Name).ShouldBe("Doodeedoo"); } } + + [Fact] + public void ShouldNotApplyDynamicConfigurationToDictionaries() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDynamics + .Map(ctx => ctx.Source.Value) + .To(d => d["Name"]); + + var source = new PublicField + { + Value = 123 + }; + + var target = new Dictionary + { + ["Name"] = 1, + ["Value"] = 2 + }; + + mapper.Map(source).Over(target); + + target["Name"].ShouldBe(1); + target["Value"].ShouldBe(123); + } + } } } From 136b92e27866883e9138cf39e7403d40ecb0571d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 22:06:13 +0000 Subject: [PATCH 70/74] Test coverage for non-conflict of target dictionary and dynamic configuration --- .../WhenConfiguringTargetDynamicMapping.cs | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs index f61a9ed19..2d0bff828 100644 --- a/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs +++ b/AgileMapper.UnitTests/Dynamics/Configuration/WhenConfiguringTargetDynamicMapping.cs @@ -274,7 +274,7 @@ public void ShouldNotApplyTargetDictionaryConfigurationToDynamics() } [Fact] - public void ShouldNotApplyDynamicConfigurationToDictionaries() + public void ShouldNotApplyTargetDynamicConfigurationToDictionaries() { using (var mapper = Mapper.CreateNew()) { @@ -301,5 +301,49 @@ public void ShouldNotApplyDynamicConfigurationToDictionaries() target["Value"].ShouldBe(123); } } + + [Fact] + public void ShouldNotConflictTargetDynamicAndDictionaryConfiguration() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From>() + .ToDictionaries + .UseMemberNameSeparator("-"); + + mapper.WhenMapping + .From>() + .ToDynamics + .UseMemberNameSeparator("+"); + + var source = new PublicProperty
+ { + Value = new Address { Line1 = "L1", Line2 = "L2" } + }; + + var dictionaryTarget = new Dictionary + { + ["Value-Line1"] = "Line 1!", + ["Value-Line2"] = "Line 2!" + }; + + mapper.Map(source).Over(dictionaryTarget); + + dictionaryTarget["Value-Line1"].ShouldBe("L1"); + dictionaryTarget["Value-Line2"].ShouldBe("L2"); + + dynamic dynamicTarget = new ExpandoObject(); + var targetDynamicDictionary = (IDictionary)dynamicTarget; + + targetDynamicDictionary["Value+Line1"] = "Line 1?!"; + targetDynamicDictionary["Value+Line2"] = "Line 2?!"; + + mapper.Map(source).Over(dynamicTarget); + + targetDynamicDictionary["Value+Line1"].ShouldBe("L1"); + targetDynamicDictionary["Value+Line2"].ShouldBe("L2"); + } + } } } From 00d73b421631ec382e0d831c22abb00b3b8afa9b Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Tue, 19 Dec 2017 22:13:30 +0000 Subject: [PATCH 71/74] Flattening objects using ToANew() --- .../WhenFlatteningObjects.cs | 10 +- AgileMapper/Flattening/FlattenedObject.cs | 18 -- AgileMapper/Flattening/ObjectFlattener.cs | 159 ------------------ AgileMapper/Mapper.cs | 3 +- AgileMapper/MapperContext.cs | 4 - 5 files changed, 8 insertions(+), 186 deletions(-) delete mode 100644 AgileMapper/Flattening/FlattenedObject.cs delete mode 100644 AgileMapper/Flattening/ObjectFlattener.cs diff --git a/AgileMapper.UnitTests/WhenFlatteningObjects.cs b/AgileMapper.UnitTests/WhenFlatteningObjects.cs index 5a69505fc..e33f12322 100644 --- a/AgileMapper.UnitTests/WhenFlatteningObjects.cs +++ b/AgileMapper.UnitTests/WhenFlatteningObjects.cs @@ -19,13 +19,15 @@ public void ShouldIncludeASimpleTypeMember() } [Fact] - public void ShouldIncludeASimpleTypeArrayMember() + public void ShouldFlattenASimpleTypeArrayMember() { var source = new PublicProperty { Value = new[] { 1L, 2L, 3L } }; var result = Mapper.Flatten(source); ((object)result).ShouldNotBeNull(); - ((long[])result.Value).ShouldBe(1, 2, 3); + ((long)result.Value_0_).ShouldBe(1L); + ((long)result.Value_1_).ShouldBe(2L); + ((long)result.Value_2_).ShouldBe(3L); } [Fact] @@ -82,7 +84,7 @@ public void ShouldIncludeAComplexTypeEnumerableMemberSimpleTypeMember() }; var result = Mapper.Flatten(source); - ((string)result.Value0_ProductId).ShouldBe("SumminElse"); + ((string)result.Value_0__ProductId).ShouldBe("SumminElse"); } [Fact] @@ -94,7 +96,7 @@ public void ShouldHandleANullComplexTypeEnumerableMemberElement() }; var result = Mapper.Flatten(source); - ((string)result.Value0_ProductId).ShouldBeNull(); + ((string)result.Value_0__ProductId).ShouldBeNull(); } } } diff --git a/AgileMapper/Flattening/FlattenedObject.cs b/AgileMapper/Flattening/FlattenedObject.cs deleted file mode 100644 index 1592def4d..000000000 --- a/AgileMapper/Flattening/FlattenedObject.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace AgileObjects.AgileMapper.Flattening -{ - using System.Collections.Generic; - using System.Dynamic; - - internal class FlattenedObject : DynamicObject - { - private readonly Dictionary _propertyValuesByName; - - public FlattenedObject(Dictionary propertyValuesByName) - { - _propertyValuesByName = propertyValuesByName; - } - - public override bool TryGetMember(GetMemberBinder binder, out object result) - => _propertyValuesByName.TryGetValue(binder.Name, out result); - } -} diff --git a/AgileMapper/Flattening/ObjectFlattener.cs b/AgileMapper/Flattening/ObjectFlattener.cs deleted file mode 100644 index cfb28c5f8..000000000 --- a/AgileMapper/Flattening/ObjectFlattener.cs +++ /dev/null @@ -1,159 +0,0 @@ -namespace AgileObjects.AgileMapper.Flattening -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; - using Extensions.Internal; - using Members; - using NetStandardPolyfills; - - internal class ObjectFlattener - { - #region Cached Items - - private static readonly ParameterExpression _objectFlattenerParameter = Parameters.Create(); - - private static readonly MethodInfo _getPropertiesMethod = typeof(ObjectFlattener) - .GetNonPublicInstanceMethods("GetPropertyValuesByName") - .Last(); - - #endregion - - public FlattenedObject Flatten(TSource source) - => new FlattenedObject(GetPropertyValuesByName(source)); - - private Dictionary GetPropertyValuesByName(TSource source) - { - var propertyValuesByName = - GetPropertyValuesByName(source, parentMemberName: null) - .ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2); - - return propertyValuesByName; - } - - internal IEnumerable> GetPropertyValuesByName( - TSource parentObject, - string parentMemberName) - { - foreach (var sourceMember in GlobalContext.Instance.MemberCache.GetSourceMembers(typeof(TSource))) - { - var name = GetName(parentMemberName, sourceMember); - var value = GetValue(parentObject, sourceMember); - - if (sourceMember.IsSimple || (sourceMember.IsEnumerable && sourceMember.ElementType.IsSimple())) - { - yield return Tuple.Create(name, value); - continue; - } - - if (sourceMember.IsComplex) - { - foreach (var nestedPropertyValueByName in GetComplexTypePropertyValuesByName(value, sourceMember.Type, name)) - { - yield return nestedPropertyValueByName; - } - - continue; - } - - foreach (var nestedPropertyValueByName in GetEnumerablePropertyValuesByName(value, sourceMember.ElementType, name)) - { - yield return nestedPropertyValueByName; - } - } - } - - private static string GetName(string parentMemberName, Member sourceMember) - { - if (parentMemberName == null) - { - return sourceMember.Name; - } - - return parentMemberName + "_" + sourceMember.Name; - } - - private object GetValue(TSource source, Member member) - { - if (source == null) - { - return member.Type.IsValueType() ? Activator.CreateInstance(member.Type) : null; - } - - var cacheKey = typeof(TSource).FullName + $".{member.Name}: GetValue"; - - var valueFunc = GlobalContext.Instance.Cache.GetOrAdd(cacheKey, k => - { - var sourceParameter = Parameters.Create("source"); - var valueAccess = member.GetAccess(sourceParameter); - - if (member.Type.IsValueType()) - { - valueAccess = valueAccess.GetConversionTo(typeof(object)); - } - - var valueLambda = Expression.Lambda>(valueAccess, sourceParameter); - - return valueLambda.Compile(); - }); - - return valueFunc.Invoke(source); - } - - private IEnumerable> GetComplexTypePropertyValuesByName( - object parentComplexType, - Type parentMemberType, - string parentMemberName) - { - var cacheKey = parentMemberType.FullName + ": GetPropertiesCaller"; - - var getPropertiesFunc = GlobalContext.Instance.Cache.GetOrAdd(cacheKey, k => - { - var sourceParameter = Parameters.Create("source"); - var parentMemberNameParameter = Parameters.Create("parentMemberName"); - - var getPropertiesCall = Expression.Call( - _objectFlattenerParameter, - _getPropertiesMethod.MakeGenericMethod(parentMemberType), - sourceParameter.GetConversionTo(parentMemberType), - parentMemberNameParameter); - - var getPropertiesLambda = Expression - .Lambda>>>( - getPropertiesCall, - _objectFlattenerParameter, - sourceParameter, - parentMemberNameParameter); - - return getPropertiesLambda.Compile(); - }); - - return getPropertiesFunc.Invoke(this, parentComplexType, parentMemberName); - } - - private IEnumerable> GetEnumerablePropertyValuesByName( - object parentEnumerable, - Type declaredEnumerableElementType, - string parentMemberName) - { - var items = (IEnumerable)parentEnumerable; - - var i = 0; - - foreach (var item in items) - { - var itemType = item?.GetType() ?? declaredEnumerableElementType; - - foreach (var nestedPropertyValueByName in GetComplexTypePropertyValuesByName(item, itemType, parentMemberName + i)) - { - yield return nestedPropertyValueByName; - } - - ++i; - } - } - } -} \ No newline at end of file diff --git a/AgileMapper/Mapper.cs b/AgileMapper/Mapper.cs index 29c35919d..0fa76f49d 100644 --- a/AgileMapper/Mapper.cs +++ b/AgileMapper/Mapper.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper { using System; + using System.Dynamic; using System.Linq.Expressions; using Api; using Api.Configuration; @@ -204,7 +205,7 @@ TSource IMapper.Clone( return ((IMapper)this).Map(source).ToANew(configurations); } - dynamic IMapper.Flatten(TSource source) => Context.ObjectFlattener.Flatten(source); + dynamic IMapper.Flatten(TSource source) => Map(source).ToANew(); ITargetTypeSelector IMapper.Map(TSource source) => new MappingExecutor(source, Context); diff --git a/AgileMapper/MapperContext.cs b/AgileMapper/MapperContext.cs index 80d60e96b..823ad1cd8 100644 --- a/AgileMapper/MapperContext.cs +++ b/AgileMapper/MapperContext.cs @@ -4,7 +4,6 @@ using Configuration; using Configuration.Inline; using DataSources; - using Flattening; using Members; using Members.Sources; using ObjectPopulation; @@ -15,7 +14,6 @@ internal class MapperContext { internal static readonly MapperContext Default = new MapperContext(); - private ObjectFlattener _objectFlattener; private InlineMapperContextSet _inlineContexts; public MapperContext() @@ -44,8 +42,6 @@ public MapperContext() public ObjectMapperFactory ObjectMapperFactory { get; } - public ObjectFlattener ObjectFlattener => _objectFlattener ?? (_objectFlattener = new ObjectFlattener()); - public InlineMapperContextSet InlineContexts => _inlineContexts ?? (_inlineContexts = new InlineMapperContextSet(this)); public UserConfigurationSet UserConfigurations { get; } From 5756d76b705299b0dbdc9d1a84c3f826d88e2431 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 20 Dec 2017 08:21:01 +0000 Subject: [PATCH 72/74] Tidying --- AgileMapper/DataSources/DataSourceSet.cs | 5 +++-- .../Dictionaries/DictionaryTargetMember.cs | 21 ++++--------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index adb90fe0c..aede04d73 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -90,9 +90,10 @@ public Expression GetPopulationExpression(IMemberMapperData mapperData) private Expression GetFallbackValueOrNull(IMemberMapperData mapperData) { - var fallbackValue = _dataSources.Last().Value; + var finalDataSource = _dataSources.Last(); + var fallbackValue = finalDataSource.Value; - if (_dataSources.HasOne()) + if (finalDataSource.IsConditional || _dataSources.HasOne()) { return fallbackValue; } diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 6eb6eea49..a7bfe6160 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -157,28 +157,15 @@ public override Expression GetAccess(Expression instance, IMemberMapperData mapp return base.GetAccess(instance, mapperData); } - if (ReturnNullAccess()) + if (ReturnKeyedAccess()) { - return Type.ToDefaultExpression(); + return GetKeyedAccess(mapperData); } - return GetKeyedAccess(mapperData); + return Type.ToDefaultExpression(); } - private bool ReturnNullAccess() - { - if (Type == ValueType) - { - return false; - } - - if (Type.IsSimple()) - { - return false; - } - - return true; - } + private bool ReturnKeyedAccess() => (Type == ValueType) || Type.IsSimple(); private Expression GetKeyedAccess(IMemberMapperData mapperData) { From 7ebdf2b079029d4ded9864cefd52401add6fdbef Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 20 Dec 2017 08:44:57 +0000 Subject: [PATCH 73/74] Fixing flattening test --- .../WhenFlatteningObjects.cs | 9 +++-- .../Enumerables/IPopulationLoopData.cs | 36 ----------------- .../PopulationLoopDataExtensions.cs | 40 +++++++++++++++++++ 3 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs diff --git a/AgileMapper.UnitTests/WhenFlatteningObjects.cs b/AgileMapper.UnitTests/WhenFlatteningObjects.cs index e33f12322..5e35311a7 100644 --- a/AgileMapper.UnitTests/WhenFlatteningObjects.cs +++ b/AgileMapper.UnitTests/WhenFlatteningObjects.cs @@ -52,9 +52,10 @@ public void ShouldIncludeAComplexTypeMemberSimpleTypeMember() public void ShouldHandleANullComplexTypeMember() { var source = new PublicProperty> { Value = null }; - var result = Mapper.Flatten(source); + var result = (IDictionary)Mapper.Flatten(source); - ((int)result.Value_Value).ShouldBeDefault(); + result.ShouldNotContainKey("Value"); + result.ShouldNotContainKey("Value_Value"); } [Fact] @@ -94,9 +95,9 @@ public void ShouldHandleANullComplexTypeEnumerableMemberElement() { Value = new Product[] { null } }; - var result = Mapper.Flatten(source); + var result = (IDictionary)Mapper.Flatten(source); - ((string)result.Value_0__ProductId).ShouldBeNull(); + result.ShouldNotContainKey("Value_0__ProductId"); } } } diff --git a/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs index 277ddcf91..c3849ab69 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/IPopulationLoopData.cs @@ -1,8 +1,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables { - using System; using System.Linq.Expressions; - using Extensions.Internal; internal interface IPopulationLoopData { @@ -18,38 +16,4 @@ internal interface IPopulationLoopData Expression Adapt(LoopExpression loop); } - - internal static class PopulationLoopDataExtensions - { - public static Expression BuildPopulationLoop( - this TLoopData loopData, - EnumerablePopulationBuilder builder, - IObjectMappingData mappingData, - Func elementPopulationFactory) - where TLoopData : IPopulationLoopData - { - // TODO: Not all enumerable mappings require the Counter - var breakLoop = Expression.Break(Expression.Label(typeof(void), "Break")); - - var elementPopulation = elementPopulationFactory.Invoke(loopData, mappingData); - - var loopBody = Expression.Block( - Expression.IfThen(loopData.LoopExitCheck, breakLoop), - elementPopulation, - builder.GetCounterIncrement()); - - var populationLoop = loopData.NeedsContinueTarget - ? Expression.Loop(loopBody, breakLoop.Target, loopData.ContinueLoopTarget) - : Expression.Loop(loopBody, breakLoop.Target); - - var adaptedLoop = loopData.Adapt(populationLoop); - - var population = Expression.Block( - new[] { builder.Counter }, - builder.Counter.AssignTo(0.ToConstantExpression()), - adaptedLoop); - - return population; - } - } } \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs b/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs new file mode 100644 index 000000000..db225cf6c --- /dev/null +++ b/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs @@ -0,0 +1,40 @@ +namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables +{ + using System; + using System.Linq.Expressions; + using Extensions.Internal; + + internal static class PopulationLoopDataExtensions + { + public static Expression BuildPopulationLoop( + this TLoopData loopData, + EnumerablePopulationBuilder builder, + IObjectMappingData mappingData, + Func elementPopulationFactory) + where TLoopData : IPopulationLoopData + { + // TODO: Not all enumerable mappings require the Counter + var breakLoop = Expression.Break(Expression.Label(typeof(void), "Break")); + + var elementPopulation = elementPopulationFactory.Invoke(loopData, mappingData); + + var loopBody = Expression.Block( + loopData.GetLoopGuard(breakLoop), + elementPopulation, + builder.GetCounterIncrement()); + + var populationLoop = loopData.NeedsContinueTarget + ? Expression.Loop(loopBody, breakLoop.Target, loopData.ContinueLoopTarget) + : Expression.Loop(loopBody, breakLoop.Target); + + var adaptedLoop = loopData.Adapt(populationLoop); + + var population = Expression.Block( + new[] { builder.Counter }, + builder.Counter.AssignTo(0.ToConstantExpression()), + adaptedLoop); + + return population; + } + } +} \ No newline at end of file From 8c59fbfbe50f80a1f03bacfda34039e56b8b1b45 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 20 Dec 2017 18:08:13 +0000 Subject: [PATCH 74/74] Handling null entries in complex type collections being flattened or mapped to dictionaries or dynamics --- .../DictionaryPopulationBuilder.cs | 66 ++++++++++++++++--- .../PopulationLoopDataExtensions.cs | 2 +- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 4bf71bb47..3c60413f1 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables.Dictionaries { + using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -115,14 +116,51 @@ private Expression AssignDictionaryEntry( } var derivedSourceTypes = mappingData.MapperData.GetDerivedSourceTypes(); + var hasDerivedSourceTypes = derivedSourceTypes.Any(); - if (derivedSourceTypes.None()) + List typedVariables; + List mappingExpressions; + + if (hasDerivedSourceTypes) { - return GetPopulation(loopData, dictionaryEntryMember, mappingData); + typedVariables = new List(derivedSourceTypes.Count); + mappingExpressions = new List(typedVariables.Count * 2 + 2); + + AddDerivedSourceTypePopulations( + loopData, + dictionaryEntryMember, + mappingData, + derivedSourceTypes, + typedVariables, + mappingExpressions); + } + else + { + typedVariables = null; + mappingExpressions = new List(2); } - var typedVariables = new List(derivedSourceTypes.Count); - var mappingExpressions = new List(typedVariables.Count * 2 + 1); + InsertSourceElementNullCheck(loopData, mappingExpressions); + + mappingExpressions.Add(GetPopulation(loopData, dictionaryEntryMember, mappingData)); + + var mappingBlock = hasDerivedSourceTypes + ? Expression.Block(typedVariables, mappingExpressions) + : Expression.Block(mappingExpressions); + + return mappingBlock; + } + + private void AddDerivedSourceTypePopulations( + IPopulationLoopData loopData, + QualifiedMember dictionaryEntryMember, + IObjectMappingData mappingData, + IEnumerable derivedSourceTypes, + ICollection typedVariables, + ICollection mappingExpressions) + { + var sourceElement = loopData.GetSourceElementValue(); + var mapNextElement = Expression.Continue(loopData.ContinueLoopTarget); var orderedDerivedSourceTypes = derivedSourceTypes .OrderBy(t => t, TypeComparer.MostToLeastDerived); @@ -130,7 +168,6 @@ private Expression AssignDictionaryEntry( foreach (var derivedSourceType in orderedDerivedSourceTypes) { var derivedSourceCheck = new DerivedSourceTypeCheck(derivedSourceType); - var sourceElement = loopData.GetSourceElementValue(); var typedVariableAssignment = derivedSourceCheck.GetTypedVariableAssignment(sourceElement); typedVariables.Add(derivedSourceCheck.TypedVariable); @@ -139,18 +176,29 @@ private Expression AssignDictionaryEntry( var derivedTypeMapping = GetDerivedTypeMapping(derivedSourceCheck, mappingData); var derivedTypePopulation = GetPopulation(derivedTypeMapping, dictionaryEntryMember, mappingData); var incrementCounter = _wrappedBuilder.GetCounterIncrement(); - var mapNextElement = Expression.Continue(loopData.ContinueLoopTarget); var derivedMappingBlock = Expression.Block(derivedTypePopulation, incrementCounter, mapNextElement); var ifDerivedTypeReturn = Expression.IfThen(derivedSourceCheck.TypeCheck, derivedMappingBlock); mappingExpressions.Add(ifDerivedTypeReturn); } + } - mappingExpressions.Add(GetPopulation(loopData, dictionaryEntryMember, mappingData)); + private static void InsertSourceElementNullCheck(IPopulationLoopData loopData, IList mappingExpressions) + { + var sourceElement = loopData.GetSourceElementValue(); + + if (sourceElement.Type.CannotBeNull()) + { + return; + } - var mappingBlock = Expression.Block(typedVariables, mappingExpressions); + loopData.NeedsContinueTarget = true; - return mappingBlock; + var sourceElementIsNull = sourceElement.GetIsDefaultComparison(); + var continueLoop = Expression.Continue(loopData.ContinueLoopTarget); + var ifNullContinue = Expression.IfThen(sourceElementIsNull, continueLoop); + + mappingExpressions.Insert(0, ifNullContinue); } private Expression GetPopulation( diff --git a/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs b/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs index db225cf6c..091fba010 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/PopulationLoopDataExtensions.cs @@ -19,7 +19,7 @@ public static Expression BuildPopulationLoop( var elementPopulation = elementPopulationFactory.Invoke(loopData, mappingData); var loopBody = Expression.Block( - loopData.GetLoopGuard(breakLoop), + Expression.IfThen(loopData.LoopExitCheck, breakLoop), elementPopulation, builder.GetCounterIncrement());