Skip to content

Commit ad01ac7

Browse files
authored
Ctor selection improvements (#101)
* Taking complex type argument data source conditions into account when selecting a constructor * Extending test coverage * Test coverage for multiple conditional data sources for a constructor argument * Support for multiple conditional complex type ctor argument data sources * Fixing creation method ordering / Extending test coverage * Fixing projection validation tests
1 parent af79546 commit ad01ac7

File tree

10 files changed

+439
-71
lines changed

10 files changed

+439
-71
lines changed

AgileMapper.UnitTests.Orms.EfCore2/Configuration/Inline/WhenValidatingProjectionsInline.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ public Task ShouldErrorIfCachedProjectionTargetTypeIsUnconstructableInline()
4949
await context
5050
.Addresses
5151
.ProjectUsing(mapper)
52-
.To<ProductStruct>(cfg => cfg
52+
.To<PublicStringCtorDto>(cfg => cfg
5353
.ThrowNowIfMappingPlanIsIncomplete())
5454
.FirstOrDefaultAsync();
5555
});
5656

57-
validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<ProductStruct>");
57+
validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<PublicStringCtorDto>");
5858
validationEx.Message.ShouldContain("Rule set: Project");
5959
validationEx.Message.ShouldContain("Unconstructable target Types");
60-
validationEx.Message.ShouldContain("Address -> ProductStruct");
60+
validationEx.Message.ShouldContain("Address -> PublicStringCtorDto");
6161
});
6262
}
6363
}

AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ public Task ShouldErrorIfCachedProjectionTargetTypeIsUnconstructable()
5353
{
5454
return RunTest((context, mapper) =>
5555
{
56-
mapper.GetPlanForProjecting(context.Addresses).To<ProductStruct>();
56+
mapper.GetPlanForProjecting(context.Addresses).To<PublicStringCtorDto>();
5757

5858
var validationEx = Should.Throw<MappingValidationException>(() =>
5959
mapper.ThrowNowIfAnyMappingPlanIsIncomplete());
6060

61-
validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<ProductStruct>");
61+
validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<PublicStringCtorDto>");
6262
validationEx.Message.ShouldContain("Rule set: Project");
6363
validationEx.Message.ShouldContain("Unconstructable target Types");
64-
validationEx.Message.ShouldContain("Address -> ProductStruct");
64+
validationEx.Message.ShouldContain("Address -> PublicStringCtorDto");
6565

6666
return Task.CompletedTask;
6767
});

AgileMapper.UnitTests/Configuration/WhenConfiguringConstructorDataSources.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Configuration
22
{
33
using System;
4+
using AgileMapper.Extensions;
45
using Common;
56
using TestClasses;
67
#if !NET35
@@ -65,5 +66,123 @@ public void ShouldApplyAConfiguredExpressionByParameterName()
6566
result.Value.ShouldBe(222);
6667
}
6768
}
69+
70+
[Fact]
71+
public void ShouldApplyMultipleConfiguredSourceValues()
72+
{
73+
using (var mapper = Mapper.CreateNew())
74+
{
75+
mapper.WhenMapping
76+
.From<PublicField<int>>()
77+
.ToANew<CtorTester<int>>()
78+
.If(ctx => ctx.Source.Value < 5)
79+
.Map(0)
80+
.ToCtor<int>()
81+
.But
82+
.If(ctx => ctx.Source.Value < 10)
83+
.Map(5)
84+
.ToCtor<int>()
85+
.But
86+
.If(ctx => ctx.Source.Value < 15)
87+
.Map(10)
88+
.ToCtor<int>();
89+
90+
var lessThenFiveSource = new PublicField<int> { Value = 4 };
91+
var lessthanFiveResult = mapper.Map(lessThenFiveSource).ToANew<CtorTester<int>>();
92+
93+
lessthanFiveResult.Value.ShouldBe(0);
94+
95+
var lessThenTenSource = new PublicField<int> { Value = 8 };
96+
var lessthanTenResult = mapper.Map(lessThenTenSource).ToANew<CtorTester<int>>();
97+
98+
lessthanTenResult.Value.ShouldBe(5);
99+
100+
var lessThenFifteenSource = new PublicField<int> { Value = 11 };
101+
var lessthanFifteenResult = mapper.Map(lessThenFifteenSource).ToANew<CtorTester<int>>();
102+
103+
lessthanFifteenResult.Value.ShouldBe(10);
104+
105+
var moreThanFifteenSource = new PublicField<int> { Value = 123 };
106+
var morethanFifteenResult = mapper.Map(moreThanFifteenSource).ToANew<CtorTester<int>>();
107+
108+
morethanFifteenResult.Value.ShouldBe(123);
109+
}
110+
}
111+
112+
[Fact]
113+
public void ShouldApplyMultipleConfiguredComplexTypeSourceValues()
114+
{
115+
using (var mapper = Mapper.CreateNew())
116+
{
117+
mapper.WhenMapping
118+
.From<CtorTester<int>>()
119+
.ToANew<CtorTester<int>>()
120+
.If(ctx => ctx.Source.Value < 5)
121+
.Map(new Address { Line1 = "< 5" })
122+
.ToCtor<Address>()
123+
.But
124+
.If(ctx => ctx.Source.Value < 10)
125+
.Map(new Address { Line1 = "< 10" })
126+
.ToCtor<Address>()
127+
.But
128+
.If(ctx => ctx.Source.Value < 15)
129+
.Map(new Address { Line1 = "< 15" })
130+
.ToCtor<Address>();
131+
132+
var lessThanFiveSource = new CtorTester<int>(3);
133+
var lessThanFiveResult = lessThanFiveSource.DeepCloneUsing(mapper);
134+
135+
lessThanFiveResult.Value.ShouldBe(3);
136+
lessThanFiveResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 5");
137+
138+
var lessThanTenSource = new CtorTester<int>(6);
139+
var lessThanTenResult = lessThanTenSource.DeepCloneUsing(mapper);
140+
141+
lessThanTenResult.Value.ShouldBe(6);
142+
lessThanTenResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 10");
143+
144+
var lessThanFifteenSource = new CtorTester<int>(14);
145+
var lessThanFifteenResult = lessThanFifteenSource.DeepCloneUsing(mapper);
146+
147+
lessThanFifteenResult.Value.ShouldBe(14);
148+
lessThanFifteenResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 15");
149+
150+
var addressSource = new CtorTester<int>(123, new Address { Line1 = "One!", Line2 = "Two!" });
151+
var addressResult = addressSource.DeepCloneUsing(mapper);
152+
153+
addressResult.Value.ShouldBe(123);
154+
addressResult.Address.ShouldNotBeNull().ShouldNotBeSameAs(addressSource.Address);
155+
addressResult.Address.Line1.ShouldBe("One!");
156+
addressResult.Address.Line2.ShouldBe("Two!");
157+
158+
var noAddressSource = new CtorTester<int>(789);
159+
var noAddressResult = noAddressSource.DeepCloneUsing(mapper);
160+
161+
noAddressResult.Value.ShouldBe(789);
162+
noAddressResult.Address.ShouldBeNull();
163+
}
164+
}
165+
166+
#region Helper Classes
167+
168+
private class CtorTester<T>
169+
{
170+
public CtorTester(T value)
171+
{
172+
Value = value;
173+
}
174+
175+
public CtorTester(T value, Address address)
176+
{
177+
Value = value;
178+
Address = address;
179+
}
180+
181+
public T Value { get; }
182+
183+
public Address Address { get; }
184+
}
185+
186+
#endregion
68187
}
69188
}

AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreation.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,50 @@ public void ShouldUseAConfiguredFactoryForAnUnconstructableType()
334334
}
335335
}
336336

337+
[Fact]
338+
public void ShouldPrioritiseCreationMethods()
339+
{
340+
using (var mapper = Mapper.CreateNew())
341+
{
342+
mapper.WhenMapping
343+
.From<ConstructionTester>()
344+
.ToANew<ConstructionTester>()
345+
.If(ctx => ctx.Source.Value1 < 5)
346+
.CreateInstancesUsing(ctx => new ConstructionTester(100, 100))
347+
.But
348+
.If(ctx => ctx.Source.Value1 < 10)
349+
.CreateInstancesUsing(ctx => new ConstructionTester(500, 500));
350+
351+
var lessThanFiveSource = new ConstructionTester(2);
352+
var lessThanFiveResult = mapper.Map(lessThanFiveSource).ToANew<ConstructionTester>();
353+
354+
lessThanFiveResult.Value1.ShouldBe(100);
355+
lessThanFiveResult.Value2.ShouldBe(100);
356+
lessThanFiveResult.Address.ShouldBeNull();
357+
358+
var lessThanTenSource = new ConstructionTester(8);
359+
var lessThanTenResult = mapper.Map(lessThanTenSource).ToANew<ConstructionTester>();
360+
361+
lessThanTenResult.Value1.ShouldBe(500);
362+
lessThanTenResult.Value2.ShouldBe(500);
363+
lessThanTenResult.Address.ShouldBeNull();
364+
365+
var addressSource = new ConstructionTester(123, 456, new Address { Line1 = "One!" });
366+
var addressResult = mapper.Map(addressSource).ToANew<ConstructionTester>();
367+
368+
addressResult.Value1.ShouldBe(123);
369+
addressResult.Value2.ShouldBe(456);
370+
addressResult.Address.ShouldNotBeNull().Line1.ShouldBe("One!");
371+
372+
var noAddressSource = new ConstructionTester(123, 456);
373+
var noAddressResult = mapper.Map(noAddressSource).ToANew<ConstructionTester>();
374+
375+
noAddressResult.Value1.ShouldBe(123);
376+
noAddressResult.Value2.ShouldBe(456);
377+
noAddressResult.Address.ShouldBeNull();
378+
}
379+
}
380+
337381
[Fact]
338382
public void ShouldHandleAnObjectMappingDataCreationException()
339383
{
@@ -562,6 +606,7 @@ public class Parent
562606
{
563607
public Status.StatusId ParentStatusId { get; set; }
564608

609+
// ReSharper disable once UnusedMember.Global
565610
public Status ParentStatus => Status.GetStatus(ParentStatusId);
566611
}
567612

@@ -573,6 +618,38 @@ public class ParentDto
573618
}
574619
}
575620

621+
private class ConstructionTester
622+
{
623+
public ConstructionTester(int value1)
624+
: this(value1, default(int))
625+
{
626+
}
627+
628+
public ConstructionTester(int value1, int value2)
629+
: this(value1, value2, default(Address))
630+
{
631+
}
632+
633+
public ConstructionTester(int value1, int value2, Address address)
634+
{
635+
Value1 = value1;
636+
Value2 = value2;
637+
Address = address;
638+
}
639+
640+
public static ConstructionTester Create(int value1, int value2)
641+
=> new ConstructionTester(value1, value2);
642+
643+
public static ConstructionTester GetInstance(int value1, int value2, Address address)
644+
=> new ConstructionTester(value1, value2, address);
645+
646+
public int Value1 { get; }
647+
648+
public int Value2 { get; }
649+
650+
public Address Address { get; }
651+
}
652+
576653
#endregion
577654
}
578655
}

AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace AgileObjects.AgileMapper.UnitTests
22
{
3+
using System;
4+
using AgileMapper.Extensions;
35
using Common;
46
using TestClasses;
57
#if !NET35
@@ -112,5 +114,45 @@ public void ShouldHandleAnUnconstructableRootTargetType()
112114

113115
result.ShouldBeNull();
114116
}
117+
118+
[Fact]
119+
public void ShouldConditionallyUseConstructorsWhereArgumentsAreNull()
120+
{
121+
var noAddressSource = new CtorTester("Test 1");
122+
var noAddressResult = noAddressSource.DeepClone();
123+
124+
noAddressResult.Value.ShouldBe("Test 1");
125+
noAddressResult.Address.ShouldBeNull();
126+
127+
var addressSource = new CtorTester("Test 2", new Address { Line1 = "Line 1!" });
128+
var addressResult = addressSource.DeepClone();
129+
130+
addressResult.Value.ShouldBe("Test 2");
131+
addressResult.Address.ShouldNotBeNull();
132+
addressResult.Address.ShouldNotBeSameAs(addressSource.Address);
133+
addressResult.Address.Line1.ShouldBe("Line 1!");
134+
}
135+
136+
#region Helper Classes
137+
138+
private class CtorTester
139+
{
140+
public CtorTester(string value)
141+
{
142+
Value = value;
143+
}
144+
145+
public CtorTester(string value, Address address)
146+
{
147+
Value = value;
148+
Address = address ?? throw new ArgumentNullException(nameof(address));
149+
}
150+
151+
public string Value { get; }
152+
153+
public Address Address { get; }
154+
}
155+
156+
#endregion
115157
}
116158
}

AgileMapper/Configuration/PotentialCloneExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static IList<T> CloneItems<T>(this IList<T> cloneableItems)
2121
return clonedItems;
2222
}
2323

24-
public static void AddSorted<T>(this List<T> items, T newItem)
24+
public static void AddSorted<T>(this IList<T> items, T newItem)
2525
where T : IComparable<T>
2626
{
2727
if (items.None())

AgileMapper/DataSources/DataSourceSet.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ internal class DataSourceSet : IEnumerable<IDataSource>
1616
private readonly List<ParameterExpression> _variables;
1717
private Expression _value;
1818

19-
public DataSourceSet(
20-
IMemberMapperData mapperData,
21-
params IDataSource[] dataSources)
19+
public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSources)
2220
{
2321
MapperData = mapperData;
2422
_dataSources = dataSources;
@@ -39,6 +37,11 @@ public DataSourceSet(
3937
HasValue = true;
4038
}
4139

40+
if (dataSource.IsConditional)
41+
{
42+
IsConditional = true;
43+
}
44+
4245
if (dataSource.Variables.Any())
4346
{
4447
_variables.AddRange(dataSource.Variables);
@@ -57,6 +60,8 @@ public DataSourceSet(
5760

5861
public bool HasValue { get; }
5962

63+
public bool IsConditional { get; }
64+
6065
public Expression SourceMemberTypeTest { get; }
6166

6267
public ICollection<ParameterExpression> Variables => _variables;

AgileMapper/Extensions/Internal/EnumerableExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ public static T First<T>(this IList<T> items, Func<T, bool> predicate)
5858
public static T FirstOrDefault<T>(this IList<T> items, Func<T, bool> predicate)
5959
=> TryFindMatch(items, predicate, out var match) ? match : default(T);
6060

61+
[DebuggerStepThrough]
62+
public static IEnumerable<T> TakeUntil<T>(this IEnumerable<T> items, Func<T, bool> predicate)
63+
{
64+
foreach (var item in items)
65+
{
66+
yield return item;
67+
68+
if (predicate.Invoke(item))
69+
{
70+
yield break;
71+
}
72+
}
73+
}
74+
6175
[DebuggerStepThrough]
6276
public static bool TryFindMatch<T>(this IList<T> items, Func<T, bool> predicate, out T match)
6377
{

AgileMapper/Members/MemberMapperDataExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ public static bool TargetMemberIsUnmappable(this IMemberMapperData mapperData, o
180180
}
181181

182182
if (mapperData.TargetMember.LeafMember.HasMatchingCtorParameter &&
183-
mapperData.TargetMember.LeafMember.IsWriteable &&
184183
((mapperData.Parent?.IsRoot != true) ||
185184
!mapperData.RuleSet.Settings.RootHasPopulatedTarget))
186185
{

0 commit comments

Comments
 (0)