Skip to content

Commit 6d59add

Browse files
authored
Auto circular object tracking (#31)
* Auto-tracking mapped objects when circular relationships are detected * Changing TrackMappedObjects to MaintainIdentityIntegrity * Adding DisableObjectTracking configuration option * Test coverage for explicit disabling of mapped object caching * Removing instance mappers from non-configured circular relationship tests * Test coverage for mapping link relationships
1 parent ee7741c commit 6d59add

20 files changed

+429
-247
lines changed

AgileMapper.PerformanceTester/ConcreteMappers/AgileMapper/AgileMapperComplexTypeMapper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ internal class AgileMapperComplexTypeMapper : ComplexTypeMapperBase
77
{
88
public override void Initialise()
99
{
10+
Mapper.WhenMapping.DisableObjectTracking();
1011
}
1112

1213
protected override Foo Clone(Foo foo)

AgileMapper.UnitTests/Configuration/WhenConfiguringExceptionHandling.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void ShouldRestrictACallbackBySourceAndTargetType()
161161
.To<Person>()
162162
.PassExceptionsTo(ctx => thrownException = ctx.Exception)
163163
.And
164-
.TrackMappedObjects();
164+
.MaintainIdentityIntegrity();
165165

166166
mapper.After
167167
.CreatingInstances

AgileMapper.UnitTests/WhenMappingCircularReferences.cs

Lines changed: 250 additions & 180 deletions
Large diffs are not rendered by default.

AgileMapper.UnitTests/WhenMappingToNewEnumerables.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void ShouldCreateAComplexTypeArrayUsingRuntimeTypedElements()
7676
{
7777
using (var mapper = Mapper.CreateNew())
7878
{
79-
mapper.WhenMapping.TrackMappedObjects();
79+
mapper.WhenMapping.MaintainIdentityIntegrity();
8080

8181
var viewModel = new CustomerViewModel { Name = "Dave", AddressLine1 = "View model!" };
8282
var source = new List<PersonViewModel> { viewModel, viewModel };

AgileMapper.UnitTests/WhenViewingMappingPlans.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,6 @@ public void ShouldShowObjectTracking()
315315
{
316316
using (var mapper = Mapper.CreateNew())
317317
{
318-
mapper.WhenMapping.TrackMappedObjects();
319-
320318
string plan = mapper.GetPlanFor<Parent>().ToANew<Parent>();
321319

322320
plan.ShouldContain("pToPData.TryGet(sourceChild.EldestParent, out parent)");

AgileMapper/Api/Configuration/IFullMappingSettings.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,30 @@ public interface IFullMappingSettings<TSource, TTarget> : IConditionalMappingCon
3535
IFullMappingSettings<TSource, TTarget> PassExceptionsTo(Action<IMappingExceptionData<TSource, TTarget>> callback);
3636

3737
/// <summary>
38-
/// Keep track of objects during a mapping from and to the source and target types being configured, in order
39-
/// to short-circuit circular relationships and ensure 1-to-1 relationships between source and mapped objects.
38+
/// Ensure 1-to-1 relationships between source and mapped objects during a mapping from and to the source and
39+
/// target types being configured, by tracking and reusing mapped objects if they appear more than once in a
40+
/// source object tree. Mapped objects are automatically tracked in object trees with circular relationships -
41+
/// unless <see cref="DisableObjectTracking"/> is called - so configuring this option is not necessary just to
42+
/// map circular relationships.
4043
/// </summary>
4144
/// <returns>
42-
/// An IFullMappingSettings{TSource, TTarget} with which to configure further settings for the source and
43-
/// target types being configured.
45+
/// An <see cref="IFullMappingSettings{TSource, TTarget}"/> with which to configure further settings for the source
46+
/// and target types being configured.
47+
/// </returns>
48+
IFullMappingSettings<TSource, TTarget> MaintainIdentityIntegrity();
49+
50+
/// <summary>
51+
/// Disable tracking of objects during circular relationship mapping from and to the source and target types
52+
/// being configured. Mapped objects are tracked by default when mapping circular relationships to prevent stack
53+
/// overflows if two objects in a source object tree hold references to each other, and to ensure 1-to-1 relationships
54+
/// between source and mapped objects. If you are confident that each object in a source object tree appears
55+
/// only once, disabling object tracking will increase mapping performance.
56+
/// </summary>
57+
/// <returns>
58+
/// An <see cref="IFullMappingSettings{TSource, TTarget}"/> with which to configure further settings for the source
59+
/// and target types being configured.
4460
/// </returns>
45-
IFullMappingSettings<TSource, TTarget> TrackMappedObjects();
61+
IFullMappingSettings<TSource, TTarget> DisableObjectTracking();
4662

4763
/// <summary>
4864
/// Map null source collections to null instead of an empty collection, for the source and target types

AgileMapper/Api/Configuration/IGlobalConfigSettings.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,27 @@ public interface IGlobalConfigSettings
105105
#endregion
106106

107107
/// <summary>
108-
/// Keep track of objects during mappings between all source and target types, in order to short-circuit
109-
/// circular relationships and ensure a 1-to-1 relationship between source and mapped objects.
108+
/// Ensure 1-to-1 relationships between source and mapped objects by tracking and reusing mapped objects if
109+
/// they appear more than once in a source object tree. Mapped objects are automatically tracked in object
110+
/// trees with circular relationships - unless <see cref="DisableObjectTracking"/> is called - so configuring
111+
/// this option is not necessary just to map circular relationships.
110112
/// </summary>
111113
/// <returns>
112114
/// An <see cref="IGlobalConfigSettings"/> with which to globally configure other mapping aspects.
113115
/// </returns>
114-
IGlobalConfigSettings TrackMappedObjects();
116+
IGlobalConfigSettings MaintainIdentityIntegrity();
117+
118+
/// <summary>
119+
/// Disable tracking of objects during circular relationship mapping between all source and target types.
120+
/// Mapped objects are tracked by default when mapping circular relationships to prevent stack overflows
121+
/// if two objects in a source object tree hold references to each other, and to ensure 1-to-1 relationships
122+
/// between source and mapped objects. If you are confident that each object in a source object tree appears
123+
/// only once, disabling object tracking will increase mapping performance.
124+
/// </summary>
125+
/// <returns>
126+
/// An <see cref="IGlobalConfigSettings"/> with which to globally configure other mapping aspects.
127+
/// </returns>
128+
IGlobalConfigSettings DisableObjectTracking();
115129

116130
/// <summary>
117131
/// Map null source collections to null instead of an empty collection, for all source and target types.

AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,33 @@ private IGlobalConfigSettings UseNamePatterns(IEnumerable<string> patterns)
201201
#endregion
202202

203203
/// <summary>
204-
/// Keep track of objects during mappings between all source and target types, in order to short-circuit
205-
/// circular relationships and ensure a 1-to-1 relationship between source and mapped objects.
204+
/// Ensure 1-to-1 relationships between source and mapped objects by tracking and reusing mapped objects if
205+
/// they appear more than once in a source object tree. Mapped objects are automatically tracked in object
206+
/// trees with circular relationships - unless <see cref="DisableObjectTracking"/> is called - so configuring
207+
/// this option is not necessary when mapping circular relationships.
206208
/// </summary>
207209
/// <returns>
208-
/// This <see cref="IGlobalConfigSettings"/> with which to globally configure other mapping aspects.
210+
/// An <see cref="IGlobalConfigSettings"/> with which to globally configure other mapping aspects.
211+
/// </returns>
212+
public IGlobalConfigSettings MaintainIdentityIntegrity()
213+
{
214+
_mapperContext.UserConfigurations.Add(MappedObjectCachingSettings.CacheAll(_mapperContext));
215+
return this;
216+
}
217+
218+
/// <summary>
219+
/// Disable tracking of objects during circular relationship mapping between all source and target types.
220+
/// Mapped objects are tracked by default when mapping circular relationships to prevent stack overflows
221+
/// if two objects in a source object tree hold references to each other, and to ensure 1-to-1 relationships
222+
/// between source and mapped objects. If you are confident that each object in a source object tree appears
223+
/// only once, disabling object tracking will increase mapping performance.
224+
/// </summary>
225+
/// <returns>
226+
/// An <see cref="IGlobalConfigSettings"/> with which to globally configure other mapping aspects.
209227
/// </returns>
210-
public IGlobalConfigSettings TrackMappedObjects()
228+
public IGlobalConfigSettings DisableObjectTracking()
211229
{
212-
_mapperContext.UserConfigurations.Add(ObjectTrackingMode.TrackAll(_mapperContext));
230+
_mapperContext.UserConfigurations.Add(MappedObjectCachingSettings.CacheNone(_mapperContext));
213231
return this;
214232
}
215233

AgileMapper/Api/Configuration/MappingConfigurator.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,15 @@ public IFullMappingSettings<TSource, TTarget> PassExceptionsTo(Action<IMappingEx
6666
return this;
6767
}
6868

69-
public IFullMappingSettings<TSource, TTarget> TrackMappedObjects()
69+
public IFullMappingSettings<TSource, TTarget> MaintainIdentityIntegrity() => SetMappedObjectCaching(cache: true);
70+
71+
public IFullMappingSettings<TSource, TTarget> DisableObjectTracking() => SetMappedObjectCaching(cache: false);
72+
73+
private IFullMappingSettings<TSource, TTarget> SetMappedObjectCaching(bool cache)
7074
{
71-
var trackingMode = new ObjectTrackingMode(ConfigInfo.ForTargetType<TTarget>());
75+
var settings = new MappedObjectCachingSettings(ConfigInfo.ForTargetType<TTarget>(), cache);
7276

73-
ConfigInfo.MapperContext.UserConfigurations.Add(trackingMode);
77+
ConfigInfo.MapperContext.UserConfigurations.Add(settings);
7478
return this;
7579
}
7680

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace AgileObjects.AgileMapper.Configuration
2+
{
3+
internal class MappedObjectCachingSettings : UserConfiguredItemBase
4+
{
5+
public MappedObjectCachingSettings(MappingConfigInfo configInfo, bool cache)
6+
: base(configInfo)
7+
{
8+
Cache = cache;
9+
}
10+
11+
#region Factory Methods
12+
13+
public static MappedObjectCachingSettings CacheAll(MapperContext mapperContext)
14+
=> new MappedObjectCachingSettings(ForAllMappings(mapperContext), cache: true);
15+
16+
public static MappedObjectCachingSettings CacheNone(MapperContext mapperContext)
17+
=> new MappedObjectCachingSettings(ForAllMappings(mapperContext), cache: false);
18+
19+
private static MappingConfigInfo ForAllMappings(MapperContext mapperContext)
20+
=> MappingConfigInfo.AllRuleSetsSourceTypesAndTargetTypes(mapperContext);
21+
22+
#endregion
23+
24+
public bool Cache { get; }
25+
}
26+
}

0 commit comments

Comments
 (0)