Skip to content

Commit 2030dda

Browse files
committed
Lazy-loading recursion mapper funcs deemed necessary during execution of a recursion mapper func; fixes issue #62
1 parent 652edae commit 2030dda

File tree

13 files changed

+96
-36
lines changed

13 files changed

+96
-36
lines changed

AgileMapper.UnitTests/WhenMappingCircularReferences.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,42 @@ public void ShouldMapNestedMultiplyRecursiveRelationships()
391391
resultTwo.ChildRecursor.ShouldBeSameAs(resultOne);
392392
}
393393

394+
// See https://github.com/agileobjects/AgileMapper/issues/62
395+
[Fact]
396+
public void ShouldMapChildOneToManyRecursiveRelationships()
397+
{
398+
var sourceLocation1 = new Location { Id = 1 };
399+
var sourceLocation2 = new Location { Id = 2 };
400+
401+
sourceLocation1.LocationPlace = sourceLocation2.LocationPlace = new Place
402+
{
403+
Locations = new[] { sourceLocation1, sourceLocation2 }
404+
};
405+
406+
var source = new[] { sourceLocation1, sourceLocation2 };
407+
408+
var result = Mapper
409+
.Map<IEnumerable<Location>>(source)
410+
.ToANew<IEnumerable<DtoLocation>>()
411+
.ToArray();
412+
413+
result.Length.ShouldBe(2);
414+
415+
var dtoLocation1 = result.First();
416+
dtoLocation1.Id.ShouldBe(1);
417+
dtoLocation1.LocationPlace.ShouldNotBeNull();
418+
419+
var dtoLocation2 = result.Second();
420+
dtoLocation2.Id.ShouldBe(2);
421+
dtoLocation2.LocationPlace.ShouldNotBeNull();
422+
dtoLocation2.LocationPlace.Locations.Count().ShouldBe(2);
423+
424+
dtoLocation2.LocationPlace.ShouldBeSameAs(dtoLocation1.LocationPlace);
425+
dtoLocation2.LocationPlace.Locations.Count().ShouldBe(2);
426+
dtoLocation2.LocationPlace.Locations.First().ShouldBe(dtoLocation1);
427+
dtoLocation2.LocationPlace.Locations.Second().ShouldBe(dtoLocation2);
428+
}
429+
394430
[Fact]
395431
public void ShouldUseConfiguredRecursiveDataSources()
396432
{
@@ -547,6 +583,30 @@ internal class SubjectPresenter
547583
public Presenter Presenter { get; set; }
548584
}
549585

586+
public class Location
587+
{
588+
public int Id { get; set; }
589+
590+
public Place LocationPlace { get; set; }
591+
}
592+
593+
public class Place
594+
{
595+
public IEnumerable<Location> Locations { get; set; }
596+
}
597+
598+
public class DtoLocation
599+
{
600+
public int Id { get; set; }
601+
602+
public DtoPlace LocationPlace { get; set; }
603+
}
604+
605+
public class DtoPlace
606+
{
607+
public IEnumerable<DtoLocation> Locations { get; set; }
608+
}
609+
550610
#endregion
551611
}
552612
}

AgileMapper/Caching/ReferenceEqualsComparer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ internal struct ReferenceEqualsComparer<T> : IEqualityComparer<T>
66
{
77
public bool Equals(T x, T y) => ReferenceEquals(x, y);
88

9-
public int GetHashCode(T obj) => 0;
9+
public int GetHashCode(T obj) => obj.GetHashCode();
1010
}
1111
}

AgileMapper/Extensions/Internal/ExpressionExtensions.Replace.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Linq.Expressions;
7+
using Caching;
78
using static System.Linq.Expressions.ExpressionType;
89

910
internal static partial class ExpressionExtensions
@@ -55,7 +56,7 @@ public static TExpression Replace<TExpression>(
5556
return new ExpressionReplacer(
5657
target,
5758
replacement,
58-
comparer ?? ReferenceEqualsEqualityComparer.Instance)
59+
comparer ?? default(ReferenceEqualsComparer<Expression>))
5960
.Replace<TExpression>(expression);
6061
}
6162

@@ -348,15 +349,5 @@ private Expression ReplaceIn<TExpression>(TExpression expression, Func<TExpressi
348349
return replacer.Invoke(expression);
349350
}
350351
}
351-
352-
private class ReferenceEqualsEqualityComparer : IEqualityComparer<Expression>
353-
{
354-
public static readonly IEqualityComparer<Expression> Instance =
355-
new ReferenceEqualsEqualityComparer();
356-
357-
public bool Equals(Expression x, Expression y) => x == y;
358-
359-
public int GetHashCode(Expression obj) => obj.GetHashCode();
360-
}
361352
}
362353
}

AgileMapper/Members/MemberMapperDataExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ public static bool UseMemberInitialisations(this IMemberMapperData mapperData)
4646
public static bool MapToNullCollections(this IMemberMapperData mapperData)
4747
=> mapperData.MapperContext.UserConfigurations.MapToNullCollections(mapperData);
4848

49-
public static IMemberMapperData GetRootMapperData(this IMemberMapperData mapperData)
49+
public static ObjectMapperData GetRootMapperData(this IMemberMapperData mapperData)
5050
{
5151
while (!mapperData.IsRoot)
5252
{
5353
mapperData = mapperData.Parent;
5454
}
5555

56-
return mapperData;
56+
return (ObjectMapperData)mapperData;
5757
}
5858

5959
public static IBasicMapperData GetElementMapperData(this IMemberMapperData mapperData)

AgileMapper/ObjectPopulation/IObjectMapper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ internal interface IObjectMapper : IObjectMapperFunc
1212

1313
IEnumerable<IRecursionMapperFunc> RecursionMapperFuncs { get; }
1414

15+
void CacheRecursionMapperFuncs();
16+
1517
bool IsStaticallyCacheable();
1618

1719
void Reset();

AgileMapper/ObjectPopulation/MapperCreationCallbackKey.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation
66

77
internal class MapperCreationCallbackKey
88
{
9-
public static readonly IEqualityComparer<MapperCreationCallbackKey> Comparer = new KeyComparer();
10-
119
private readonly MappingRuleSet _ruleSet;
1210
private readonly Type _sourceType;
1311
private readonly Type _targetType;
@@ -27,7 +25,7 @@ public MapperCreationCallbackKey(
2725
_targetType = targetType;
2826
}
2927

30-
private class KeyComparer : IEqualityComparer<MapperCreationCallbackKey>
28+
public struct Comparer : IEqualityComparer<MapperCreationCallbackKey>
3129
{
3230
public bool Equals(MapperCreationCallbackKey x, MapperCreationCallbackKey y)
3331
{

AgileMapper/ObjectPopulation/MapperKeys/ObjectMapperKeyBase.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
namespace AgileObjects.AgileMapper.ObjectPopulation.MapperKeys
22
{
3-
using Members;
43
using Members.Sources;
54

65
internal abstract class ObjectMapperKeyBase : SourceMemberTypeDependentKeyBase, ITypedMapperKey

AgileMapper/ObjectPopulation/MapperKeys/RootMapperKeyComparer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ public bool Equals(IRootMapperKey x, IRootMapperKey y)
1111
// ReSharper restore PossibleNullReferenceException
1212
}
1313

14+
#region ExcludeFromCodeCoverage
15+
#if DEBUG
16+
[ExcludeFromCodeCoverage]
17+
#endif
18+
#endregion
1419
public int GetHashCode(IRootMapperKey obj) => 0;
1520
}
1621
}

AgileMapper/ObjectPopulation/ObjectMapper.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,20 @@ public ObjectMapper(
4040

4141
if (MapperData.HasMapperFuncs)
4242
{
43-
_recursionMapperFuncsByKey = CreateRecursionMapperFuncsCache(mappingData);
43+
_recursionMapperFuncsByKey = MapperData.MapperContext.Cache.CreateNew<ObjectMapperKeyBase, IRecursionMapperFunc>();
44+
MapperData.Mapper = this;
45+
46+
CacheRecursionMapperFuncs();
4447
}
4548
}
4649

4750
#region Setup
4851

49-
private ICache<ObjectMapperKeyBase, IRecursionMapperFunc> CreateRecursionMapperFuncsCache(
50-
IObjectMappingData mappingData)
52+
public void CacheRecursionMapperFuncs()
5153
{
52-
var cache = MapperData.MapperContext.Cache.CreateNew<ObjectMapperKeyBase, IRecursionMapperFunc>();
53-
54-
for (var i = 0; i < MapperData.RequiredMapperFuncKeys.Count; i++)
54+
// Using a for loop here because creation of a recursion mapper func can
55+
// cause additions to MapperData.RequiredMapperFuncKeys
56+
for (var i = _recursionMapperFuncsByKey.Count; i < MapperData.RequiredMapperFuncKeys.Count; i++)
5557
{
5658
var mapperKey = MapperData.RequiredMapperFuncKeys[i];
5759

@@ -80,12 +82,10 @@ private ICache<ObjectMapperKeyBase, IRecursionMapperFunc> CreateRecursionMapperF
8082

8183
var mapperFunc = mapperFuncCreator.Invoke(
8284
mapperKey.MappingData,
83-
mappingData.MappingContext.LazyLoadRecursionMappingFuncs);
85+
mapperKey.MappingData.MappingContext.LazyLoadRecursionMappingFuncs);
8486

85-
cache.GetOrAdd(mapperKey, k => mapperFunc);
87+
_recursionMapperFuncsByKey.GetOrAdd(mapperKey, k => mapperFunc);
8688
}
87-
88-
return cache;
8989
}
9090

9191
#endregion

AgileMapper/ObjectPopulation/ObjectMapperFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void RegisterCreationCallback(MapperCreationCallbackKey creationCallbackK
3737
if (_creationCallbacksByKey == null)
3838
{
3939
_creationCallbacksByKey =
40-
new Dictionary<MapperCreationCallbackKey, Action<IObjectMapper>>(MapperCreationCallbackKey.Comparer);
40+
new Dictionary<MapperCreationCallbackKey, Action<IObjectMapper>>(default(MapperCreationCallbackKey.Comparer));
4141
}
4242

4343
_creationCallbacksByKey.Add(creationCallbackKey, callback);

0 commit comments

Comments
 (0)