From 1ada77a67d6c6e036c0435dbe045dc25d2cadaf2 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Fri, 17 May 2019 14:34:23 +0100 Subject: [PATCH 01/14] Removing unused dictionary member population code and optimising --- .../Dictionaries/DictionaryTargetMember.cs | 62 +------------------ .../EnumerablePopulationBuilder.cs | 25 ++++++-- 2 files changed, 22 insertions(+), 65 deletions(-) diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 2c9a1eac4..7949dd9a2 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -259,11 +259,7 @@ public override Expression GetPopulation(Expression value, IMemberMapperData map } var keyedAccess = GetKeyedAccess(mapperData); - - var convertedValue = - GetCheckedValueOrNull(value, keyedAccess, mapperData) ?? - mapperData.GetValueConversion(value, ValueType); - + var convertedValue = mapperData.GetValueConversion(value, ValueType); var keyedAssignment = keyedAccess.AssignTo(convertedValue); return keyedAssignment; @@ -280,62 +276,6 @@ private bool ValueIsFlattening(Expression value, out Expression flattening) return false; } - private Expression GetCheckedValueOrNull(Expression value, Expression keyedAccess, IMemberMapperData mapperData) - { - if (HasSimpleEntries) - { - return null; - } - - if ((value.NodeType != Block) && (value.NodeType != Try) || mapperData.TargetIsDefinitelyUnpopulated()) - { - return mapperData.SourceMember.IsEnumerable ? value.GetConversionTo(ValueType) : null; - } - - var checkedAccess = GetAccessChecked(mapperData); - var existingValue = checkedAccess.Variables.First(); - - if (value.NodeType == Try) - { - return GetCheckedTryCatch((TryExpression)value, keyedAccess, checkedAccess, existingValue); - } - - var checkedValue = ((BlockExpression)value).Replace(keyedAccess, existingValue); - - return checkedValue.Update( - checkedValue.Variables.Append(existingValue), - checkedValue.Expressions.Prepend(checkedAccess.Expressions.First())); - } - - private static Expression GetCheckedTryCatch( - TryExpression tryCatchValue, - Expression keyedAccess, - Expression checkedAccess, - ParameterExpression existingValue) - { - var existingValueOrDefault = Expression.Condition( - checkedAccess, - existingValue, - existingValue.Type.ToDefaultExpression()); - - var replacements = new ExpressionReplacementDictionary(1) { [keyedAccess] = existingValueOrDefault }; - - var updatedCatchHandlers = tryCatchValue - .Handlers - .ProjectToArray(handler => handler.Update( - handler.Variable, - handler.Filter.Replace(replacements), - handler.Body.Replace(replacements))); - - var updatedTryCatch = tryCatchValue.Update( - tryCatchValue.Body, - updatedCatchHandlers, - tryCatchValue.Finally, - tryCatchValue.Fault); - - return Expression.Block(new[] { existingValue }, updatedTryCatch); - } - public DictionaryTargetMember WithTypeOf(Member sourceMember) { if (sourceMember.Type == Type) diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 00a462a82..ee83c00a7 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -595,20 +595,37 @@ private static Expression GetValueCheckedElementMapping( if (mapping.NodeType != ExpressionType.Try) { - return Expression.Block( - new[] { valueVariable }, + return existingElementValueCheck.Update( + existingElementValueCheck.Variables, existingElementValueCheck.Expressions.Append(mapping)); } var mappingTryCatch = (TryExpression)mapping; + Expression mappingTryCatchBody; + + if (mappingTryCatch.Body.NodeType != ExpressionType.Block) + { + mappingTryCatchBody = Expression.Block( + new[] { valueVariable }, + existingElementValueCheck.Expressions.Append(mappingTryCatch.Body)); + } + else + { + var mappingTryCatchBodyBlock = (BlockExpression)mappingTryCatch.Body; + + mappingTryCatchBody = mappingTryCatchBodyBlock.Update( + mappingTryCatchBodyBlock.Variables.Prepend(valueVariable), + existingElementValueCheck.Expressions.Append(mappingTryCatchBodyBlock.Expressions)); + } + mapping = mappingTryCatch.Update( - Expression.Block(existingElementValueCheck.Expressions.Append(mappingTryCatch.Body)), + mappingTryCatchBody, mappingTryCatch.Handlers, mappingTryCatch.Finally, mappingTryCatch.Fault); - return Expression.Block(new[] { valueVariable }, mapping); + return mapping; } public Expression GetSimpleElementConversion(Expression sourceElement) From 1da251687a7d2b559146e623941b551f4eac02f5 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 22 May 2019 11:14:11 +0100 Subject: [PATCH 02/14] QueryString class test coverage --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenWorkingWithQueryStrings.cs | 144 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 AgileMapper.UnitTests/WhenWorkingWithQueryStrings.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 8d6904379..8b18a6e0f 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -301,6 +301,7 @@ + diff --git a/AgileMapper.UnitTests/WhenWorkingWithQueryStrings.cs b/AgileMapper.UnitTests/WhenWorkingWithQueryStrings.cs new file mode 100644 index 000000000..dbf1eab14 --- /dev/null +++ b/AgileMapper.UnitTests/WhenWorkingWithQueryStrings.cs @@ -0,0 +1,144 @@ +namespace AgileObjects.AgileMapper.UnitTests +{ + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using Common; +#if !NET35 + using Xunit; +#else + using Fact = NUnit.Framework.TestAttribute; + + [NUnit.Framework.TestFixture] +#endif + public class WhenWorkingWithQueryStrings + { + [Fact] + public void ShouldParseANameValueString() + { + var qs = QueryString.Parse("data=value"); + + qs.ShouldHaveSingleItem(); + qs.ShouldContainKeyAndValue("data", "value"); + } + + [Fact] + public void ShouldHandleALeadingQuestionMark() + { + var qs = QueryString.Parse("?key=value"); + + qs.ShouldHaveSingleItem(); + qs.ShouldContainKeyAndValue("key", "value"); + } + + [Fact] + public void ShouldExplicitlyConvertToString() + { + var qs = QueryString.Parse("key=value"); + + var stringQs = (string)qs; + + stringQs.ShouldBe("key=value"); + } + + [Fact] + public void ShouldEnumerateKeysAndValues() + { + var qs = QueryString.Parse("?key1=value1&key2=value2&key3=value3"); + + var i = 1; + + foreach (var keyAndValue in qs) + { + keyAndValue.Key.ShouldBe("key" + i); + keyAndValue.Value.ShouldBe("value" + i); + + ++i; + } + } + + [Fact] + public void ShouldActAsAnIDictionary() + { + IDictionary qs = QueryString.Parse("key1=value1"); + + qs.ShouldHaveSingleItem().Key.ShouldBe("key1"); + + qs.Add("key2", "value2"); + + qs.Keys.Count.ShouldBe(2); + qs.Keys.First().ShouldBe("key1"); + qs.Keys.Second().ShouldBe("key2"); + + qs.Values.Count.ShouldBe(2); + qs.Values.First().ShouldBe("value1"); + qs.Values.Second().ShouldBe("value2"); + + qs.Count.ShouldBe(2); + qs.First().Key.ShouldBe("key1"); + qs.First().Value.ShouldBe("value1"); + qs.Second().Key.ShouldBe("key2"); + qs.Second().Value.ShouldBe("value2"); + + qs.ContainsKey("key1").ShouldBeTrue(); + qs.Remove("key1"); + qs.ContainsKey("key1").ShouldBeFalse(); + + qs.ShouldHaveSingleItem().Key.ShouldBe("key2"); + + qs.TryGetValue("key2", out var value2).ShouldBeTrue(); + value2.ShouldBe("value2"); + + qs["key2"].ShouldBe("value2"); + qs["key2"] = "magic!"; + qs["key2"].ShouldBe("magic!"); + } + + [Fact] + public void ShouldActAsAnICollection() + { + ICollection> qs = QueryString.Parse("key1=value1"); + + qs.IsReadOnly.ShouldBeFalse(); + qs.ShouldHaveSingleItem().Key.ShouldBe("key1"); + + qs.Add(new KeyValuePair("key2", "value2")); + + qs.Count.ShouldBe(2); + qs.First().Key.ShouldBe("key1"); + qs.First().Value.ShouldBe("value1"); + qs.Second().Key.ShouldBe("key2"); + qs.Second().Value.ShouldBe("value2"); + + var keyValue1 = new KeyValuePair("key1", "value1"); + + qs.Contains(keyValue1).ShouldBeTrue(); + qs.Remove(keyValue1); + qs.Contains(keyValue1).ShouldBeFalse(); + + qs.ShouldHaveSingleItem().Key.ShouldBe("key2"); + + var copyCollection = new KeyValuePair[1]; + + qs.CopyTo(copyCollection, 0); + + copyCollection[0].Key.ShouldBe("key2"); + copyCollection[0].Value.ShouldBe("value2"); + + qs.Clear(); + qs.ShouldBeEmpty(); + } + + [Fact] + public void ShouldActAsAnIEnumerable() + { + IEnumerable qs = QueryString.Parse("key1=value1"); + + foreach (KeyValuePair keyAndValue in qs) + { + keyAndValue.Key.ShouldBe("key1"); + keyAndValue.Value.ShouldBe("value1"); + } + } + } +} From 75b981be6a2bf7d78724b597577a70278ca7763f Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 8 Jun 2019 20:14:37 +0100 Subject: [PATCH 03/14] Optimising DictionaryTargetMember.TryGetMappingBody, IList.Append(IList) and MappingFactory.UseLocalValueVariable --- .../Internal/EnumerableExtensions.cs | 19 ++++- .../Internal/ExpressionExtensions.cs | 69 ------------------- .../Dictionaries/DictionaryTargetMember.cs | 61 +++++++++++++++- .../ObjectPopulation/MappingFactory.cs | 8 ++- 4 files changed, 83 insertions(+), 74 deletions(-) diff --git a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index 492cab8f1..cb57c3633 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -152,6 +152,11 @@ public static TResult[] ProjectToArray(this IList items, public static T[] CopyToArray(this IList items) { + if (items.Count == 0) + { + return Enumerable.EmptyArray; + } + var clonedArray = new T[items.Count]; clonedArray.CopyFrom(items); @@ -267,9 +272,14 @@ public static T[] Append(this IList array, T extraItem) public static T[] Append(this IList array, IList extraItems) { - if (array.Count == 1) + if (extraItems.Count == 0) { - return Prepend(extraItems, array[0]); + return array.CopyToArray(); + } + + if (array.Count == 0) + { + return extraItems.CopyToArray(); } if (extraItems.Count == 1) @@ -277,6 +287,11 @@ public static T[] Append(this IList array, IList extraItems) return Append(array, extraItems[0]); } + if (array.Count == 1) + { + return Prepend(extraItems, array[0]); + } + var combinedArray = new T[array.Count + extraItems.Count]; combinedArray.CopyFrom(array); diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index 127de0cdc..02d0d96c7 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -434,75 +434,6 @@ public static bool IsRootedIn(this Expression expression, Expression possiblePar return false; } - public static bool TryGetMappingBody(this Expression value, out Expression mapping) - { - if (value.NodeType == Try) - { - value = ((TryExpression)value).Body; - } - - if ((value.NodeType != Block)) - { - mapping = null; - return false; - } - - var mappingBlock = (BlockExpression)value; - var mappingVariables = mappingBlock.Variables.ToList(); - - if (mappingBlock.Expressions[0].NodeType == Try) - { - mappingBlock = (BlockExpression)((TryExpression)mappingBlock.Expressions[0]).Body; - mappingVariables.AddRange(mappingBlock.Variables); - } - else - { - mappingBlock = (BlockExpression)value; - } - - if (mappingBlock.Expressions.HasOne()) - { - mapping = null; - return false; - } - - mapping = mappingBlock; - - var mappingExpressions = GetMappingExpressions(mapping); - - if (mappingExpressions.HasOne() && - (mappingExpressions[0].NodeType == Block)) - { - mappingBlock = (BlockExpression)mappingExpressions[0]; - mappingVariables.AddRange(mappingBlock.Variables); - mapping = mappingBlock.Update(mappingVariables, mappingBlock.Expressions); - return true; - } - - mapping = mappingVariables.Any() - ? Expression.Block(mappingVariables, mappingExpressions) - : mappingExpressions.HasOne() - ? mappingExpressions[0] - : Expression.Block(mappingExpressions); - - return true; - } - - private static IList GetMappingExpressions(Expression mapping) - { - var expressions = new List(); - - while (mapping.NodeType == Block) - { - var mappingBlock = (BlockExpression)mapping; - - expressions.AddRange(mappingBlock.Expressions.Take(mappingBlock.Expressions.Count - 1)); - mapping = mappingBlock.Result; - } - - return expressions; - } - public static bool TryGetVariableAssignment(this IList mappingExpressions, out BinaryExpression binaryExpression) { if (mappingExpressions.TryFindMatch(exp => exp.NodeType == Assign, out var assignment)) diff --git a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs index 7949dd9a2..1768dc7eb 100644 --- a/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs +++ b/AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs @@ -1,7 +1,9 @@ namespace AgileObjects.AgileMapper.Members.Dictionaries { using System; + using System.Collections.Generic; using System.Dynamic; + using System.Linq; using Caching; using Extensions; using Extensions.Internal; @@ -269,13 +271,70 @@ private bool ValueIsFlattening(Expression value, out Expression flattening) { if (HasObjectEntries || HasSimpleEntries) { - return value.TryGetMappingBody(out flattening); + return TryGetMappingBody(value, out flattening); } flattening = null; return false; } + private static bool TryGetMappingBody(Expression value, out Expression mapping) + { + if (value.NodeType == Try) + { + value = ((TryExpression)value).Body; + } + + if ((value.NodeType != Block)) + { + mapping = null; + return false; + } + + var mappingBlock = (BlockExpression)value; + + if (mappingBlock.Expressions.HasOne()) + { + mapping = null; + return false; + } + + var mappingExpressions = GetMappingExpressions(mappingBlock); + + if (mappingExpressions.HasOne() && + (mappingExpressions[0].NodeType == Block)) + { + IList mappingVariables = mappingBlock.Variables; + mappingBlock = (BlockExpression)mappingExpressions[0]; + mappingVariables = mappingVariables.Append(mappingBlock.Variables); + mapping = mappingBlock.Update(mappingVariables, mappingBlock.Expressions); + return true; + } + + mapping = mappingBlock.Variables.Any() + ? Expression.Block(mappingBlock.Variables, mappingExpressions) + : mappingExpressions.HasOne() + ? mappingExpressions[0] + : Expression.Block(mappingExpressions); + + return true; + } + + private static IList GetMappingExpressions(Expression mapping) + { + var expressions = new List(); + + while (mapping.NodeType == Block) + { + var mappingBlock = (BlockExpression)mapping; + + expressions.AddRange(mappingBlock.Expressions.Take(mappingBlock.Expressions.Count - 1)); + mapping = mappingBlock.Result; + } + + return expressions; + } + public DictionaryTargetMember WithTypeOf(Member sourceMember) { if (sourceMember.Type == Type) diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index 8d34258f7..45d22b74a 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -320,14 +320,18 @@ private static Expression UseLocalValueVariable( { var variableAssignment = variable.AssignTo(variableValue); - if (body.NodeType != ExpressionType.Try) + if (body.NodeType == ExpressionType.Block) { if (performValueReplacement) { body = body.Replace(variableValue, variable); } - return Expression.Block(new[] { variable }, variableAssignment, body); + var block = (BlockExpression)body; + + return Expression.Block( + block.Variables.Append(variable), + block.Expressions.Prepend(variableAssignment)); } var tryCatch = (TryExpression)body; From e042e8d07dc9dc687fa4d75444173df1f0fd47d8 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 8 Jun 2019 20:22:31 +0100 Subject: [PATCH 04/14] Tidying --- .../Extensions/Internal/TypeExtensions.cs | 3 +++ .../ObjectPopulation/MappingFactory.cs | 22 ++----------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/AgileMapper/Extensions/Internal/TypeExtensions.cs b/AgileMapper/Extensions/Internal/TypeExtensions.cs index 048d286ff..d16766bbe 100644 --- a/AgileMapper/Extensions/Internal/TypeExtensions.cs +++ b/AgileMapper/Extensions/Internal/TypeExtensions.cs @@ -15,6 +15,9 @@ internal static class TypeExtensions private static readonly Assembly _systemCoreLib = typeof(Func<>).GetAssembly(); #endif + public static string GetSourceValueVariableName(this Type sourceType) + => "source" + sourceType.GetVariableNameInPascalCase(); + public static string GetShortVariableName(this Type type) { var variableName = type.GetVariableNameInPascalCase(); diff --git a/AgileMapper/ObjectPopulation/MappingFactory.cs b/AgileMapper/ObjectPopulation/MappingFactory.cs index 45d22b74a..6f4a2a403 100644 --- a/AgileMapper/ObjectPopulation/MappingFactory.cs +++ b/AgileMapper/ObjectPopulation/MappingFactory.cs @@ -1,6 +1,5 @@ namespace AgileObjects.AgileMapper.ObjectPopulation { - using System; using Extensions; using Extensions.Internal; using Members; @@ -214,7 +213,7 @@ private static Expression GetDirectAccessMapping( if (useLocalSourceValueVariable) { - var sourceValueVariableName = GetSourceValueVariableName(mapperData, mappingValues.SourceValue.Type); + var sourceValueVariableName = mappingValues.SourceValue.Type.GetSourceValueVariableName(); sourceValue = Expression.Variable(mappingValues.SourceValue.Type, sourceValueVariableName); sourceValueVariableValue = mappingValues.SourceValue; } @@ -250,24 +249,7 @@ private static bool ShouldUseLocalSourceValueVariable( SourceAccessCounter.MultipleAccessesExist(sourceValue, mapping); } - private static string GetSourceValueVariableName(IMemberMapperData mapperData, Type sourceType = null) - { - var sourceValueVariableName = "source" + (sourceType ?? mapperData.SourceType).GetVariableNameInPascalCase(); - - var numericSuffix = default(string); - for (var i = mapperData.MappingDataObject.Name.Length - 1; i > 0; --i) - { - if (!char.IsDigit(mapperData.MappingDataObject.Name[i])) - { - break; - } - - numericSuffix = mapperData.MappingDataObject.Name[i] + numericSuffix; - } - - return sourceValueVariableName + numericSuffix; - } public static Expression UseLocalSourceValueVariableIfAppropriate( Expression mappingExpression, @@ -285,7 +267,7 @@ public static Expression UseLocalSourceValueVariableIfAppropriate( return mappingExpression; } - var sourceValueVariableName = GetSourceValueVariableName(mapperData); + var sourceValueVariableName = mapperData.SourceType.GetSourceValueVariableName(); var sourceValueVariable = Expression.Variable(mapperData.SourceType, sourceValueVariableName); return UseLocalValueVariable( From 4225252966956eb9e50e73d3461a93e2ac67274a Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 8 Jun 2019 20:52:51 +0100 Subject: [PATCH 05/14] Removing unused code --- .../MappingExpressionFactoryBase.cs | 30 +------------------ .../ObjectPopulation/ObjectMapperData.cs | 14 ++------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 2d1eb7d18..430382e11 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -238,23 +238,10 @@ private Expression GetMappingBlock(MappingCreationContext context) return firstExpression; } - var isSingleExpression = mappingExpressions.HasOne(); - var firstExpressionType = firstExpression.NodeType; - - if (isSingleExpression && (firstExpressionType == Constant)) - { - goto CreateFullMappingBlock; - } - Expression returnExpression; - if (firstExpressionType != Block) + if (firstExpression.NodeType != Block) { - if (UseFirstExpression(firstExpression, isSingleExpression)) - { - return GetReturnExpression(firstExpression, context); - } - if (TryAdjustForUnusedLocalVariableIfApplicable(context, out returnExpression)) { return returnExpression; @@ -265,8 +252,6 @@ private Expression GetMappingBlock(MappingCreationContext context) return returnExpression; } - CreateFullMappingBlock: - returnExpression = GetReturnExpression(GetReturnValue(context.MapperData), context); mappingExpressions.Add(context.MapperData.GetReturnLabel(returnExpression)); @@ -294,19 +279,6 @@ private static void AdjustForSingleExpressionBlockIfApplicable(MappingCreationCo } } - private static bool UseFirstExpression(Expression firstExpression, bool isSingleExpression) - { - if (firstExpression.NodeType == MemberAccess) - { - return true; - } - - return isSingleExpression && - (firstExpression.NodeType == Conditional) && - (firstExpression.Type != typeof(void)); - - } - private static bool TryAdjustForUnusedLocalVariableIfApplicable(MappingCreationContext context, out Expression returnExpression) { if (!context.MapperData.Context.UseLocalVariable) diff --git a/AgileMapper/ObjectPopulation/ObjectMapperData.cs b/AgileMapper/ObjectPopulation/ObjectMapperData.cs index a7f3611f0..80858b0b8 100644 --- a/AgileMapper/ObjectPopulation/ObjectMapperData.cs +++ b/AgileMapper/ObjectPopulation/ObjectMapperData.cs @@ -29,7 +29,6 @@ internal class ObjectMapperData : BasicMapperData, IMemberMapperData private static readonly MethodInfo _mapRepeatedElementMethod = typeof(IObjectMappingDataUntyped).GetPublicInstanceMethod("MapRepeated", parameterCount: 3); - private readonly List _childMapperDatas; private ObjectMapperData _entryPointMapperData; private Expression _targetInstance; private ParameterExpression _instanceVariable; @@ -55,7 +54,7 @@ private ObjectMapperData( { MapperContext = mappingData.MappingContext.MapperContext; DeclaredTypeMapperData = OriginalMapperData = declaredTypeMapperData; - _childMapperDatas = new List(); + ChildMapperDatas = new List(); DataSourceIndex = dataSourceIndex.GetValueOrDefault(); MappingDataObject = GetMappingDataObject(parent); @@ -99,7 +98,7 @@ private ObjectMapperData( return; } - parent._childMapperDatas.Add(this); + parent.ChildMapperDatas.Add(this); Parent = parent; if (!this.TargetMemberIsEnumerableElement()) @@ -341,13 +340,6 @@ private static bool UseExistingMapperData( // do a runtime-typed child mapping, we're able to reuse the parent MapperData // by finding it from the entry point: var parentMapperData = mappingData.Parent.MapperData; - - if (parentMapperData.ChildMapperDatas.None()) - { - existingMapperData = null; - return false; - } - var membersSource = mappingData.MapperKey.GetMembersSource(parentMapperData); if (!(membersSource is IChildMembersSource childMembersSource)) @@ -403,7 +395,7 @@ private static ObjectMapperData GetMapperDataOrNull( public ObjectMapperData OriginalMapperData { get; set; } - public IList ChildMapperDatas => _childMapperDatas; + public IList ChildMapperDatas { get; } public int DataSourceIndex { get; set; } From 3aeccf7544e626ea53a63d5d11967c1aca2d3132 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 10 Jun 2019 20:29:07 +0100 Subject: [PATCH 06/14] Test coverage for ExpressionInfoFinder index logic --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenConfiguringDataSourcesInline.cs | 51 +++++++++++++++++++ .../TestClasses/PublicIndex.cs | 20 ++++++++ .../WhenMappingToNewComplexTypeMembers.cs | 2 +- .../Internal/ExpressionExtensions.cs | 15 ++++-- AgileMapper/Members/ExpressionInfoFinder.cs | 21 +++++--- 6 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 AgileMapper.UnitTests/TestClasses/PublicIndex.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 8b18a6e0f..5987df1a2 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -220,6 +220,7 @@ + diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index 1d398662d..7c096864a 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -1,15 +1,19 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration.Inline { using System; + using System.Collections.Generic; using System.Linq.Expressions; using AgileMapper.Extensions; using AgileMapper.Members; using Common; + using NetStandardPolyfills; using TestClasses; #if !NET35 using Xunit; + using static System.Linq.Expressions.Expression; #else using Fact = NUnit.Framework.TestAttribute; + using static Microsoft.Scripting.Ast.Expression; [NUnit.Framework.TestFixture] #endif @@ -317,6 +321,53 @@ public void ShouldApplyDifferingTargetTypeInlineDataSourceMemberConfig() } } + [Fact] + public void ShouldApplyAnInlineNullCheckedArrayIndexDataSource() + { + var source = new PublicProperty[]> + { + Value = new[] + { + new PublicField
{ Value = new Address { Line1 = "1.1" } }, + new PublicField
{ Value = new Address { Line1 = "1.2" } } + } + }; + + var result = Mapper.Map(source).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); + + result.Value.ShouldNotBeNull(); + result.Value.Line1.ShouldBe("1.2"); + } + + [Fact] + public void ShouldApplyAnInlineNullCheckedIndexDataSource() + { + var source = new PublicProperty>> + { + Value = new PublicIndex> + { + [0] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, + [1] = new PublicField
{ Value = new Address { Line1 = "1.2" } } + } + }; + + var sourceParameter = Parameter(source.GetType(), "s"); + var sourceValueProperty = Property(sourceParameter, "Value"); + var sourceValueIndexer = sourceValueProperty.Type.GetPublicInstanceProperty("Item"); + var sourceValueIndex = MakeIndex(sourceValueProperty, sourceValueIndexer, new[] { Constant(1) }); + + var sourceLambda = Lambda>>, Address>>( + Field(sourceValueIndex, "Value"), + sourceParameter); + + var result = Mapper.Map(source).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + result.Value.ShouldNotBeNull(); + result.Value.Line1.ShouldBe("1.2"); + } + [Fact] public void ShouldHandleANullSourceMember() { diff --git a/AgileMapper.UnitTests/TestClasses/PublicIndex.cs b/AgileMapper.UnitTests/TestClasses/PublicIndex.cs new file mode 100644 index 000000000..0a19d41e3 --- /dev/null +++ b/AgileMapper.UnitTests/TestClasses/PublicIndex.cs @@ -0,0 +1,20 @@ +namespace AgileObjects.AgileMapper.UnitTests.TestClasses +{ + using System.Collections.Generic; + + public class PublicIndex + { + private readonly Dictionary _data; + + public PublicIndex() + { + _data = new Dictionary(); + } + + public TValue this[TIndex index] + { + get => _data[index]; + set => _data[index] = value; + } + } +} \ No newline at end of file diff --git a/AgileMapper.UnitTests/WhenMappingToNewComplexTypeMembers.cs b/AgileMapper.UnitTests/WhenMappingToNewComplexTypeMembers.cs index 3ef2b75f4..091fea6bf 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewComplexTypeMembers.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewComplexTypeMembers.cs @@ -363,7 +363,7 @@ public void ShouldHandleRuntimeTypedComplexAndEnumerableElementMembers() result.Value2.Count.ShouldBe(2); result.Value2.First().ShouldBeOfType>(); ((PublicProperty)result.Value2.First()).Value.ShouldBe("ikjhfeslkjdw"); - + result.Value2.Second().ShouldBeOfType>(); ((PublicField)result.Value2.Second()).Value.ShouldBe("ldkjkdhusdiuoji"); } diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index 02d0d96c7..a080933d5 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -198,11 +198,16 @@ public static Expression GetCount( ? nameof(Enumerable.LongCount) : nameof(Enumerable.Count); - return Expression.Call( - typeof(Enumerable) - .GetPublicStaticMethod(linqCountMethodName, parameterCount: 1) - .MakeGenericMethod(collectionAccess.Type.GetEnumerableElementType()), - collectionAccess); + var linqCountMethod = typeof(Enumerable) + .GetPublicStaticMethod(linqCountMethodName, parameterCount: 1) + .MakeGenericMethod(collectionAccess.Type.GetEnumerableElementType()); + + if (collectionAccess.Type.IsAssignableTo(linqCountMethod.GetParameters().First().ParameterType)) + { + return Expression.Call(linqCountMethod, collectionAccess); + } + + return null; } public static Expression GetValueOrDefaultCall(this Expression nullableExpression) diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 753acc74f..8eff432af 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -105,23 +105,28 @@ private Expression GetNestedAccessChecks() private static Expression GetAccessCheck(Expression access) { - Expression count; - switch (access.NodeType) { case ArrayIndex: - count = Expression.ArrayLength(((BinaryExpression)access).Left); - break; + var arrayIndexAccess = (BinaryExpression)access; + var arrayLength = Expression.ArrayLength(arrayIndexAccess.Left); + var indexValue = arrayIndexAccess.Right; + + return Expression.GreaterThanOrEqual(arrayLength, indexValue); case Index: - count = ((IndexExpression)access).Object.GetCount(); - break; + var count = ((IndexExpression)access).Object.GetCount(); + + if (count == null) + { + goto default; + } + + return Expression.GreaterThan(count, ToNumericConverter.Zero); default: return access.GetIsNotDefaultComparison(); } - - return Expression.GreaterThan(count, ToNumericConverter.Zero); } protected override Expression VisitBinary(BinaryExpression binary) From fca241517d2322cb29189847ad2ebc056fa5dee8 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Mon, 10 Jun 2019 20:54:46 +0100 Subject: [PATCH 07/14] Extending array index data source code coverage --- .../WhenConfiguringDataSourcesInline.cs | 64 ++++++++++++++++++- .../DataSources/SourceMemberDataSource.cs | 1 - .../Internal/ExpressionExtensions.cs | 22 ++++++- AgileMapper/Members/ExpressionInfoFinder.cs | 12 +++- AgileMapper/Members/MemberExtensions.cs | 15 +++-- .../ComplexTypeToNullComparisonConverter.cs | 1 - 6 files changed, 101 insertions(+), 14 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index 7c096864a..12ae2baea 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -338,10 +338,44 @@ public void ShouldApplyAnInlineNullCheckedArrayIndexDataSource() result.Value.ShouldNotBeNull(); result.Value.Line1.ShouldBe("1.2"); + + var nullArraySource = new PublicProperty[]> { Value = null }; + + var nullArrayResult = Mapper.Map(nullArraySource).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); + + nullArrayResult.Value.ShouldBeNull(); + + var tooSmallArraySource = new PublicProperty[]> + { + Value = new[] + { + new PublicField
{ Value = new Address { Line1 = "1.1" } } + } + }; + + var tooSmallArrayResult = Mapper.Map(tooSmallArraySource).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); + + tooSmallArrayResult.Value.ShouldBeNull(); + + var nullArrayObjectSource = new PublicProperty[]> + { + Value = new[] + { + new PublicField
{ Value = new Address { Line1 = "1.1" } }, + new PublicField
{ Value = null } + } + }; + + var nullArrayObjectResult = Mapper.Map(nullArrayObjectSource).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); + + nullArrayObjectResult.Value.ShouldBeNull(); } [Fact] - public void ShouldApplyAnInlineNullCheckedIndexDataSource() + public void ShouldApplyAnInlineNullCheckedIntKeyedIndexDataSource() { var source = new PublicProperty>> { @@ -368,6 +402,34 @@ public void ShouldApplyAnInlineNullCheckedIndexDataSource() result.Value.Line1.ShouldBe("1.2"); } + [Fact] + public void ShouldApplyAnInlineNullCheckedStringKeyedIndexDataSource() + { + var source = new PublicProperty>> + { + Value = new PublicIndex> + { + ["A"] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, + ["B"] = new PublicField
{ Value = new Address { Line1 = "1.2" } } + } + }; + + var sourceParameter = Parameter(source.GetType(), "s"); + var sourceValueProperty = Property(sourceParameter, "Value"); + var sourceValueIndexer = sourceValueProperty.Type.GetPublicInstanceProperty("Item"); + var sourceValueIndex = MakeIndex(sourceValueProperty, sourceValueIndexer, new[] { Constant("B") }); + + var sourceLambda = Lambda>>, Address>>( + Field(sourceValueIndex, "Value"), + sourceParameter); + + var result = Mapper.Map(source).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + result.Value.ShouldNotBeNull(); + result.Value.Line1.ShouldBe("1.2"); + } + [Fact] public void ShouldHandleANullSourceMember() { diff --git a/AgileMapper/DataSources/SourceMemberDataSource.cs b/AgileMapper/DataSources/SourceMemberDataSource.cs index 29643f238..f557b8d7e 100644 --- a/AgileMapper/DataSources/SourceMemberDataSource.cs +++ b/AgileMapper/DataSources/SourceMemberDataSource.cs @@ -4,7 +4,6 @@ using Extensions.Internal; using Members; using ObjectPopulation; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index a080933d5..18f3ba47c 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -424,7 +424,7 @@ private static Expression GetCopyListCollectionCreation( public static bool IsRootedIn(this Expression expression, Expression possibleParent) { - var parent = expression.GetParentOrNull(); + var parent = GetParentOrNull(expression); while (parent != null) { @@ -433,12 +433,30 @@ public static bool IsRootedIn(this Expression expression, Expression possiblePar return true; } - parent = parent.GetParentOrNull(); + parent = GetParentOrNull(parent); } return false; } + public static Expression GetParentOrNull(this Expression expression) + { + // TODO: Update ReadableExpressions + var parent = PublicExpressionExtensions.GetParentOrNull(expression); + + if (parent != null) + { + return parent; + } + + if (expression.NodeType == ArrayIndex) + { + return ((BinaryExpression)expression).Left; + } + + return null; + } + public static bool TryGetVariableAssignment(this IList mappingExpressions, out BinaryExpression binaryExpression) { if (mappingExpressions.TryFindMatch(exp => exp.NodeType == Assign, out var assignment)) diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 8eff432af..fb23d1996 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -112,10 +112,18 @@ private static Expression GetAccessCheck(Expression access) var arrayLength = Expression.ArrayLength(arrayIndexAccess.Left); var indexValue = arrayIndexAccess.Right; - return Expression.GreaterThanOrEqual(arrayLength, indexValue); + return Expression.GreaterThan(arrayLength, indexValue); case Index: - var count = ((IndexExpression)access).Object.GetCount(); + var index = (IndexExpression)access; + var indexKeyType = index.Indexer.GetGetter().GetParameters().First().ParameterType; + + if (!indexKeyType.IsWholeNumberNumeric()) + { + goto default; + } + + var count = index.Object.GetCount(); if (count == null) { diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 11ca1d2f7..8d331cb6e 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -5,6 +5,12 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; +#if NET35 + using Microsoft.Scripting.Ast; + using LinqExp = System.Linq.Expressions; +#else + using System.Linq.Expressions; +#endif using System.Reflection; using Configuration; using Extensions; @@ -13,12 +19,7 @@ using ObjectPopulation; using ReadableExpressions; using ReadableExpressions.Extensions; -#if NET35 - using Microsoft.Scripting.Ast; - using LinqExp = System.Linq.Expressions; -#else - using System.Linq.Expressions; -#endif + using ExpressionExtensions = Extensions.Internal.ExpressionExtensions; using static System.StringComparison; using static Constants; using static Member; @@ -410,7 +411,7 @@ public static IList GetMemberAccessChain( } memberAccesses.Insert(0, memberExpression); - rootExpression = memberExpression.GetParentOrNull(); + rootExpression = ExpressionExtensions.GetParentOrNull(memberExpression); if (rootExpression == null) { diff --git a/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs b/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs index 4c8ce4ba6..dfe0ccac7 100644 --- a/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs +++ b/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs @@ -5,7 +5,6 @@ using Extensions; using Extensions.Internal; using Members; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else From 92b793ffe2bf9e7d46b0e408ddc95ad1b877f01c Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 12 Jun 2019 08:31:35 +0100 Subject: [PATCH 08/14] Extending test coverage for index expression data sources --- .../WhenConfiguringDataSourcesInline.cs | 220 ++++++++++++------ .../TestClasses/PublicIndex.cs | 2 +- .../Internal/ExpressionExtensions.cs | 13 +- AgileMapper/Members/ExpressionInfoFinder.cs | 5 +- 4 files changed, 160 insertions(+), 80 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index 12ae2baea..ee5a016fe 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -1,7 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration.Inline { using System; - using System.Collections.Generic; using System.Linq.Expressions; using AgileMapper.Extensions; using AgileMapper.Members; @@ -324,110 +323,187 @@ public void ShouldApplyDifferingTargetTypeInlineDataSourceMemberConfig() [Fact] public void ShouldApplyAnInlineNullCheckedArrayIndexDataSource() { - var source = new PublicProperty[]> + using (var mapper = Mapper.CreateNew()) { - Value = new[] + var source = new PublicProperty[]> { - new PublicField
{ Value = new Address { Line1 = "1.1" } }, - new PublicField
{ Value = new Address { Line1 = "1.2" } } - } - }; + Value = new[] + { + new PublicField
{ Value = new Address { Line1 = "1.1" } }, + new PublicField
{ Value = new Address { Line1 = "1.2" } } + } + }; - var result = Mapper.Map(source).ToANew>(cfg => cfg - .Map(s => s.Value[1].Value, t => t.Value)); + var result = mapper.Map(source).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); - result.Value.ShouldNotBeNull(); - result.Value.Line1.ShouldBe("1.2"); + result.Value.ShouldNotBeNull(); + result.Value.Line1.ShouldBe("1.2"); - var nullArraySource = new PublicProperty[]> { Value = null }; - - var nullArrayResult = Mapper.Map(nullArraySource).ToANew>(cfg => cfg - .Map(s => s.Value[1].Value, t => t.Value)); + var nullArraySource = new PublicProperty[]> { Value = null }; - nullArrayResult.Value.ShouldBeNull(); + var nullArrayResult = mapper.Map(nullArraySource).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); - var tooSmallArraySource = new PublicProperty[]> - { - Value = new[] + nullArrayResult.Value.ShouldBeNull(); + + var tooSmallArraySource = new PublicProperty[]> { - new PublicField
{ Value = new Address { Line1 = "1.1" } } - } - }; - - var tooSmallArrayResult = Mapper.Map(tooSmallArraySource).ToANew>(cfg => cfg - .Map(s => s.Value[1].Value, t => t.Value)); + Value = new[] + { + new PublicField
{ Value = new Address { Line1 = "1.1" } } + } + }; - tooSmallArrayResult.Value.ShouldBeNull(); + var tooSmallArrayResult = mapper.Map(tooSmallArraySource).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); - var nullArrayObjectSource = new PublicProperty[]> - { - Value = new[] + tooSmallArrayResult.Value.ShouldBeNull(); + + var nullArrayObjectSource = new PublicProperty[]> { - new PublicField
{ Value = new Address { Line1 = "1.1" } }, - new PublicField
{ Value = null } - } - }; - - var nullArrayObjectResult = Mapper.Map(nullArrayObjectSource).ToANew>(cfg => cfg - .Map(s => s.Value[1].Value, t => t.Value)); - - nullArrayObjectResult.Value.ShouldBeNull(); + Value = new[] + { + new PublicField
{ Value = new Address { Line1 = "1.1" } }, + new PublicField
{ Value = null } + } + }; + + var nullArrayObjectResult = mapper.Map(nullArrayObjectSource).ToANew>(cfg => cfg + .Map(s => s.Value[1].Value, t => t.Value)); + + nullArrayObjectResult.Value.ShouldBeNull(); + } } [Fact] public void ShouldApplyAnInlineNullCheckedIntKeyedIndexDataSource() { - var source = new PublicProperty>> + using (var mapper = Mapper.CreateNew()) { - Value = new PublicIndex> + var source = new PublicProperty>> { - [0] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, - [1] = new PublicField
{ Value = new Address { Line1 = "1.2" } } - } - }; + Value = new PublicIndex> + { + [0] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, + [1] = new PublicField
{ Value = new Address { Line1 = "1.2" } } + } + }; + + var sourceParameter = Parameter(source.GetType(), "s"); + var sourceValueProperty = Property(sourceParameter, "Value"); + var sourceValueIndexer = sourceValueProperty.Type.GetPublicInstanceProperty("Item"); + var sourceValueIndex = MakeIndex(sourceValueProperty, sourceValueIndexer, new[] { Constant(1) }); + + var sourceLambda = Lambda>>, Address>>( + Field(sourceValueIndex, "Value"), + sourceParameter); + + var result = Mapper.Map(source).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + result.Value.ShouldNotBeNull(); + result.Value.Line1.ShouldBe("1.2"); - var sourceParameter = Parameter(source.GetType(), "s"); - var sourceValueProperty = Property(sourceParameter, "Value"); - var sourceValueIndexer = sourceValueProperty.Type.GetPublicInstanceProperty("Item"); - var sourceValueIndex = MakeIndex(sourceValueProperty, sourceValueIndexer, new[] { Constant(1) }); + var nullIndexerSource = new PublicProperty>> { Value = null }; + + var nullIndexerResult = mapper.Map(nullIndexerSource).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + nullIndexerResult.Value.ShouldBeNull(); + + var noEntrySource = new PublicProperty>> + { + Value = new PublicIndex> + { + [0] = new PublicField
{ Value = new Address { Line1 = "1.1" } } + } + }; - var sourceLambda = Lambda>>, Address>>( - Field(sourceValueIndex, "Value"), - sourceParameter); + var noEntryResult = mapper.Map(noEntrySource).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); - var result = Mapper.Map(source).ToANew>(cfg => cfg - .Map(sourceLambda, t => t.Value)); + noEntryResult.Value.ShouldBeNull(); - result.Value.ShouldNotBeNull(); - result.Value.Line1.ShouldBe("1.2"); + var nullIndexedObjectSource = new PublicProperty>> + { + Value = new PublicIndex> + { + [0] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, + [1] = new PublicField
{ Value = null } + } + }; + + var nullIndexedObjectResult = mapper.Map(nullIndexedObjectSource).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + nullIndexedObjectResult.Value.ShouldBeNull(); + } } [Fact] public void ShouldApplyAnInlineNullCheckedStringKeyedIndexDataSource() { - var source = new PublicProperty>> + using (var mapper = Mapper.CreateNew()) { - Value = new PublicIndex> + var source = new PublicProperty>> { - ["A"] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, - ["B"] = new PublicField
{ Value = new Address { Line1 = "1.2" } } - } - }; + Value = new PublicIndex> + { + ["A"] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, + ["B"] = new PublicField
{ Value = new Address { Line1 = "1.2" } } + } + }; + + var sourceParameter = Parameter(source.GetType(), "s"); + var sourceValueProperty = Property(sourceParameter, "Value"); + var sourceValueIndexer = sourceValueProperty.Type.GetPublicInstanceProperty("Item"); + var sourceValueIndex = MakeIndex(sourceValueProperty, sourceValueIndexer, new[] { Constant("B") }); + + var sourceLambda = Lambda>>, Address>>( + Field(sourceValueIndex, "Value"), + sourceParameter); + + var result = mapper.Map(source).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + result.Value.ShouldNotBeNull(); + result.Value.Line1.ShouldBe("1.2"); - var sourceParameter = Parameter(source.GetType(), "s"); - var sourceValueProperty = Property(sourceParameter, "Value"); - var sourceValueIndexer = sourceValueProperty.Type.GetPublicInstanceProperty("Item"); - var sourceValueIndex = MakeIndex(sourceValueProperty, sourceValueIndexer, new[] { Constant("B") }); + var nullIndexerSource = new PublicProperty>> { Value = null }; - var sourceLambda = Lambda>>, Address>>( - Field(sourceValueIndex, "Value"), - sourceParameter); + var nullIndexerResult = mapper.Map(nullIndexerSource).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); - var result = Mapper.Map(source).ToANew>(cfg => cfg - .Map(sourceLambda, t => t.Value)); + nullIndexerResult.Value.ShouldBeNull(); - result.Value.ShouldNotBeNull(); - result.Value.Line1.ShouldBe("1.2"); + var noEntrySource = new PublicProperty>> + { + Value = new PublicIndex> + { + ["A"] = new PublicField
{ Value = new Address { Line1 = "1.1" } } + } + }; + + var noEntryResult = mapper.Map(noEntrySource).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + noEntryResult.Value.ShouldBeNull(); + + var nullIndexedObjectSource = new PublicProperty>> + { + Value = new PublicIndex> + { + ["A"] = new PublicField
{ Value = new Address { Line1 = "1.1" } }, + ["B"] = new PublicField
{ Value = null } + } + }; + + var nullIndexedObjectResult = mapper.Map(nullIndexedObjectSource).ToANew>(cfg => cfg + .Map(sourceLambda, t => t.Value)); + + nullIndexedObjectResult.Value.ShouldBeNull(); + } } [Fact] diff --git a/AgileMapper.UnitTests/TestClasses/PublicIndex.cs b/AgileMapper.UnitTests/TestClasses/PublicIndex.cs index 0a19d41e3..da4ccfded 100644 --- a/AgileMapper.UnitTests/TestClasses/PublicIndex.cs +++ b/AgileMapper.UnitTests/TestClasses/PublicIndex.cs @@ -13,7 +13,7 @@ public PublicIndex() public TValue this[TIndex index] { - get => _data[index]; + get => _data.TryGetValue(index, out var value) ? value : default(TValue); set => _data[index] = value; } } diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index 18f3ba47c..4727e6341 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -449,12 +449,17 @@ public static Expression GetParentOrNull(this Expression expression) return parent; } - if (expression.NodeType == ArrayIndex) + switch (expression.NodeType) { - return ((BinaryExpression)expression).Left; - } + case ArrayIndex: + return ((BinaryExpression)expression).Left; - return null; + case Index: + return ((IndexExpression)expression).Object; + + default: + return null; + } } public static bool TryGetVariableAssignment(this IList mappingExpressions, out BinaryExpression binaryExpression) diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index fb23d1996..2f1d74347 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -7,7 +7,6 @@ namespace AgileObjects.AgileMapper.Members using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; - using TypeConversion; #if NET35 using Microsoft.Scripting.Ast; using static Microsoft.Scripting.Ast.ExpressionType; @@ -118,7 +117,7 @@ private static Expression GetAccessCheck(Expression access) var index = (IndexExpression)access; var indexKeyType = index.Indexer.GetGetter().GetParameters().First().ParameterType; - if (!indexKeyType.IsWholeNumberNumeric()) + if (!indexKeyType.IsNumeric()) { goto default; } @@ -130,7 +129,7 @@ private static Expression GetAccessCheck(Expression access) goto default; } - return Expression.GreaterThan(count, ToNumericConverter.Zero); + return Expression.GreaterThan(count, index.Arguments.First().GetConversionTo(count.Type)); default: return access.GetIsNotDefaultComparison(); From ae387bec92d8423552cb260b22f223f48b08cbdd Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Wed, 12 Jun 2019 14:07:41 +0100 Subject: [PATCH 09/14] Using instance mapper --- .../Configuration/Inline/WhenConfiguringDataSourcesInline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index ee5a016fe..4c78b63b9 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -399,7 +399,7 @@ public void ShouldApplyAnInlineNullCheckedIntKeyedIndexDataSource() Field(sourceValueIndex, "Value"), sourceParameter); - var result = Mapper.Map(source).ToANew>(cfg => cfg + var result = mapper.Map(source).ToANew>(cfg => cfg .Map(sourceLambda, t => t.Value)); result.Value.ShouldNotBeNull(); From 0812eddb54e85f5390323aee857f53d6d6d9bc6c Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Thu, 13 Jun 2019 19:12:41 +0100 Subject: [PATCH 10/14] Updating to v2.3.1 of ReadableExpressions Removing Index tests from .NET 3.5 tests --- ...AgileMapper.PerformanceTester.Net45.csproj | 4 +-- .../packages.config | 2 +- .../AgileMapper.UnitTests.Net35.csproj | 4 +-- AgileMapper.UnitTests.Net35/packages.config | 2 +- .../AgileMapper.UnitTests.NonParallel.csproj | 4 +-- .../packages.config | 2 +- .../AgileMapper.UnitTests.csproj | 4 +-- .../WhenConfiguringDataSourcesInline.cs | 9 ++++--- AgileMapper.UnitTests/packages.config | 2 +- AgileMapper/AgileMapper.csproj | 2 +- .../DataSources/SourceMemberDataSource.cs | 7 ++--- .../Internal/ExpressionExtensions.cs | 27 ++----------------- AgileMapper/Members/MemberExtensions.cs | 3 +-- .../ComplexTypeToNullComparisonConverter.cs | 7 ++--- 14 files changed, 29 insertions(+), 50 deletions(-) diff --git a/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj b/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj index 6d774bf3e..32e86bc86 100644 --- a/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj +++ b/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj @@ -37,8 +37,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net40\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.1.1\lib\net40\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net40\AgileObjects.ReadableExpressions.dll ..\packages\AutoMapper.7.0.1\lib\net45\AutoMapper.dll diff --git a/AgileMapper.PerformanceTester.Net45/packages.config b/AgileMapper.PerformanceTester.Net45/packages.config index fcd3dee54..4352c4c8d 100644 --- a/AgileMapper.PerformanceTester.Net45/packages.config +++ b/AgileMapper.PerformanceTester.Net45/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj index 7f64f66af..70e14fb2b 100644 --- a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj +++ b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj @@ -37,8 +37,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net35\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.1.1\lib\net35\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net35\AgileObjects.ReadableExpressions.dll ..\packages\DynamicLanguageRuntime.1.1.2\lib\Net35\Microsoft.Dynamic.dll diff --git a/AgileMapper.UnitTests.Net35/packages.config b/AgileMapper.UnitTests.Net35/packages.config index 737e3b5da..b15618b29 100644 --- a/AgileMapper.UnitTests.Net35/packages.config +++ b/AgileMapper.UnitTests.Net35/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj b/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj index ab86a47df..6ad7ae3be 100644 --- a/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj +++ b/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj @@ -42,8 +42,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net40\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.1.1\lib\net40\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net40\AgileObjects.ReadableExpressions.dll diff --git a/AgileMapper.UnitTests.NonParallel/packages.config b/AgileMapper.UnitTests.NonParallel/packages.config index 4f52878b7..2a2122151 100644 --- a/AgileMapper.UnitTests.NonParallel/packages.config +++ b/AgileMapper.UnitTests.NonParallel/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 5987df1a2..d3b3922f3 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -44,8 +44,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net40\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.1.1\lib\net40\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net40\AgileObjects.ReadableExpressions.dll ..\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs index 4c78b63b9..250f4af91 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringDataSourcesInline.cs @@ -5,14 +5,13 @@ using AgileMapper.Extensions; using AgileMapper.Members; using Common; - using NetStandardPolyfills; using TestClasses; #if !NET35 + using NetStandardPolyfills; using Xunit; using static System.Linq.Expressions.Expression; #else using Fact = NUnit.Framework.TestAttribute; - using static Microsoft.Scripting.Ast.Expression; [NUnit.Framework.TestFixture] #endif @@ -375,7 +374,9 @@ public void ShouldApplyAnInlineNullCheckedArrayIndexDataSource() nullArrayObjectResult.Value.ShouldBeNull(); } } - + +#if !NET35 + // System.Linq.Expressions.Expression.MakeIndex() is missing in .NET 3.5, so not much danger of this configuration: [Fact] public void ShouldApplyAnInlineNullCheckedIntKeyedIndexDataSource() { @@ -505,7 +506,7 @@ public void ShouldApplyAnInlineNullCheckedStringKeyedIndexDataSource() nullIndexedObjectResult.Value.ShouldBeNull(); } } - +#endif [Fact] public void ShouldHandleANullSourceMember() { diff --git a/AgileMapper.UnitTests/packages.config b/AgileMapper.UnitTests/packages.config index d7c2d5300..09b6deb71 100644 --- a/AgileMapper.UnitTests/packages.config +++ b/AgileMapper.UnitTests/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper/AgileMapper.csproj b/AgileMapper/AgileMapper.csproj index 88f55b340..290d2ed28 100644 --- a/AgileMapper/AgileMapper.csproj +++ b/AgileMapper/AgileMapper.csproj @@ -25,7 +25,7 @@ - + diff --git a/AgileMapper/DataSources/SourceMemberDataSource.cs b/AgileMapper/DataSources/SourceMemberDataSource.cs index f557b8d7e..823e734ad 100644 --- a/AgileMapper/DataSources/SourceMemberDataSource.cs +++ b/AgileMapper/DataSources/SourceMemberDataSource.cs @@ -1,14 +1,15 @@ namespace AgileObjects.AgileMapper.DataSources { using System.Collections.Generic; - using Extensions.Internal; - using Members; - using ObjectPopulation; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; + using ObjectPopulation; + using ReadableExpressions.Extensions; internal class SourceMemberDataSource : DataSourceBase { diff --git a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs index 4727e6341..a080933d5 100644 --- a/AgileMapper/Extensions/Internal/ExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/ExpressionExtensions.cs @@ -424,7 +424,7 @@ private static Expression GetCopyListCollectionCreation( public static bool IsRootedIn(this Expression expression, Expression possibleParent) { - var parent = GetParentOrNull(expression); + var parent = expression.GetParentOrNull(); while (parent != null) { @@ -433,35 +433,12 @@ public static bool IsRootedIn(this Expression expression, Expression possiblePar return true; } - parent = GetParentOrNull(parent); + parent = parent.GetParentOrNull(); } return false; } - public static Expression GetParentOrNull(this Expression expression) - { - // TODO: Update ReadableExpressions - var parent = PublicExpressionExtensions.GetParentOrNull(expression); - - if (parent != null) - { - return parent; - } - - switch (expression.NodeType) - { - case ArrayIndex: - return ((BinaryExpression)expression).Left; - - case Index: - return ((IndexExpression)expression).Object; - - default: - return null; - } - } - public static bool TryGetVariableAssignment(this IList mappingExpressions, out BinaryExpression binaryExpression) { if (mappingExpressions.TryFindMatch(exp => exp.NodeType == Assign, out var assignment)) diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 8d331cb6e..221287ff6 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -19,7 +19,6 @@ using ObjectPopulation; using ReadableExpressions; using ReadableExpressions.Extensions; - using ExpressionExtensions = Extensions.Internal.ExpressionExtensions; using static System.StringComparison; using static Constants; using static Member; @@ -411,7 +410,7 @@ public static IList GetMemberAccessChain( } memberAccesses.Insert(0, memberExpression); - rootExpression = ExpressionExtensions.GetParentOrNull(memberExpression); + rootExpression = memberExpression.GetParentOrNull(); if (rootExpression == null) { diff --git a/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs b/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs index dfe0ccac7..c2991bc57 100644 --- a/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs +++ b/AgileMapper/Queryables/Converters/ComplexTypeToNullComparisonConverter.cs @@ -2,14 +2,15 @@ { using System.Collections.Generic; using System.Linq; - using Extensions; - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions; + using Extensions.Internal; + using Members; + using ReadableExpressions.Extensions; internal static class ComplexTypeToNullComparisonConverter { From 29993c23db93a927343f90099b1b7fe1844ca786 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 15 Jun 2019 09:03:09 +0100 Subject: [PATCH 11/14] Adding coverage TODOs --- AgileMapper/Configuration/DataSourceReversalSetting.cs | 1 + AgileMapper/Configuration/MapperConfiguration.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/AgileMapper/Configuration/DataSourceReversalSetting.cs b/AgileMapper/Configuration/DataSourceReversalSetting.cs index 2a8848c01..a1d7e8bc2 100644 --- a/AgileMapper/Configuration/DataSourceReversalSetting.cs +++ b/AgileMapper/Configuration/DataSourceReversalSetting.cs @@ -53,6 +53,7 @@ public string GetConflictMessage(DataSourceReversalSetting conflicting) return GetRedundantSettingConflictMessage(conflicting, " by default"); } + // TODO: Test coverage?! var targetType = ConfigInfo.TargetType.GetFriendlyName(); if (ConfigInfo.IsForAllSourceTypes()) diff --git a/AgileMapper/Configuration/MapperConfiguration.cs b/AgileMapper/Configuration/MapperConfiguration.cs index be06ee64c..2d46b2469 100644 --- a/AgileMapper/Configuration/MapperConfiguration.cs +++ b/AgileMapper/Configuration/MapperConfiguration.cs @@ -66,6 +66,7 @@ protected TServiceProvider GetServiceProvider() /// A cloned copy of the mapper being configured. protected IMapper CreateNewMapper() => _mapper.CloneSelf(); + // TODO: Test coverage /// /// Create and compile a mapping function for a particular type of mapping of the source type specified by /// the given . Use this overload for anonymous types. From 1263752709478da298a2913432990e62aa70792e Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 15 Jun 2019 13:36:35 +0100 Subject: [PATCH 12/14] Increasing data source reversal test coverage / Lazy-loading ExpressionInfoFinder collection members --- ...onfiguringReverseDataSourcesIncorrectly.cs | 94 ++++++++++++------- AgileMapper/Members/ExpressionInfoFinder.cs | 40 ++++---- 2 files changed, 80 insertions(+), 54 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs index b45c36b90..02e7a00e2 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs @@ -14,7 +14,7 @@ public class WhenConfiguringReverseDataSourcesIncorrectly { [Fact] - public void ShouldErrorIfRedundantMappingScopeOptInConfigured() + public void ShouldErrorIfGlobalScopeRedundantMappingScopeOptInConfigured() { var configEx = Should.Throw(() => { @@ -23,8 +23,7 @@ public void ShouldErrorIfRedundantMappingScopeOptInConfigured() mapper.WhenMapping .AutoReverseConfiguredDataSources() .AndWhenMapping - .From() - .To>() + .From().To>() .AutoReverseConfiguredDataSources(); } }); @@ -34,7 +33,7 @@ public void ShouldErrorIfRedundantMappingScopeOptInConfigured() } [Fact] - public void ShouldErrorIfRedundantMemberScopeOptInConfigured() + public void ShouldErrorIfGlobalScopeRedundantMemberScopeOptInConfigured() { var configEx = Should.Throw(() => { @@ -43,10 +42,29 @@ public void ShouldErrorIfRedundantMemberScopeOptInConfigured() mapper.WhenMapping .AutoReverseConfiguredDataSources() .AndWhenMapping + .From().To>() + .Map(ctx => ctx.Source.Id).To(pp => pp.Value) + .AndViceVersa(); + } + }); + + configEx.Message.ShouldContain("reversed"); + configEx.Message.ShouldContain("enabled by default"); + } + + [Fact] + public void ShouldErrorIfMappingScopeRedundantMemberScopeOptInConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping .From() .To>() - .Map(ctx => ctx.Source.Id) - .To(pp => pp.Value) + .AutoReverseConfiguredDataSources() + .And + .Map(ctx => ctx.Source.Id).To(pp => pp.Value) .AndViceVersa(); } }); @@ -56,19 +74,32 @@ public void ShouldErrorIfRedundantMemberScopeOptInConfigured() } [Fact] - public void ShouldErrorIfRedundantMappingScopeOptOutConfigured() + public void ShouldErrorIfGlobalScopeRedundantMappingScopeOptOutConfigured() { var configEx = Should.Throw(() => { using (var mapper = Mapper.CreateNew()) { - mapper.WhenMapping.AutoReverseConfiguredDataSources(); + mapper.WhenMapping + .From().To>() + .DoNotAutoReverseConfiguredDataSources(); + } + }); + configEx.Message.ShouldContain("data source reversal"); + configEx.Message.ShouldContain("disabled by default"); + } + + [Fact] + public void ShouldErrorIfGlobalScopeRedundantMemberScopeOptOutConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { mapper.WhenMapping - .From().To>() - .DoNotAutoReverseConfiguredDataSources() - .And - .Map(c => c.Id, pp => pp.Value) + .From().To>() + .Map(ctx => ctx.Source.Id).To(pp => pp.Value) .ButNotViceVersa(); } }); @@ -78,17 +109,19 @@ public void ShouldErrorIfRedundantMappingScopeOptOutConfigured() } [Fact] - public void ShouldErrorIfRedundantMemberScopeOptOutConfigured() + public void ShouldErrorIfMappingScopeRedundantMemberScopeOptOutConfigured() { var configEx = Should.Throw(() => { using (var mapper = Mapper.CreateNew()) { + mapper.WhenMapping.AutoReverseConfiguredDataSources(); + mapper.WhenMapping - .From() - .To>() - .Map(ctx => ctx.Source.Id) - .To(pp => pp.Value) + .From().To>() + .DoNotAutoReverseConfiguredDataSources() + .And + .Map(c => c.Id, pp => pp.Value) .ButNotViceVersa(); } }); @@ -107,13 +140,11 @@ public void ShouldErrorIfRedundantReverseDataSourceConfigured() mapper.WhenMapping.AutoReverseConfiguredDataSources(); mapper.WhenMapping - .From() - .To>() + .From().To>() .Map(p => p.Id, pp => pp.Value); mapper.WhenMapping - .From>() - .To() + .From>().To() .Map(pp => pp.Value, p => p.Id); } }); @@ -129,14 +160,12 @@ public void ShouldErrorIfRedundantExplicitReverseDataSourceConfigured() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To>() + .From().To>() .Map(p => p.Id, ptf => ptf.Value1) .AndViceVersa(); mapper.WhenMapping - .From>() - .To() + .From>().To() .Map(pp => pp.Value2, p => p.Id); } }); @@ -152,8 +181,7 @@ public void ShouldErrorOnMemberScopeOptInOfConfiguredConstantDataSource() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To>() + .From().To>() .Map("HELLO!").To(pf => pf.Value) .AndViceVersa(); } @@ -172,8 +200,7 @@ public void ShouldErrorOnMemberScopeOptInOfConfiguredConstantFuncDataSource() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To>() + .From().To>() .Map(p => "HELLO?!", pf => pf.Value) .AndViceVersa(); } @@ -192,8 +219,7 @@ public void ShouldErrorOnMemberScopeOptInOfConfiguredReadOnlySourceMemberDataSou using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From>() - .To() + .From>().To() .Map((prof, cvm) => prof.Value).To(cvm => cvm.AddressLine1) .AndViceVersa(); } @@ -213,8 +239,7 @@ public void ShouldErrorOnMemberScopeOptInOfConfiguredSourceMemberDataSourceForWr using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From>() - .To>() + .From>().To>() .Map((pp, pwop) => pp.Value1).To(pwop => pwop.Value) .AndViceVersa(); } @@ -234,8 +259,7 @@ public void ShouldErrorOnMemberScopeOptInOfConditionalConfiguredDataSource() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To>() + .From().To>() .If((p, pf) => p.Name.Contains("Rich")) .Map(p => p.Name, pf => pf.Value) .AndViceVersa(); diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 9dbe6b9fb..796a96b0b 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -45,9 +45,9 @@ private class ExpressionInfoFinderInstance : ExpressionVisitor { private readonly Expression _mappingDataObject; private readonly bool _includeTargetNullChecking; - private readonly ICollection _stringMemberAccessSubjects; - private readonly ICollection _nullCheckSubjects; - private readonly Dictionary _nestedAccessesByPath; + private ICollection _stringMemberAccessSubjects; + private ICollection _nullCheckSubjects; + private Dictionary _nestedAccessesByPath; private ICollection _allInvocations; private ICollection _multiInvocations; @@ -55,11 +55,17 @@ public ExpressionInfoFinderInstance(Expression mappingDataObject, bool targetCan { _mappingDataObject = mappingDataObject; _includeTargetNullChecking = targetCanBeNull; - _stringMemberAccessSubjects = new List(); - _nullCheckSubjects = new List(); - _nestedAccessesByPath = new Dictionary(); } + private ICollection StringMemberAccessSubjects + => _stringMemberAccessSubjects ?? (_stringMemberAccessSubjects = new List()); + + private ICollection NullCheckSubjects + => _nullCheckSubjects ?? (_nullCheckSubjects = new List()); + + private Dictionary NestedAccessesByPath + => _nestedAccessesByPath ?? (_nestedAccessesByPath = new Dictionary()); + private ICollection AllInvocations => _allInvocations ?? (_allInvocations = new List()); @@ -70,7 +76,7 @@ public ExpressionInfo FindIn(Expression expression) { Visit(expression); - if (_nestedAccessesByPath.None() && _multiInvocations.NoneOrNull()) + if ((_nestedAccessesByPath == null) && (_multiInvocations == null)) { return EmptyExpressionInfo; } @@ -86,12 +92,7 @@ public ExpressionInfo FindIn(Expression expression) private Expression GetNestedAccessChecks() { - if (_nestedAccessesByPath.None()) - { - return null; - } - - return _nestedAccessesByPath + return _nestedAccessesByPath? .Values .Reverse() .Project(GetAccessCheck) @@ -293,14 +294,14 @@ private static bool IsNullableGetValueOrDefaultCall(MethodCallExpression methodC private void AddExistingNullCheck(Expression checkedAccess) { - _nullCheckSubjects.Add(checkedAccess.ToString()); + NullCheckSubjects.Add(checkedAccess.ToString()); } private void AddStringMemberAccessSubjectIfAppropriate(Expression member) { if ((member?.Type == typeof(string)) && AccessSubjectCouldBeNull(member)) { - _stringMemberAccessSubjects.Add(member); + StringMemberAccessSubjects.Add(member); } } @@ -373,7 +374,8 @@ private bool GuardMemberAccess(Expression memberAccess) return false; } - if ((memberAccess.Type == typeof(string)) && !_stringMemberAccessSubjects.Contains(memberAccess)) + if ((memberAccess.Type == typeof(string)) && + (_stringMemberAccessSubjects?.Contains(memberAccess) != true)) { return false; } @@ -418,13 +420,13 @@ private void AddMemberAccess(Expression memberAccess) { var memberAccessString = memberAccess.ToString(); - if (_nullCheckSubjects.Contains(memberAccessString) || - _nestedAccessesByPath.ContainsKey(memberAccessString)) + if (_nullCheckSubjects?.Contains(memberAccessString) == true || + _nestedAccessesByPath?.ContainsKey(memberAccessString) == true) { return; } - _nestedAccessesByPath.Add(memberAccessString, memberAccess); + NestedAccessesByPath.Add(memberAccessString, memberAccess); } } From 6e6acaf0dc1a80c05c384ac8cbccbf88052b1128 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 15 Jun 2019 18:11:40 +0100 Subject: [PATCH 13/14] Fixing reverse data source test coverage --- .../WhenConfiguringReverseDataSources.cs | 39 ++++- ...onfiguringReverseDataSourcesIncorrectly.cs | 137 +++++++++++++++++- .../DataSourceReversalSetting.cs | 1 - 3 files changed, 170 insertions(+), 7 deletions(-) diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSources.cs index 6a0b23ff0..1df1ff1bf 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSources.cs @@ -42,12 +42,10 @@ public void ShouldReverseAConfiguredMemberByMappingScope() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To>() + .From().To>() .AutoReverseConfiguredDataSources() .And - .Map(ctx => ctx.Source.Id) - .To(pp => pp.Value); + .Map(ctx => ctx.Source.Id).To(pp => pp.Value); var source = new Person { Id = Guid.NewGuid() }; var result = mapper.Map(source).ToANew>(); @@ -60,6 +58,39 @@ public void ShouldReverseAConfiguredMemberByMappingScope() } } + [Fact] + public void ShouldReverseConfiguredMembersByMappingScope() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources() + .And + .Map(ctx => ctx.Source.Id).To(pp => pp.Value); + + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources() + .And + .Map(ctx => ctx.Source.Id).To(pp => pp.Value); + + var source = new Person { Id = Guid.NewGuid() }; + + var propertyResult = mapper.Map(source).ToANew>(); + propertyResult.Value.ShouldBe(source.Id); + + var reversePropertyResult = mapper.Map(propertyResult).ToANew(); + reversePropertyResult.Id.ShouldBe(source.Id); + + var fieldResult = mapper.Map(source).ToANew>(); + fieldResult.Value.ShouldBe(source.Id); + + var reverseFieldResult = mapper.Map(fieldResult).ToANew(); + reverseFieldResult.Id.ShouldBe(source.Id); + } + } + [Fact] public void ShouldReverseAConfiguredMemberByMemberScope() { diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs index 02e7a00e2..da37f2ab9 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs @@ -60,8 +60,7 @@ public void ShouldErrorIfMappingScopeRedundantMemberScopeOptInConfigured() using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To>() + .From().To>() .AutoReverseConfiguredDataSources() .And .Map(ctx => ctx.Source.Id).To(pp => pp.Value) @@ -130,6 +129,140 @@ public void ShouldErrorIfMappingScopeRedundantMemberScopeOptOutConfigured() configEx.Message.ShouldContain("disabled by default"); } + [Fact] + public void ShouldErrorIfDuplicateMappingScopeOptInConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("already enabled"); + configEx.Message.ShouldContain("Person -> PublicProperty"); + } + + [Fact] + public void ShouldErrorIfAllSourcesConflictingMappingScopeOptInConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .To>() + .AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("already enabled"); + configEx.Message.ShouldContain("to PublicProperty"); + } + + [Fact] + public void ShouldErrorIfConflictingMappingScopeOptInConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .DoNotAutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("cannot be disabled"); + configEx.Message.ShouldContain("already been enabled"); + configEx.Message.ShouldContain("Person -> PublicProperty"); + } + + [Fact] + public void ShouldErrorIfDuplicateMappingScopeOptOutConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .DoNotAutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .DoNotAutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("already disabled"); + configEx.Message.ShouldContain("Person -> PublicProperty"); + } + + [Fact] + public void ShouldErrorIfAllSourcesConflictingMappingScopeOptOutConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .To>() + .DoNotAutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .DoNotAutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("already disabled"); + configEx.Message.ShouldContain("to PublicProperty"); + } + + [Fact] + public void ShouldErrorIfConflictingMappingScopeOptOutConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .DoNotAutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("cannot be enabled"); + configEx.Message.ShouldContain("already been disabled"); + configEx.Message.ShouldContain("Person -> PublicProperty"); + } + [Fact] public void ShouldErrorIfRedundantReverseDataSourceConfigured() { diff --git a/AgileMapper/Configuration/DataSourceReversalSetting.cs b/AgileMapper/Configuration/DataSourceReversalSetting.cs index a1d7e8bc2..2a8848c01 100644 --- a/AgileMapper/Configuration/DataSourceReversalSetting.cs +++ b/AgileMapper/Configuration/DataSourceReversalSetting.cs @@ -53,7 +53,6 @@ public string GetConflictMessage(DataSourceReversalSetting conflicting) return GetRedundantSettingConflictMessage(conflicting, " by default"); } - // TODO: Test coverage?! var targetType = ConfigInfo.TargetType.GetFriendlyName(); if (ConfigInfo.IsForAllSourceTypes()) From a41938ced6f7f7c88cbef89a146b2aff7b128fdc Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 15 Jun 2019 21:51:54 +0100 Subject: [PATCH 14/14] Increasing code coverage / Removing unused or obselete code --- .../AgileMapper.UnitTests.csproj | 1 + .../WhenConfiguringEntityMapping.cs | 38 ++++++++++++ ...henConfiguringObjectTrackingIncorrectly.cs | 27 +++++++- ...onfiguringReverseDataSourcesIncorrectly.cs | 21 +++++++ .../Configuration/WhenResolvingServices.cs | 61 +++++++++++++++++++ .../WhenCloningDictionarySettings.cs | 41 +++++++++++++ .../ConfiguredServiceProvider.cs | 6 +- .../DataSourceReversalSetting.cs | 5 -- .../Configuration/EntityKeyMappingSetting.cs | 22 +++---- .../Configuration/MapToNullCondition.cs | 9 +-- AgileMapper/Members/MemberExtensions.cs | 7 --- .../SourceInstanceDictionaryAdapter.cs | 17 +----- .../EnumerablePopulationBuilder.cs | 2 +- 13 files changed, 199 insertions(+), 58 deletions(-) create mode 100644 AgileMapper.UnitTests/MapperCloning/WhenCloningDictionarySettings.cs diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index d3b3922f3..28d4c8701 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -144,6 +144,7 @@ + diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringEntityMapping.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringEntityMapping.cs index 558c457fd..c15b593b6 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringEntityMapping.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringEntityMapping.cs @@ -1,5 +1,6 @@ namespace AgileObjects.AgileMapper.UnitTests.Configuration { + using System; using AgileMapper.Configuration; using Common; using TestClasses; @@ -73,6 +74,43 @@ public void ShouldIgnoreEntityKeysForASpecificSourceAndTargetType() } } + [Fact] + public void ShouldIgnoreEntityKeysForSpecificSourceAndTargetTypes() + { + using (var mapper = Mapper.CreateNew()) + { + var sourceId = new { Id = 999 }; + var sourceIdAndDate = new { Id = 999, DateCreated = DateTime.Now }; + + mapper.WhenMapping.MapEntityKeys(); + + mapper.WhenMapping + .From(sourceId).To() + .IgnoreEntityKeys(); + + mapper.WhenMapping + .From(sourceIdAndDate).To() + .IgnoreEntityKeys(); + + var sourceIdResult = mapper.Map(sourceId).ToANew(); + + sourceIdResult.Id.ShouldBeDefault(); + + var sourceIdAndDateResult = mapper.Map(sourceIdAndDate).ToANew(); + + sourceIdAndDateResult.Id.ShouldBeDefault(); + sourceIdAndDateResult.DateCreated.ShouldBe(sourceIdAndDate.DateCreated); + + var nonMatchingSourceResult = mapper.Map(new { Id = 987, Name = "Fred" }).ToANew(); + + nonMatchingSourceResult.Id.ShouldBe(987); + + var nonMatchingTargetResult = mapper.Map(sourceId).ToANew(); + + nonMatchingTargetResult.Id.ShouldBe(999); + } + } + [Fact] public void ShouldErrorIfDuplicateMapKeysConfigured() { diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectTrackingIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectTrackingIncorrectly.cs index 43bc28176..6164042ae 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectTrackingIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringObjectTrackingIncorrectly.cs @@ -125,13 +125,12 @@ public void ShouldErrorIfGlobalObjectTrackingDisabledTwice() } [Fact] - public void ShouldErrorIfObjectTrackingDisabledTwice() + public void ShouldErrorIfDuplicateObjectTrackingDisabled() { using (var mapper = Mapper.CreateNew()) { mapper.WhenMapping - .From() - .To() + .From().To() .DisableObjectTracking(); var configEx = Should.Throw(() => @@ -146,5 +145,27 @@ public void ShouldErrorIfObjectTrackingDisabledTwice() configEx.Message.ShouldContain("PersonViewModel -> Person"); } } + + [Fact] + public void ShouldErrorIfRedundantObjectTrackingDisabled() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .To() + .DisableObjectTracking(); + + var configEx = Should.Throw(() => + { + mapper.WhenMapping + .From() + .To() + .DisableObjectTracking(); + }); + + configEx.Message.ShouldContain("Object tracking is already disabled"); + configEx.Message.ShouldContain("to Person"); + } + } } } diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs index da37f2ab9..02fdecd06 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringReverseDataSourcesIncorrectly.cs @@ -72,6 +72,27 @@ public void ShouldErrorIfMappingScopeRedundantMemberScopeOptInConfigured() configEx.Message.ShouldContain("enabled by default"); } + [Fact] + public void ShouldErrorIfMappingScopeRedundantDerivedMappingScopeOptInConfigured() + { + var configEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + + mapper.WhenMapping + .From().To>() + .AutoReverseConfiguredDataSources(); + } + }); + + configEx.Message.ShouldContain("already enabled"); + configEx.Message.ShouldContain("Product -> PublicProperty"); + } + [Fact] public void ShouldErrorIfGlobalScopeRedundantMappingScopeOptOutConfigured() { diff --git a/AgileMapper.UnitTests/Configuration/WhenResolvingServices.cs b/AgileMapper.UnitTests/Configuration/WhenResolvingServices.cs index dc6bc49ef..db672d932 100644 --- a/AgileMapper.UnitTests/Configuration/WhenResolvingServices.cs +++ b/AgileMapper.UnitTests/Configuration/WhenResolvingServices.cs @@ -512,6 +512,46 @@ public void ShouldErrorIfNoServiceProviderMethodsAvailable() configEx.Message.ShouldContain("Resolve"); } + [Fact] + public void ShouldErrorIfServiceProviderMethodIsParameterless() + { + var configEx = Should.Throw(() => + Mapper.WhenMapping.UseServiceProvider(new ParameterlessInvalidServiceProvider())); + + configEx.Message.ShouldContain("No supported service provider methods were found"); + configEx.Message.ShouldContain("ParameterlessInvalidServiceProvider"); + } + + [Fact] + public void ShouldErrorIfServiceProviderMethodHasInvalidFirstParameterType() + { + var configEx = Should.Throw(() => + Mapper.WhenMapping.UseServiceProvider(new InvalidFirstParameterTypeInvalidServiceProvider())); + + configEx.Message.ShouldContain("No supported service provider methods were found"); + configEx.Message.ShouldContain("InvalidFirstParameterTypeInvalidServiceProvider"); + } + + [Fact] + public void ShouldErrorIfServiceProviderMethodHasInvalidSecondParameterType() + { + var configEx = Should.Throw(() => + Mapper.WhenMapping.UseServiceProvider(new InvalidSecondParameterTypeInvalidServiceProvider())); + + configEx.Message.ShouldContain("No supported service provider methods were found"); + configEx.Message.ShouldContain("InvalidSecondParameterTypeInvalidServiceProvider"); + } + + [Fact] + public void ShouldErrorIfServiceProviderMethodHasInvalidExtraParameterType() + { + var configEx = Should.Throw(() => + Mapper.WhenMapping.UseServiceProvider(new InvalidExtraParameterTypeInvalidServiceProvider())); + + configEx.Message.ShouldContain("No supported service provider methods were found"); + configEx.Message.ShouldContain("InvalidExtraParameterTypeInvalidServiceProvider"); + } + #region Helper Classes public interface ILogger @@ -611,6 +651,27 @@ public class GetServiceOrInstanceServiceProvider public object GetInstance(Type serviceType) => Activator.CreateInstance(serviceType); } + public class ParameterlessInvalidServiceProvider + { + public object GetService() => Activator.CreateInstance(typeof(object)); + } + + public class InvalidFirstParameterTypeInvalidServiceProvider + { + // ReSharper disable once AssignNullToNotNullAttribute + public object GetService(string serviceTypeName) => Activator.CreateInstance(Type.GetType(serviceTypeName)); + } + + public class InvalidSecondParameterTypeInvalidServiceProvider + { + public object GetService(Type serviceType, int ctorIndex) => Activator.CreateInstance(serviceType); + } + + public class InvalidExtraParameterTypeInvalidServiceProvider + { + public object GetService(Type serviceType, string name, int ctorIndex) => Activator.CreateInstance(serviceType); + } + #endregion } } diff --git a/AgileMapper.UnitTests/MapperCloning/WhenCloningDictionarySettings.cs b/AgileMapper.UnitTests/MapperCloning/WhenCloningDictionarySettings.cs new file mode 100644 index 000000000..347f8c936 --- /dev/null +++ b/AgileMapper.UnitTests/MapperCloning/WhenCloningDictionarySettings.cs @@ -0,0 +1,41 @@ +namespace AgileObjects.AgileMapper.UnitTests.MapperCloning +{ + using System.Collections.Generic; + using Common; + using TestClasses; +#if !NET35 + using Xunit; +#else + using Fact = NUnit.Framework.TestAttribute; + + [NUnit.Framework.TestFixture] +#endif + public class WhenCloningDictionarySettings + { + [Fact] + public void ShouldCloneFullAndMemberKeys() + { + using (var baseMapper = Mapper.CreateNew()) + { + baseMapper.WhenMapping + .FromDictionariesWithValueType().To() + .MapFullKey("BlahBlah").To(p => p.ProductId) + .And + .MapMemberNameKey("ProductPrice").To(p => p.Price); + + using (var clonedMapper = baseMapper.CloneSelf()) + { + var source = new Dictionary + { + ["BlahBlah"] = "DictionaryAdventures.co.uk", + ["ProductPrice"] = 12.00 + }; + var result = clonedMapper.Map(source).ToANew(); + + result.ProductId.ShouldBe("DictionaryAdventures.co.uk"); + result.Price.ShouldBe(12.00); + } + } + } + } +} diff --git a/AgileMapper/Configuration/ConfiguredServiceProvider.cs b/AgileMapper/Configuration/ConfiguredServiceProvider.cs index 5ee7d3b6a..4544e8eb4 100644 --- a/AgileMapper/Configuration/ConfiguredServiceProvider.cs +++ b/AgileMapper/Configuration/ConfiguredServiceProvider.cs @@ -79,6 +79,7 @@ public static IEnumerable CreateFromOrThrow Array.IndexOf(_serviceProviderMethodNames, method.Name) != -1) .Project(method => GetServiceProviderOrNull( method, providerObject, @@ -108,11 +109,6 @@ private static ConfiguredServiceProvider GetServiceProviderOrNull( ref bool unnamedServiceProviderFound, ref bool namedServiceProviderFound) { - if (Array.IndexOf(_serviceProviderMethodNames, method.Name) == -1) - { - return null; - } - var parameters = method.GetParameters(); if (parameters.None() || (parameters[0].ParameterType != typeof(Type))) diff --git a/AgileMapper/Configuration/DataSourceReversalSetting.cs b/AgileMapper/Configuration/DataSourceReversalSetting.cs index 2a8848c01..36176ce70 100644 --- a/AgileMapper/Configuration/DataSourceReversalSetting.cs +++ b/AgileMapper/Configuration/DataSourceReversalSetting.cs @@ -25,11 +25,6 @@ public DataSourceReversalSetting(MappingConfigInfo configInfo, bool reverse) public override bool ConflictsWith(UserConfiguredItemBase otherItem) { - if (otherItem == this) - { - return true; - } - if (base.ConflictsWith(otherItem)) { var otherSettings = (DataSourceReversalSetting)otherItem; diff --git a/AgileMapper/Configuration/EntityKeyMappingSetting.cs b/AgileMapper/Configuration/EntityKeyMappingSetting.cs index 992a36248..ae0764e34 100644 --- a/AgileMapper/Configuration/EntityKeyMappingSetting.cs +++ b/AgileMapper/Configuration/EntityKeyMappingSetting.cs @@ -25,25 +25,21 @@ public EntityKeyMappingSetting(MappingConfigInfo configInfo, bool mapKeys) public override bool ConflictsWith(UserConfiguredItemBase otherItem) { - if (otherItem == this) + if (!base.ConflictsWith(otherItem)) { - return true; + return false; } - if (base.ConflictsWith(otherItem)) - { - var otherSettings = (EntityKeyMappingSetting)otherItem; - - if ((this == MapAllKeys) || (otherSettings == MapAllKeys)) - { - return (otherSettings.MapKeys == MapKeys); - } + var otherSettings = (EntityKeyMappingSetting)otherItem; - // Settings have overlapping, non-global source and target types - return true; + if ((this == MapAllKeys) || (otherSettings == MapAllKeys)) + { + return (otherSettings.MapKeys == MapKeys); } - return false; + // Settings have overlapping, non-global source and target types + return true; + } public string GetConflictMessage(EntityKeyMappingSetting conflicting) diff --git a/AgileMapper/Configuration/MapToNullCondition.cs b/AgileMapper/Configuration/MapToNullCondition.cs index bafa283c1..20aa0af16 100644 --- a/AgileMapper/Configuration/MapToNullCondition.cs +++ b/AgileMapper/Configuration/MapToNullCondition.cs @@ -34,14 +34,7 @@ public string GetConflictMessage() => $"Type {TargetTypeName} already has a configured map-to-null condition"; public override bool AppliesTo(IBasicMapperData mapperData) - { - if (mapperData.TargetMemberIsEnumerableElement()) - { - return false; - } - - return base.AppliesTo(mapperData); - } + => !mapperData.TargetMemberIsEnumerableElement() && base.AppliesTo(mapperData); protected override Expression GetConditionOrNull(IMemberMapperData mapperData, CallbackPosition position) { diff --git a/AgileMapper/Members/MemberExtensions.cs b/AgileMapper/Members/MemberExtensions.cs index 221287ff6..bfcf0a553 100644 --- a/AgileMapper/Members/MemberExtensions.cs +++ b/AgileMapper/Members/MemberExtensions.cs @@ -77,13 +77,6 @@ public static string GetFriendlyMemberPath( public static bool IsUnmappable(this QualifiedMember member, out string reason) { - if (member.Depth < 2) - { - // Either the root member, QualifiedMember.All or QualifiedMember.None: - reason = null; - return false; - } - if (IsStructNonSimpleMember(member)) { reason = member.Type.GetFriendlyName() + " member on a struct"; diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs index 4d8903c55..8d76bcbfc 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceInstanceDictionaryAdapter.cs @@ -32,7 +32,7 @@ public override Expression GetSourceValues() var returnEmpty = Expression.Return(returnLabel, emptyTarget); var ifKeyNotFoundShortCircuit = GetKeyNotFoundShortCircuit(returnEmpty); - var isValueIsNullShortCircuit = GetNullValueEntryShortCircuitIfAppropriate(returnEmpty); + var isValueIsNullShortCircuit = DictionaryVariables.GetEntryValueAssignment(); var sourceValueBlock = Expression.Block( new[] { DictionaryVariables.Key, DictionaryVariables.Value }, @@ -46,21 +46,6 @@ public override Expression GetSourceValues() public Expression GetKeyNotFoundShortCircuit(Expression shortCircuitReturn) => DictionaryVariables.GetKeyNotFoundShortCircuit(shortCircuitReturn); - private Expression GetNullValueEntryShortCircuitIfAppropriate(Expression shortCircuitReturn) - { - var valueAssignment = DictionaryVariables.GetEntryValueAssignment(); - - if (shortCircuitReturn.Type.CannotBeNull()) - { - return valueAssignment; - } - - var valueIsNull = DictionaryVariables.Value.GetIsDefaultComparison(); - var ifValueNullShortCircuit = Expression.IfThen(valueIsNull, shortCircuitReturn); - - return Expression.Block(valueAssignment, ifValueNullShortCircuit); - } - public Expression GetEntryValueAccess() => DictionaryVariables.GetEntryValueAccess(); public Expression GetSourceCountAccess() => _defaultAdapter.GetSourceCountAccess(); diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index ee83c00a7..04bc0a6f8 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -614,7 +614,7 @@ private static Expression GetValueCheckedElementMapping( { var mappingTryCatchBodyBlock = (BlockExpression)mappingTryCatch.Body; - mappingTryCatchBody = mappingTryCatchBodyBlock.Update( + mappingTryCatchBody = Expression.Block( mappingTryCatchBodyBlock.Variables.Prepend(valueVariable), existingElementValueCheck.Expressions.Append(mappingTryCatchBodyBlock.Expressions)); }