diff --git a/AgileMapper.UnitTests.Orms.EfCore2/WhenMappingOverEnumerables.cs b/AgileMapper.UnitTests.Orms.EfCore2/WhenMappingOverEnumerables.cs index ca6166934..5c3967c68 100644 --- a/AgileMapper.UnitTests.Orms.EfCore2/WhenMappingOverEnumerables.cs +++ b/AgileMapper.UnitTests.Orms.EfCore2/WhenMappingOverEnumerables.cs @@ -35,11 +35,13 @@ await writeContext.Persons.AddRangeAsync( var localPersons = readContext.Persons.Local; - localPersons.Count.ShouldBe(4); - localPersons.First().Name.ShouldBe("One"); - localPersons.Second().Name.ShouldBe("Two"); - localPersons.Third().Name.ShouldBe("Three"); - localPersons.Fourth().Name.ShouldBe("Four"); + var orderedPersons = localPersons.OrderBy(p => p.PersonId).ToArray(); + + orderedPersons.Length.ShouldBe(4); + orderedPersons.First().Name.ShouldBe("One"); + orderedPersons.Second().Name.ShouldBe("Two"); + orderedPersons.Third().Name.ShouldBe("Three"); + orderedPersons.Fourth().Name.ShouldBe("Four"); mapper.WhenMapping.InstancesOf().IdentifyUsing(p => p.Name); @@ -55,24 +57,26 @@ await writeContext.Persons.AddRangeAsync( await readContext.SaveChangesAsync(); - localPersons.Count.ShouldBe(4); - localPersons.First().Name.ShouldBe("One"); - localPersons.First().Address.ShouldBeNull(); + orderedPersons = localPersons.OrderBy(p => p.PersonId).ToArray(); + + orderedPersons.Length.ShouldBe(4); + orderedPersons.First().Name.ShouldBe("One"); + orderedPersons.First().Address.ShouldBeNull(); - localPersons.Second().Name.ShouldBe("Two"); - localPersons.Second().Address.ShouldNotBeNull(); - localPersons.Second().Address.Line1.ShouldBe("Two Line 1"); - localPersons.Second().Address.Line2.ShouldBeNull(); + orderedPersons.Second().Name.ShouldBe("Two"); + orderedPersons.Second().Address.ShouldNotBeNull(); + orderedPersons.Second().Address.Line1.ShouldBe("Two Line 1"); + orderedPersons.Second().Address.Line2.ShouldBeNull(); - localPersons.Third().Name.ShouldBe("Three"); - localPersons.Third().Address.ShouldNotBeNull(); - localPersons.Third().Address.Line1.ShouldBe("Three Line 1"); - localPersons.Third().Address.Line2.ShouldBe("Three Line 2"); + orderedPersons.Third().Name.ShouldBe("Three"); + orderedPersons.Third().Address.ShouldNotBeNull(); + orderedPersons.Third().Address.Line1.ShouldBe("Three Line 1"); + orderedPersons.Third().Address.Line2.ShouldBe("Three Line 2"); - localPersons.Fourth().Name.ShouldBe("Five"); - localPersons.Fourth().Address.ShouldNotBeNull(); - localPersons.Fourth().Address.Line1.ShouldBe("Five Line 1"); - localPersons.Fourth().Address.Line2.ShouldBeNull(); + orderedPersons.Fourth().Name.ShouldBe("Five"); + orderedPersons.Fourth().Address.ShouldNotBeNull(); + orderedPersons.Fourth().Address.Line1.ShouldBe("Five Line 1"); + orderedPersons.Fourth().Address.Line2.ShouldBeNull(); } }); } diff --git a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringTypeIdentifiersInline.cs b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringTypeIdentifiersInline.cs index 8c7daf1bd..b060c15e0 100644 --- a/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringTypeIdentifiersInline.cs +++ b/AgileMapper.UnitTests/Configuration/Inline/WhenConfiguringTypeIdentifiersInline.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; + using AgileMapper.Configuration; using Common; using TestClasses; #if !NET35 @@ -81,5 +82,141 @@ public void ShouldExtendConfiguredIdentifiersInline() mapper.InlineContexts().ShouldHaveSingleItem(); } } + + [Fact] + public void ShouldUseACompositeIdentifierInline() + { + using (var mapper = Mapper.CreateNew()) + { + var source = new[] + { + new WeddingDto + { + BrideName = "Nat", + GroomName = "Andy", + BrideAddressLine1 = "Nat + Andy's House", + GroomAddressLine1 = "Nat + Andy's House" + }, + new WeddingDto + { + BrideName = "Timea", + GroomName = "David", + BrideAddressLine1 = "Timea + David's House", + GroomAddressLine1 = "Timea + David's House" + } + }; + + var target = new List + { + new WeddingDto + { + BrideName = "Nat", + GroomName = "Andy" + }, + new WeddingDto + { + BrideName = "Kate", + GroomName = "Steve" + } + }; + + mapper.Map(source).OnTo(target, cfg => cfg + .WhenMapping + .InstancesOf() + .IdentifyUsing(a => a.BrideName, a => a.GroomName)); + + target.Count.ShouldBe(3); + + target.First().BrideName.ShouldBe("Nat"); + target.First().GroomName.ShouldBe("Andy"); + target.First().BrideAddressLine1.ShouldBe("Nat + Andy's House"); + target.First().GroomAddressLine1.ShouldBe("Nat + Andy's House"); + + target.Second().BrideName.ShouldBe("Kate"); + target.Second().GroomName.ShouldBe("Steve"); + target.Second().BrideAddressLine1.ShouldBeNull(); + target.Second().GroomAddressLine1.ShouldBeNull(); + + target.Third().BrideName.ShouldBe("Timea"); + target.Third().GroomName.ShouldBe("David"); + target.Third().BrideAddressLine1.ShouldBe("Timea + David's House"); + target.Third().GroomAddressLine1.ShouldBe("Timea + David's House"); + } + } + + [Fact] + public void ShouldUseConfiguredIdentifierInCompositeIdentifierInline() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .InstancesOf>() + .IdentifyUsing(ptf => ptf.Value1); + + var source = new[] + { + new PublicTwoFields> + { + Value1 = 111, + Value2 = new PublicTwoFields { Value1 = 222, Value2 = 333 } + }, + new PublicTwoFields> + { + Value1 = null, + Value2 = null + }, + }; + + var target = new List>> + { + new PublicTwoFields> + { + Value1 = 111, + Value2 = new PublicTwoFields { Value1 = 222 } + } + }; + + var itemOne = target.First(); + + mapper.Map(source).OnTo(target, cfg => cfg + .WhenMapping + .InstancesOf>>() + .IdentifyUsing(ptf => ptf.Value1, ptf => ptf.Value2)); + + target.Count.ShouldBe(2); + + target.First().ShouldBeSameAs(itemOne); + target.First().Value1.ShouldBe(111); + target.First().Value2.ShouldNotBeNull(); + target.First().Value2.Value1.ShouldBe(222); + target.First().Value2.Value2.ShouldBe(333); + + target.Second().Value1.ShouldBeNull(); + } + } + + [Fact] + public void ShouldErrorIfUnidentifiableComplexTypeIdentifierSuppliedInline() + { + var idsEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + var source = new[] + { + new PublicTwoFields>() + }; + + mapper.Map(source).Over(new List>>(1), cfg => cfg + .WhenMapping + .InstancesOf>>() + .IdentifyUsing(ptf => ptf.Value1, ptf => ptf.Value2)); + } + }); + + idsEx.Message.ShouldContain("Unable to determine identifier"); + idsEx.Message.ShouldContain("ptf.Value2 of Type 'PublicField'"); + idsEx.InnerException.ShouldBeOfType(); + } } } diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringTypeIdentifiers.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringTypeIdentifiers.cs index 204c46292..4ef6f83f8 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringTypeIdentifiers.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringTypeIdentifiers.cs @@ -93,39 +93,129 @@ public void ShouldUseAConfiguredIdentifierExpression() } [Fact] - public void ShouldErrorIfRedundantIdentifierSpecified() + public void ShouldUseACompositeIdentifierWithAnEntityKey() { using (var mapper = Mapper.CreateNew()) { - var idEx = Should.Throw(() => - mapper.WhenMapping - .InstancesOf() - .IdentifyUsing(p => p.Id)); + mapper.WhenMapping + .InstancesOf>() + .IdentifyUsing(ptf => ptf.Value1, ptf => ptf.Value2); + + var source = new[] + { + new PublicTwoFields + { + Value1 = 123, + Value2 = new Product { ProductId = "321", Price = 99.99 } + }, + new PublicTwoFields + { + Value1 = 456, + Value2 = new Product { ProductId = "654", Price = 11.99 } + } + }; + + var target = new List> + { + new PublicTwoFields + { + Value1 = 123, + Value2 = new Product { ProductId = "333", Price = 10.99 } + }, + new PublicTwoFields + { + Value1 = 456, + Value2 = new Product { ProductId = "654", Price = 10.99 } + } + }; + + var itemOne = target.First(); + var itemTwo = target.Second(); + + mapper.Map(source).Over(target); + + target.Count.ShouldBe(2); - idEx.Message.ShouldContain("Id is automatically used as the identifier"); - idEx.Message.ShouldContain("does not need to be configured"); + target.First().ShouldBeSameAs(itemTwo); + target.First().Value1.ShouldBe(456); + target.First().Value2.ShouldNotBeNull(); + target.First().Value2.ProductId.ShouldBe("654"); + target.First().Value2.Price.ShouldBe(11.99); + + target.Second().ShouldNotBeSameAs(itemOne); + target.Second().Value1.ShouldBe(123); + target.Second().Value2.ShouldNotBeNull(); + target.Second().Value2.ProductId.ShouldBe("321"); + target.Second().Value2.Price.ShouldBe(99.99); } } [Fact] - public void ShouldErrorIfRedundantCustomNamingIdentifierSpecified() + public void ShouldErrorIfNoCompositeIdentifiersSupplied() { - using (var mapper = Mapper.CreateNew()) + var idsEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.InstancesOf().IdentifyUsing(); + } + }); + + idsEx.Message.ShouldContain("composite identifier values must be specified"); + idsEx.InnerException.ShouldBeOfType(); + } + + [Fact] + public void ShouldErrorIfNullCompositeIdentifierSupplied() + { + var idsEx = Should.Throw(() => { - mapper.WhenMapping.UseNamePattern("^_(.+)_$"); + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.InstancesOf().IdentifyUsing(p => p.Name, null); + } + }); + + idsEx.Message.ShouldContain("composite identifier values must be non-null"); + idsEx.InnerException.ShouldBeOfType(); + } + + [Fact] + public void ShouldErrorIfRedundantIdentifierSupplied() + { + var idEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.InstancesOf().IdentifyUsing(p => p.Id); + } + }); + + idEx.Message.ShouldContain("Id is automatically used as the identifier"); + idEx.Message.ShouldContain("does not need to be configured"); + } + + [Fact] + public void ShouldErrorIfRedundantCustomNamingIdentifierSupplied() + { + var idEx = Should.Throw(() => + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping.UseNamePattern("^_(.+)_$"); - var idEx = Should.Throw(() => mapper.WhenMapping .InstancesOf(new { _Id_ = default(int) }) - .IdentifyUsing(d => d._Id_)); + .IdentifyUsing(d => d._Id_); + } + }); - idEx.Message.ShouldContain("_Id_ is automatically used as the identifier"); - idEx.Message.ShouldContain("does not need to be configured"); - } + idEx.Message.ShouldContain("_Id_ is automatically used as the identifier"); + idEx.Message.ShouldContain("does not need to be configured"); } [Fact] - public void ShouldErrorIfMultipleIdentifiersSpecifiedForSameType() + public void ShouldErrorIfMultipleIdentifiersSuppliedForSameType() { Should.Throw(() => { diff --git a/AgileMapper/Api/Configuration/InstanceConfigurator.cs b/AgileMapper/Api/Configuration/InstanceConfigurator.cs index 0b944614c..b7f39336c 100644 --- a/AgileMapper/Api/Configuration/InstanceConfigurator.cs +++ b/AgileMapper/Api/Configuration/InstanceConfigurator.cs @@ -1,12 +1,25 @@ namespace AgileObjects.AgileMapper.Api.Configuration { using System; - using System.Linq.Expressions; + using System.Collections.Generic; using AgileMapper.Configuration; -#if NET35 + using Extensions; using Extensions.Internal; -#endif using Members; + using System.Linq.Expressions; + using ReadableExpressions; + using ReadableExpressions.Extensions; +#if NET35 + using static Microsoft.Scripting.Ast.Expression; + using Expression = Microsoft.Scripting.Ast.Expression; + using ExpressionType = Microsoft.Scripting.Ast.ExpressionType; + using UnaryExpression = Microsoft.Scripting.Ast.UnaryExpression; +#else + using static System.Linq.Expressions.Expression; + using Expression = System.Linq.Expressions.Expression; + using ExpressionType = System.Linq.Expressions.ExpressionType; + using UnaryExpression = System.Linq.Expressions.UnaryExpression; +#endif /// /// Provides options for configuring mappings of the type specified by the type argument. @@ -41,6 +54,113 @@ public void IdentifyUsing(Expression> idExpression) ); } + /// + /// Use a composite identifier composed of the given to + /// uniquely identify instances of the type being configured. + /// + /// + /// The expressions to use to uniquely identify instances of the type being configured. + /// + public void IdentifyUsing(params Expression>[] idExpressions) + { + if (idExpressions.NoneOrNull()) + { + throw new MappingConfigurationException( + "Two or more composite identifier values must be specified.", + new ArgumentException(nameof(idExpressions))); + } + + if (idExpressions.Any(a => a == null)) + { + throw new MappingConfigurationException( + "All supplied composite identifier values must be non-null.", + new ArgumentNullException(nameof(idExpressions))); + } + + var idParts = idExpressions +#if NET35 + .ProjectToArray(id => id.ToDlrExpression()); +#else + ; +#endif + var compositeIdParts = new List((idParts.Length * 2) - 1); + var entityParameter = idParts.First().Parameters.First(); + + compositeIdParts.Add(GetIdPartOrThrow(idParts.First().Body)); + + for (var i = 1; i < idParts.Length;) + { + var idPart = GetIdPartOrThrow(idParts[i++].ReplaceParameterWith(entityParameter)); + + compositeIdParts.Add(StringExpressionExtensions.Underscore); + compositeIdParts.Add(idPart); + } + + var compositeId = compositeIdParts.GetStringConcatCall(); + + var compositeIdLambda = Lambda>(compositeId, entityParameter); + + _configInfo.MapperContext.UserConfigurations.Identifiers.Add(typeof(TObject), compositeIdLambda); + } + + private Expression GetIdPartOrThrow(Expression idPart) + { + if (idPart.Type == typeof(object)) + { + if (idPart.NodeType == ExpressionType.Convert) + { + idPart = ((UnaryExpression)idPart).Operand; + } + + return GetStringIdPart(idPart); + } + + if (idPart.Type.IsSimple()) + { + return GetStringIdPart(idPart); + } + + var typeIdentifier = _configInfo + .MapperContext + .GetIdentifierOrNull(idPart); + + if (typeIdentifier != null) + { + return GetStringIdPart(typeIdentifier); + } + + // ReSharper disable once NotResolvedInText + throw new MappingConfigurationException( + "Unable to determine identifier for composite identifier part " + + $"{idPart.ToReadableString()} of Type '{idPart.Type.GetFriendlyName()}'", + new ArgumentNullException("idExpressions")); + } + + private Expression GetStringIdPart(Expression idPart) + { + if (idPart.Type != typeof(string)) + { + idPart = _configInfo.MapperContext.GetValueConversion(idPart, typeof(string)); + } + + var idPartNestedAccessesChecks = _configInfo + .RuleSet + .GetExpressionInfoFor(idPart) + .NestedAccessChecks; + + if (idPartNestedAccessesChecks == null) + { + return idPart; + } + + idPart = Condition( + idPartNestedAccessesChecks, + idPart, + StringExpressionExtensions.EmptyString); + + return idPart; + } + /// /// Use the given expression to create instances of the type being configured. /// The factory expression is passed a context object containing the current mapping's source and target diff --git a/AgileMapper/Extensions/CollectionData.cs b/AgileMapper/Extensions/CollectionData.cs index 708ef1b3e..427382ad3 100644 --- a/AgileMapper/Extensions/CollectionData.cs +++ b/AgileMapper/Extensions/CollectionData.cs @@ -8,7 +8,8 @@ namespace AgileObjects.AgileMapper.Extensions using NetStandardPolyfills; /// - /// Untyped factory class for creating instances. + /// Untyped factory class for creating instances. This class + /// supports mapping and is not intended to be used from your code. /// public static class CollectionData { @@ -87,28 +88,22 @@ public static CollectionData Create( continue; } - if (targetsById.TryGetValue(sourceItemId, out var targetsWithId)) + if (!targetsById.TryGetValue(sourceItemId, out var targetsWithId) || + EqualityComparer.Default.Equals(sourceItemId, default(TId))) { - if (EqualityComparer.Default.Equals(sourceItemId, default(TId))) - { - newSourceItems.Add(sourceItem); - continue; - } - - var targetItem = targetsWithId[0]; - - absentTargetItems.Remove(targetItem); - intersection.Add(Tuple.Create(sourceItem, targetItem)); - targetsWithId.Remove(targetItem); - - if (targetsWithId.Count == 0) - { - targetsById.Remove(sourceItemId); - } + newSourceItems.Add(sourceItem); + continue; } - else + + var targetItem = targetsWithId[0]; + + absentTargetItems.Remove(targetItem); + intersection.Add(Tuple.Create(sourceItem, targetItem)); + targetsWithId.Remove(targetItem); + + if (targetsWithId.Count == 0) { - newSourceItems.Add(sourceItem); + targetsById.Remove(sourceItemId); } } @@ -131,7 +126,8 @@ private static Dictionary> GetItemsById(IEnumerable } /// - /// Helper class for merging or updating collections. + /// Helper class for merging or updating collections. This class supports mapping and is not + /// intended to be used from your code. /// /// The type of object stored in the source collection. /// The type of object stored in the target collection. diff --git a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs index b5dcbc7ee..d1d22f324 100644 --- a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs @@ -13,6 +13,9 @@ internal static class StringExpressionExtensions { + public static readonly Expression EmptyString = Expression.Field(null, typeof(string), "Empty"); + public static readonly Expression Underscore = "_".ToConstantExpression(); + private static readonly MethodInfo _stringJoinMethod; private static readonly MethodInfo[] _stringConcatMethods; @@ -71,10 +74,9 @@ public static Expression GetStringConcatCall(this IList expressions) return Expression.Call(null, concatMethod, expressions); } - var emptyString = Expression.Field(null, typeof(string), "Empty"); var newStringArray = Expression.NewArrayInit(typeof(string), expressions); - return Expression.Call(null, _stringJoinMethod, emptyString, newStringArray); + return Expression.Call(null, _stringJoinMethod, EmptyString, newStringArray); } private static void OptimiseForStringConcat(IList expressions) @@ -106,7 +108,10 @@ private static void OptimiseForStringConcat(IList expressions) currentNamePart = string.Empty; } - expressions.Insert(0, currentNamePart.ToConstantExpression()); + if (currentNamePart != string.Empty) + { + expressions.Insert(0, currentNamePart.ToConstantExpression()); + } } public static Expression GetFirstOrDefaultCall(this Expression stringAccess) diff --git a/AgileMapper/Extensions/PublicTypeExtensions.cs b/AgileMapper/Extensions/PublicTypeExtensions.cs index 3cb7df8d1..08406207a 100644 --- a/AgileMapper/Extensions/PublicTypeExtensions.cs +++ b/AgileMapper/Extensions/PublicTypeExtensions.cs @@ -24,11 +24,6 @@ public static bool IsSimple(this Type type) { type = type.GetNonNullableType(); - if (type == typeof(ValueType)) - { - return true; - } - if (type.GetTypeCode() != NetStandardTypeCode.Object) { return true; @@ -36,7 +31,8 @@ public static bool IsSimple(this Type type) if ((type == typeof(Guid)) || (type == typeof(TimeSpan)) || - (type == typeof(DateTimeOffset))) + (type == typeof(DateTimeOffset)) || + (type == typeof(ValueType))) { return true; } diff --git a/AgileMapper/MapperContextExtensions.cs b/AgileMapper/MapperContextExtensions.cs new file mode 100644 index 000000000..c4b58e663 --- /dev/null +++ b/AgileMapper/MapperContextExtensions.cs @@ -0,0 +1,41 @@ +namespace AgileObjects.AgileMapper +{ + using System; + using Caching; + using Extensions.Internal; + using Members; +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + + internal static class MapperContextExtensions + { + public static Expression GetIdentifierOrNull( + this MapperContext context, + Expression subject, + ICache cache = null) + { + var typeIdsCache = cache ?? context.Cache.CreateScoped(default(HashCodeComparer)); + + return typeIdsCache.GetOrAdd(TypeKey.ForTypeId(subject.Type), key => + { + var configuredIdentifier = + context.UserConfigurations.Identifiers.GetIdentifierOrNullFor(key.Type); + + if (configuredIdentifier != null) + { + return configuredIdentifier.ReplaceParameterWith(subject); + } + + var identifier = context.Naming.GetIdentifierOrNull(key.Type); + + return identifier?.GetAccess(subject); + }); + } + + public static Expression GetValueConversion(this MapperContext context, Expression value, Type targetType) + => context.ValueConverters.GetConversion(value, targetType); + } +} diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index a2b06d3a4..ca5577b47 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -22,6 +22,11 @@ internal class ExpressionInfoFinder public static readonly ExpressionInfo EmptyExpressionInfo = new ExpressionInfo(null, Enumerable.EmptyArray); + public static ExpressionInfoFinder Default => + _default ?? (_default = new ExpressionInfoFinder(mappingDataObject: null)); + + private static ExpressionInfoFinder _default; + private readonly Expression _mappingDataObject; public ExpressionInfoFinder(Expression mappingDataObject) @@ -355,7 +360,8 @@ private bool GuardMemberAccess(Expression memberAccess) return false; } - if (memberAccess.Type.CannotBeNull() || !memberAccess.IsRootedIn(_mappingDataObject)) + if (memberAccess.Type.CannotBeNull() || + ((_mappingDataObject != null) && !memberAccess.IsRootedIn(_mappingDataObject))) { return false; } diff --git a/AgileMapper/Members/MemberIdentifierSet.cs b/AgileMapper/Members/MemberIdentifierSet.cs index f5f16e3fa..78fc97d18 100644 --- a/AgileMapper/Members/MemberIdentifierSet.cs +++ b/AgileMapper/Members/MemberIdentifierSet.cs @@ -45,7 +45,8 @@ private void ThrowIfIdentifierIsRedundant(Type type, LambdaExpression idMember) var defaultIdentifier = _mapperContext.Naming.GetIdentifierOrNull(type); - if (defaultIdentifier.Name != idMember.Body.GetMemberName()) + if ((defaultIdentifier == null) || + (defaultIdentifier.Name != idMember.Body.GetMemberName())) { return; } diff --git a/AgileMapper/Members/MemberMapperDataExtensions.cs b/AgileMapper/Members/MemberMapperDataExtensions.cs index 2309eb277..71637be75 100644 --- a/AgileMapper/Members/MemberMapperDataExtensions.cs +++ b/AgileMapper/Members/MemberMapperDataExtensions.cs @@ -134,8 +134,20 @@ public static ExpressionInfoFinder.ExpressionInfo GetExpressionInfoFor( Expression value, bool targetCanBeNull) { - return mapperData.RuleSet.Settings.GuardAccessTo(value) - ? mapperData.ExpressionInfoFinder.FindIn(value, targetCanBeNull) + return mapperData.RuleSet.GetExpressionInfoFor( + value, + mapperData.ExpressionInfoFinder, + targetCanBeNull); + } + + public static ExpressionInfoFinder.ExpressionInfo GetExpressionInfoFor( + this MappingRuleSet ruleSet, + Expression value, + ExpressionInfoFinder infoFinder = null, + bool targetCanBeNull = false) + { + return ruleSet.Settings?.GuardAccessTo(value) != false + ? (infoFinder ?? ExpressionInfoFinder.Default).FindIn(value, targetCanBeNull) : ExpressionInfoFinder.EmptyExpressionInfo; } @@ -401,7 +413,7 @@ public static bool CanConvert(this IMemberMapperData mapperData, Type sourceType => mapperData.MapperContext.ValueConverters.CanConvert(sourceType, targetType); public static Expression GetValueConversion(this IMemberMapperData mapperData, Expression value, Type targetType) - => mapperData.MapperContext.ValueConverters.GetConversion(value, targetType); + => mapperData.MapperContext.GetValueConversion(value, targetType); public static Expression GetMappingCallbackOrNull( this IBasicMapperData basicData, diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 362cf0204..a8fccb08a 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -179,8 +179,8 @@ public bool ElementsAreIdentifiable private bool DetermineIfElementsAreIdentifiable() { - if ((Context.SourceElementType == typeof(object)) || - (Context.TargetElementType == typeof(object))) + if ((Context.SourceElementType == typeof(string)) || + (Context.TargetElementType == typeof(string))) { return false; } @@ -190,9 +190,15 @@ private bool DetermineIfElementsAreIdentifiable() { return false; } + + if ((Context.SourceElementType == typeof(object)) || + (Context.TargetElementType == typeof(object))) + { + return false; + } var typeIdsCache = MapperData.MapperContext.Cache.CreateScoped(default(HashCodeComparer)); - var sourceElementId = GetIdentifierOrNull(Context.SourceElementType, _sourceElementParameter, MapperData, typeIdsCache); + var sourceElementId = MapperData.MapperContext.GetIdentifierOrNull(_sourceElementParameter, typeIdsCache); if (sourceElementId == null) { @@ -209,7 +215,7 @@ private bool DetermineIfElementsAreIdentifiable() } var targetElementParameter = Context.TargetElementType.GetOrCreateParameter(); - var targetElementId = GetIdentifierOrNull(Context.TargetElementType, targetElementParameter, MapperData, typeIdsCache); + var targetElementId = MapperData.MapperContext.GetIdentifierOrNull(targetElementParameter, typeIdsCache); if (targetElementId == null) { @@ -222,31 +228,6 @@ private bool DetermineIfElementsAreIdentifiable() return _targetElementIdLambda != null; } - private static Expression GetIdentifierOrNull( - Type type, - Expression parameter, - IMemberMapperData mapperData, - ICache cache) - { - return cache.GetOrAdd(TypeKey.ForTypeId(type), key => - { - var configuredIdentifier = - mapperData.MapperContext.UserConfigurations.Identifiers.GetIdentifierOrNullFor(key.Type); - - if (configuredIdentifier != null) - { - return configuredIdentifier.ReplaceParameterWith(parameter); - } - - var identifier = mapperData - .MapperContext - .Naming - .GetIdentifierOrNull(key.Type); - - return identifier?.GetAccess(parameter); - }); - } - private LambdaExpression GetSourceElementIdLambda( ParameterExpression sourceElement, Expression sourceElementId,