diff --git a/AgileMapper.UnitTests.Common/AgileMapper.UnitTests.Common.csproj b/AgileMapper.UnitTests.Common/AgileMapper.UnitTests.Common.csproj index 616ab412e..477f06402 100644 --- a/AgileMapper.UnitTests.Common/AgileMapper.UnitTests.Common.csproj +++ b/AgileMapper.UnitTests.Common/AgileMapper.UnitTests.Common.csproj @@ -11,6 +11,8 @@ + + diff --git a/AgileMapper.UnitTests.MoreTestClasses.Vb/AgileMapper.UnitTests.MoreTestClasses.Vb.vbproj b/AgileMapper.UnitTests.MoreTestClasses.Vb/AgileMapper.UnitTests.MoreTestClasses.Vb.vbproj new file mode 100644 index 000000000..a3ee1de70 --- /dev/null +++ b/AgileMapper.UnitTests.MoreTestClasses.Vb/AgileMapper.UnitTests.MoreTestClasses.Vb.vbproj @@ -0,0 +1,14 @@ + + + + net35;netstandard1.0 + AgileObjects.AgileMapper.UnitTests.MoreTestClasses.Vb + AgileObjects.AgileMapper.UnitTests.MoreTestClasses.Vb + false + + + + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client + + + diff --git a/AgileMapper.UnitTests.MoreTestClasses.Vb/PublicNamedIndex.vb b/AgileMapper.UnitTests.MoreTestClasses.Vb/PublicNamedIndex.vb new file mode 100644 index 000000000..2482b16a7 --- /dev/null +++ b/AgileMapper.UnitTests.MoreTestClasses.Vb/PublicNamedIndex.vb @@ -0,0 +1,19 @@ +Public Class PublicNamedIndex(Of T) + + Private _valueToReturn As T + + Public WriteOnly Property ValueToReturn As T + Set + _valueToReturn = Value + End Set + End Property + + Public ReadOnly Property Value( + Optional indexOne As Integer = 1, + Optional indexTwo As Integer = 2) As T + Get + Return _valueToReturn + End Get + End Property + +End Class \ No newline at end of file diff --git a/AgileMapper.UnitTests.MoreTestClasses/packages.config b/AgileMapper.UnitTests.MoreTestClasses/packages.config deleted file mode 100644 index ff08b12e1..000000000 --- a/AgileMapper.UnitTests.MoreTestClasses/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj index 88010025d..ed0a8da6d 100644 --- a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj +++ b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj @@ -38,7 +38,6 @@ - diff --git a/AgileMapper.UnitTests.NetCore/AgileMapper.UnitTests.NetCore.csproj b/AgileMapper.UnitTests.NetCore/AgileMapper.UnitTests.NetCore.csproj index 22fa55ed0..73d386da0 100644 --- a/AgileMapper.UnitTests.NetCore/AgileMapper.UnitTests.NetCore.csproj +++ b/AgileMapper.UnitTests.NetCore/AgileMapper.UnitTests.NetCore.csproj @@ -34,8 +34,6 @@ - - diff --git a/AgileMapper.UnitTests.NetCore2/AgileMapper.UnitTests.NetCore2.csproj b/AgileMapper.UnitTests.NetCore2/AgileMapper.UnitTests.NetCore2.csproj index d85efbbb2..94398fbd0 100644 --- a/AgileMapper.UnitTests.NetCore2/AgileMapper.UnitTests.NetCore2.csproj +++ b/AgileMapper.UnitTests.NetCore2/AgileMapper.UnitTests.NetCore2.csproj @@ -35,7 +35,6 @@ - diff --git a/AgileMapper.UnitTests.NetCore3/AgileMapper.UnitTests.NetCore3.csproj b/AgileMapper.UnitTests.NetCore3/AgileMapper.UnitTests.NetCore3.csproj index a2e953764..4032ac87f 100644 --- a/AgileMapper.UnitTests.NetCore3/AgileMapper.UnitTests.NetCore3.csproj +++ b/AgileMapper.UnitTests.NetCore3/AgileMapper.UnitTests.NetCore3.csproj @@ -35,7 +35,6 @@ - diff --git a/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj b/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj index f4d38bc3f..aa897987e 100644 --- a/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj +++ b/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj @@ -24,8 +24,6 @@ - - diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index b56e9d4da..434d7343a 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -30,7 +30,6 @@ - diff --git a/AgileMapper.UnitTests/Configuration/WhenViewingMappingPlans.cs b/AgileMapper.UnitTests/Configuration/WhenViewingMappingPlans.cs index 73a6bf857..cb7ed5c08 100644 --- a/AgileMapper.UnitTests/Configuration/WhenViewingMappingPlans.cs +++ b/AgileMapper.UnitTests/Configuration/WhenViewingMappingPlans.cs @@ -5,6 +5,7 @@ using AgileMapper.Members; using Common; using Common.TestClasses; + using MoreTestClasses.Vb; using TestClasses; #if !NET35 using Xunit; @@ -154,6 +155,16 @@ public void ShouldIncludeUnmappableReadOnlyArrayMemberDetails() } } + [Fact] + public void ShouldIncludeUnmappableIndexedPropertyDetails() + { + string plan = Mapper + .GetPlanFor>>() + .ToANew>>(); + + plan.ShouldContain("requires index(es) - indexOne: int, indexTwo: int"); + } + [Fact] public void ShouldIncludeUnmappableReadOnlyReadOnlyCollectionMemberDetails() { diff --git a/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs b/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs index d766809df..ad049c2c2 100644 --- a/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs +++ b/AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs @@ -3,6 +3,7 @@ using System; using AgileMapper.Extensions; using Common; + using MoreTestClasses.Vb; using TestClasses; #if !NET35 using Xunit; @@ -133,6 +134,38 @@ public void ShouldConditionallyUseConstructorsWhereArgumentsAreNull() addressResult.Address.Line1.ShouldBe("Line 1!"); } + // See https://github.com/agileobjects/AgileMapper/issues/221 + [Fact] + public void ShouldIgnoreSourceIndexedProperties() + { + var source = new PublicNamedIndex> + { + ValueToReturn = new PublicField { Value = "Test" } + }; + + var result = Mapper + .Map(source) + .ToANew>>(); + + result.ShouldNotBeNull().Value.ShouldBeNull(); + } + + // See https://github.com/agileobjects/AgileMapper/issues/221 + [Fact] + public void ShouldIgnoreTargetIndexedProperties() + { + var source = new PublicField> + { + Value = new PublicField { Value = "Hello!" } + }; + + var result = Mapper + .Map(source) + .ToANew>>(); + + result.ShouldNotBeNull().get_Value().ShouldBeNull(); + } + #region Helper Classes private class CtorTester diff --git a/AgileMapper.sln b/AgileMapper.sln index a4a4939f3..d761be3df 100644 --- a/AgileMapper.sln +++ b/AgileMapper.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30002.166 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32319.34 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{05AB6D17-6066-41D5-8E79-31C342DFC2DC}" ProjectSection(SolutionItems) = preProject @@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgileMapper.UnitTests.NetCo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AgileMapper.UnitTests.NetCore2", "AgileMapper.UnitTests.NetCore2\AgileMapper.UnitTests.NetCore2.csproj", "{72287439-1634-4164-ABAC-E4A462235C5D}" EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "AgileMapper.UnitTests.MoreTestClasses.Vb", "AgileMapper.UnitTests.MoreTestClasses.Vb\AgileMapper.UnitTests.MoreTestClasses.Vb.vbproj", "{4D694869-5B53-4145-BD7F-9187652FD847}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -137,6 +139,10 @@ Global {72287439-1634-4164-ABAC-E4A462235C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU {72287439-1634-4164-ABAC-E4A462235C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU {72287439-1634-4164-ABAC-E4A462235C5D}.Release|Any CPU.Build.0 = Release|Any CPU + {4D694869-5B53-4145-BD7F-9187652FD847}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D694869-5B53-4145-BD7F-9187652FD847}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D694869-5B53-4145-BD7F-9187652FD847}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D694869-5B53-4145-BD7F-9187652FD847}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -162,6 +168,7 @@ Global {5085AA3F-A5B7-40E2-9309-7E0B81FBE113} = {88D48136-E176-4AF7-ACA4-A2954F3BD55B} {F9307FDB-0F73-40AB-B7AC-AE79CFA5521C} = {88D48136-E176-4AF7-ACA4-A2954F3BD55B} {72287439-1634-4164-ABAC-E4A462235C5D} = {88D48136-E176-4AF7-ACA4-A2954F3BD55B} + {4D694869-5B53-4145-BD7F-9187652FD847} = {88D48136-E176-4AF7-ACA4-A2954F3BD55B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C39E9BDB-0077-445C-8F09-801CAE1AF8AB} diff --git a/AgileMapper/Members/Member.cs b/AgileMapper/Members/Member.cs index f42316b00..e36d748c3 100644 --- a/AgileMapper/Members/Member.cs +++ b/AgileMapper/Members/Member.cs @@ -19,7 +19,7 @@ internal class Member public const string RootSourceMemberName = "Source"; public const string RootTargetMemberName = "Target"; - private static readonly int _ctorParameterHashCode = + private static readonly int _ctorParameterHashCode = MemberType.ConstructorParameter.GetHashCode(); private readonly IAccessFactory _accessFactory; @@ -30,6 +30,7 @@ private Member( Type type, MemberInfo memberInfo, IAccessFactory accessFactory = null, + bool isIndexed = false, bool isReadable = true, bool isWriteable = true, bool isRoot = false) @@ -39,6 +40,7 @@ private Member( memberInfo.DeclaringType, type, accessFactory, + isIndexed, isReadable, isWriteable, isRoot) @@ -52,6 +54,7 @@ private Member( Type declaringType, Type type, IAccessFactory accessFactory = null, + bool isIndexed = false, bool isReadable = true, bool isWriteable = true, bool isRoot = false) @@ -61,6 +64,7 @@ private Member( DeclaringType = declaringType; Type = type; _accessFactory = accessFactory; + IsIndexed = isIndexed; IsReadable = isReadable; IsWriteable = isWriteable; IsRoot = isRoot; @@ -136,13 +140,21 @@ public static Member Field(FieldInfo fieldInfo) public static Member Property(PropertyInfo propertyInfo) { + var isReadable = propertyInfo.IsReadable(); + var isWritable = propertyInfo.IsWritable(); + + var isIndexed = + isReadable && propertyInfo.GetGetter().GetParameters().Any() || + isWritable && propertyInfo.GetSetter().GetParameters().Length > 1; + return new Member( MemberType.Property, propertyInfo.PropertyType, propertyInfo, new PropertyInfoWrapper(propertyInfo), - propertyInfo.IsReadable(), - propertyInfo.IsWritable()); + isIndexed, + isReadable, + isWritable); } public static Member GetMethod(MethodInfo methodInfo) @@ -203,6 +215,8 @@ public static Member DictionaryEntry(string sourceMemberName, DictionaryTargetMe public bool IsSimple { get; } + public bool IsIndexed { get; set; } + public bool IsReadable { get; } public bool IsWriteable { get; } @@ -240,6 +254,7 @@ public Member WithType(Type runtimeType) runtimeType, MemberInfo, _accessFactory, + IsIndexed, IsReadable, IsWriteable, IsRoot); @@ -250,6 +265,7 @@ public Member WithType(Type runtimeType) Name, DeclaringType, runtimeType, + isIndexed: IsIndexed, isReadable: IsReadable, isWriteable: IsWriteable, isRoot: IsRoot); diff --git a/AgileMapper/Members/MemberCache.cs b/AgileMapper/Members/MemberCache.cs index 837ab19ab..28984a04a 100644 --- a/AgileMapper/Members/MemberCache.cs +++ b/AgileMapper/Members/MemberCache.cs @@ -1,14 +1,14 @@ namespace AgileObjects.AgileMapper.Members { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Reflection; using Caching; using Extensions; using Extensions.Internal; using NetStandardPolyfills; using ReadableExpressions.Extensions; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; using static System.StringComparer; internal class MemberCache @@ -132,7 +132,11 @@ private static IEnumerable GetProperties(Type targetType, Func AllExceptBclComplexTypes(property.PropertyType); - private static bool OnlyGettable(PropertyInfo property) => property.IsReadable(); + private static bool OnlyGettable(PropertyInfo property) + { + var getter = property.GetGetter(); + return getter != null && getter.GetParameters().None(); + } #endregion diff --git a/AgileMapper/Members/MemberExtensionMethods.cs b/AgileMapper/Members/MemberExtensionMethods.cs index 8e7c3c658..6f7bb3a7c 100644 --- a/AgileMapper/Members/MemberExtensionMethods.cs +++ b/AgileMapper/Members/MemberExtensionMethods.cs @@ -103,7 +103,7 @@ public static bool IsUnmappable(this QualifiedMember member, out string reason) if (member.IsSimple || member.Type.IsValueType()) { - reason = "readonly " + ((member.IsComplex) ? "struct" : member.Type.GetFriendlyName()); + reason = "readonly " + (member.IsComplex ? "struct" : member.Type.GetFriendlyName()); return true; } @@ -113,6 +113,19 @@ public static bool IsUnmappable(this QualifiedMember member, out string reason) return true; } + if (member.IsIndexed) + { + var property = (PropertyInfo)member.LeafMember.MemberInfo; + + var indexes = string.Join(", ", property + .GetGetter() + .GetParameters() + .ProjectToArray(p => p.Name + ": " + p.ParameterType.GetFriendlyName())); + + reason = "requires index(es) - " + indexes; + return true; + } + reason = null; return false; } @@ -285,7 +298,7 @@ public static QualifiedMember ToSourceMemberOrNull( this Expression memberAccess, MapperContext mapperContext) { - return memberAccess.ToSourceMember(mapperContext, nt => { }); + return memberAccess.ToSourceMember(mapperContext, _ => { }); } public static QualifiedMember ToSourceMemberOrNull( @@ -294,7 +307,7 @@ public static QualifiedMember ToSourceMemberOrNull( out string failureReason) { var hasUnsupportedNodeType = false; - var sourceMember = memberAccess.ToSourceMember(mapperContext, nt => hasUnsupportedNodeType = true); + var sourceMember = memberAccess.ToSourceMember(mapperContext, _ => hasUnsupportedNodeType = true); if (hasUnsupportedNodeType) { @@ -347,7 +360,7 @@ public static QualifiedMember ToTargetMemberOrNull( out string failureReason) { var hasUnsupportedNodeType = false; - var targetMember = memberAccess.ToTargetMember(mapperContext, nt => hasUnsupportedNodeType = true); + var targetMember = memberAccess.ToTargetMember(mapperContext, _ => hasUnsupportedNodeType = true); if (hasUnsupportedNodeType) { diff --git a/AgileMapper/Members/QualifiedMember.cs b/AgileMapper/Members/QualifiedMember.cs index 18fd2dbb2..ea501876d 100644 --- a/AgileMapper/Members/QualifiedMember.cs +++ b/AgileMapper/Members/QualifiedMember.cs @@ -180,6 +180,8 @@ public static QualifiedMember Create(Member[] memberChain, MapperContext mapperC public bool IsReadable => LeafMember.IsReadable; public bool IsReadOnly { get; set; } + + public bool IsIndexed => LeafMember.IsIndexed; public bool IsRecursion { get; } diff --git a/AgileMapper/Members/SourceMemberMatchContext.cs b/AgileMapper/Members/SourceMemberMatchContext.cs index a2bb6dc9b..9c1802745 100644 --- a/AgileMapper/Members/SourceMemberMatchContext.cs +++ b/AgileMapper/Members/SourceMemberMatchContext.cs @@ -55,10 +55,7 @@ public ConfiguredSourceMemberIgnoreBase GetSourceMemberIgnoreOrNull(IQualifiedMe public SourceMemberMatch CreateSourceMemberMatch(IQualifiedMember matchingSourceMember = null, bool isUseable = true) { - if (matchingSourceMember == null) - { - matchingSourceMember = MatchingSourceMember; - } + matchingSourceMember ??= MatchingSourceMember; var ignoreCondition = GetSourceMemberCondition(matchingSourceMember);