Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ public Task ShouldErrorIfCachedProjectionTargetTypeIsUnconstructableInline()
await context
.Addresses
.ProjectUsing(mapper)
.To<ProductStruct>(cfg => cfg
.To<PublicStringCtorDto>(cfg => cfg
.ThrowNowIfMappingPlanIsIncomplete())
.FirstOrDefaultAsync();
});

validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<ProductStruct>");
validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<PublicStringCtorDto>");
validationEx.Message.ShouldContain("Rule set: Project");
validationEx.Message.ShouldContain("Unconstructable target Types");
validationEx.Message.ShouldContain("Address -> ProductStruct");
validationEx.Message.ShouldContain("Address -> PublicStringCtorDto");
});
}
}
Expand Down
6 changes: 3 additions & 3 deletions AgileMapper.UnitTests.Orms/WhenValidatingProjections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ public Task ShouldErrorIfCachedProjectionTargetTypeIsUnconstructable()
{
return RunTest((context, mapper) =>
{
mapper.GetPlanForProjecting(context.Addresses).To<ProductStruct>();
mapper.GetPlanForProjecting(context.Addresses).To<PublicStringCtorDto>();

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

validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<ProductStruct>");
validationEx.Message.ShouldContain("IQueryable<Address> -> IQueryable<PublicStringCtorDto>");
validationEx.Message.ShouldContain("Rule set: Project");
validationEx.Message.ShouldContain("Unconstructable target Types");
validationEx.Message.ShouldContain("Address -> ProductStruct");
validationEx.Message.ShouldContain("Address -> PublicStringCtorDto");

return Task.CompletedTask;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace AgileObjects.AgileMapper.UnitTests.Configuration
{
using System;
using AgileMapper.Extensions;
using Common;
using TestClasses;
#if !NET35
Expand Down Expand Up @@ -65,5 +66,123 @@ public void ShouldApplyAConfiguredExpressionByParameterName()
result.Value.ShouldBe(222);
}
}

[Fact]
public void ShouldApplyMultipleConfiguredSourceValues()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.From<PublicField<int>>()
.ToANew<CtorTester<int>>()
.If(ctx => ctx.Source.Value < 5)
.Map(0)
.ToCtor<int>()
.But
.If(ctx => ctx.Source.Value < 10)
.Map(5)
.ToCtor<int>()
.But
.If(ctx => ctx.Source.Value < 15)
.Map(10)
.ToCtor<int>();

var lessThenFiveSource = new PublicField<int> { Value = 4 };
var lessthanFiveResult = mapper.Map(lessThenFiveSource).ToANew<CtorTester<int>>();

lessthanFiveResult.Value.ShouldBe(0);

var lessThenTenSource = new PublicField<int> { Value = 8 };
var lessthanTenResult = mapper.Map(lessThenTenSource).ToANew<CtorTester<int>>();

lessthanTenResult.Value.ShouldBe(5);

var lessThenFifteenSource = new PublicField<int> { Value = 11 };
var lessthanFifteenResult = mapper.Map(lessThenFifteenSource).ToANew<CtorTester<int>>();

lessthanFifteenResult.Value.ShouldBe(10);

var moreThanFifteenSource = new PublicField<int> { Value = 123 };
var morethanFifteenResult = mapper.Map(moreThanFifteenSource).ToANew<CtorTester<int>>();

morethanFifteenResult.Value.ShouldBe(123);
}
}

[Fact]
public void ShouldApplyMultipleConfiguredComplexTypeSourceValues()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.From<CtorTester<int>>()
.ToANew<CtorTester<int>>()
.If(ctx => ctx.Source.Value < 5)
.Map(new Address { Line1 = "< 5" })
.ToCtor<Address>()
.But
.If(ctx => ctx.Source.Value < 10)
.Map(new Address { Line1 = "< 10" })
.ToCtor<Address>()
.But
.If(ctx => ctx.Source.Value < 15)
.Map(new Address { Line1 = "< 15" })
.ToCtor<Address>();

var lessThanFiveSource = new CtorTester<int>(3);
var lessThanFiveResult = lessThanFiveSource.DeepCloneUsing(mapper);

lessThanFiveResult.Value.ShouldBe(3);
lessThanFiveResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 5");

var lessThanTenSource = new CtorTester<int>(6);
var lessThanTenResult = lessThanTenSource.DeepCloneUsing(mapper);

lessThanTenResult.Value.ShouldBe(6);
lessThanTenResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 10");

var lessThanFifteenSource = new CtorTester<int>(14);
var lessThanFifteenResult = lessThanFifteenSource.DeepCloneUsing(mapper);

lessThanFifteenResult.Value.ShouldBe(14);
lessThanFifteenResult.Address.ShouldNotBeNull().Line1.ShouldBe("< 15");

var addressSource = new CtorTester<int>(123, new Address { Line1 = "One!", Line2 = "Two!" });
var addressResult = addressSource.DeepCloneUsing(mapper);

addressResult.Value.ShouldBe(123);
addressResult.Address.ShouldNotBeNull().ShouldNotBeSameAs(addressSource.Address);
addressResult.Address.Line1.ShouldBe("One!");
addressResult.Address.Line2.ShouldBe("Two!");

var noAddressSource = new CtorTester<int>(789);
var noAddressResult = noAddressSource.DeepCloneUsing(mapper);

noAddressResult.Value.ShouldBe(789);
noAddressResult.Address.ShouldBeNull();
}
}

#region Helper Classes

private class CtorTester<T>
{
public CtorTester(T value)
{
Value = value;
}

public CtorTester(T value, Address address)
{
Value = value;
Address = address;
}

public T Value { get; }

public Address Address { get; }
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,50 @@ public void ShouldUseAConfiguredFactoryForAnUnconstructableType()
}
}

[Fact]
public void ShouldPrioritiseCreationMethods()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.From<ConstructionTester>()
.ToANew<ConstructionTester>()
.If(ctx => ctx.Source.Value1 < 5)
.CreateInstancesUsing(ctx => new ConstructionTester(100, 100))
.But
.If(ctx => ctx.Source.Value1 < 10)
.CreateInstancesUsing(ctx => new ConstructionTester(500, 500));

var lessThanFiveSource = new ConstructionTester(2);
var lessThanFiveResult = mapper.Map(lessThanFiveSource).ToANew<ConstructionTester>();

lessThanFiveResult.Value1.ShouldBe(100);
lessThanFiveResult.Value2.ShouldBe(100);
lessThanFiveResult.Address.ShouldBeNull();

var lessThanTenSource = new ConstructionTester(8);
var lessThanTenResult = mapper.Map(lessThanTenSource).ToANew<ConstructionTester>();

lessThanTenResult.Value1.ShouldBe(500);
lessThanTenResult.Value2.ShouldBe(500);
lessThanTenResult.Address.ShouldBeNull();

var addressSource = new ConstructionTester(123, 456, new Address { Line1 = "One!" });
var addressResult = mapper.Map(addressSource).ToANew<ConstructionTester>();

addressResult.Value1.ShouldBe(123);
addressResult.Value2.ShouldBe(456);
addressResult.Address.ShouldNotBeNull().Line1.ShouldBe("One!");

var noAddressSource = new ConstructionTester(123, 456);
var noAddressResult = mapper.Map(noAddressSource).ToANew<ConstructionTester>();

noAddressResult.Value1.ShouldBe(123);
noAddressResult.Value2.ShouldBe(456);
noAddressResult.Address.ShouldBeNull();
}
}

[Fact]
public void ShouldHandleAnObjectMappingDataCreationException()
{
Expand Down Expand Up @@ -562,6 +606,7 @@ public class Parent
{
public Status.StatusId ParentStatusId { get; set; }

// ReSharper disable once UnusedMember.Global
public Status ParentStatus => Status.GetStatus(ParentStatusId);
}

Expand All @@ -573,6 +618,38 @@ public class ParentDto
}
}

private class ConstructionTester
{
public ConstructionTester(int value1)
: this(value1, default(int))
{
}

public ConstructionTester(int value1, int value2)
: this(value1, value2, default(Address))
{
}

public ConstructionTester(int value1, int value2, Address address)
{
Value1 = value1;
Value2 = value2;
Address = address;
}

public static ConstructionTester Create(int value1, int value2)
=> new ConstructionTester(value1, value2);

public static ConstructionTester GetInstance(int value1, int value2, Address address)
=> new ConstructionTester(value1, value2, address);

public int Value1 { get; }

public int Value2 { get; }

public Address Address { get; }
}

#endregion
}
}
42 changes: 42 additions & 0 deletions AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace AgileObjects.AgileMapper.UnitTests
{
using System;
using AgileMapper.Extensions;
using Common;
using TestClasses;
#if !NET35
Expand Down Expand Up @@ -112,5 +114,45 @@ public void ShouldHandleAnUnconstructableRootTargetType()

result.ShouldBeNull();
}

[Fact]
public void ShouldConditionallyUseConstructorsWhereArgumentsAreNull()
{
var noAddressSource = new CtorTester("Test 1");
var noAddressResult = noAddressSource.DeepClone();

noAddressResult.Value.ShouldBe("Test 1");
noAddressResult.Address.ShouldBeNull();

var addressSource = new CtorTester("Test 2", new Address { Line1 = "Line 1!" });
var addressResult = addressSource.DeepClone();

addressResult.Value.ShouldBe("Test 2");
addressResult.Address.ShouldNotBeNull();
addressResult.Address.ShouldNotBeSameAs(addressSource.Address);
addressResult.Address.Line1.ShouldBe("Line 1!");
}

#region Helper Classes

private class CtorTester
{
public CtorTester(string value)
{
Value = value;
}

public CtorTester(string value, Address address)
{
Value = value;
Address = address ?? throw new ArgumentNullException(nameof(address));
}

public string Value { get; }

public Address Address { get; }
}

#endregion
}
}
2 changes: 1 addition & 1 deletion AgileMapper/Configuration/PotentialCloneExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static IList<T> CloneItems<T>(this IList<T> cloneableItems)
return clonedItems;
}

public static void AddSorted<T>(this List<T> items, T newItem)
public static void AddSorted<T>(this IList<T> items, T newItem)
where T : IComparable<T>
{
if (items.None())
Expand Down
11 changes: 8 additions & 3 deletions AgileMapper/DataSources/DataSourceSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ internal class DataSourceSet : IEnumerable<IDataSource>
private readonly List<ParameterExpression> _variables;
private Expression _value;

public DataSourceSet(
IMemberMapperData mapperData,
params IDataSource[] dataSources)
public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSources)
{
MapperData = mapperData;
_dataSources = dataSources;
Expand All @@ -39,6 +37,11 @@ public DataSourceSet(
HasValue = true;
}

if (dataSource.IsConditional)
{
IsConditional = true;
}

if (dataSource.Variables.Any())
{
_variables.AddRange(dataSource.Variables);
Expand All @@ -57,6 +60,8 @@ public DataSourceSet(

public bool HasValue { get; }

public bool IsConditional { get; }

public Expression SourceMemberTypeTest { get; }

public ICollection<ParameterExpression> Variables => _variables;
Expand Down
14 changes: 14 additions & 0 deletions AgileMapper/Extensions/Internal/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ public static T First<T>(this IList<T> items, Func<T, bool> predicate)
public static T FirstOrDefault<T>(this IList<T> items, Func<T, bool> predicate)
=> TryFindMatch(items, predicate, out var match) ? match : default(T);

[DebuggerStepThrough]
public static IEnumerable<T> TakeUntil<T>(this IEnumerable<T> items, Func<T, bool> predicate)
{
foreach (var item in items)
{
yield return item;

if (predicate.Invoke(item))
{
yield break;
}
}
}

[DebuggerStepThrough]
public static bool TryFindMatch<T>(this IList<T> items, Func<T, bool> predicate, out T match)
{
Expand Down
1 change: 0 additions & 1 deletion AgileMapper/Members/MemberMapperDataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,6 @@ public static bool TargetMemberIsUnmappable(this IMemberMapperData mapperData, o
}

if (mapperData.TargetMember.LeafMember.HasMatchingCtorParameter &&
mapperData.TargetMember.LeafMember.IsWriteable &&
((mapperData.Parent?.IsRoot != true) ||
!mapperData.RuleSet.Settings.RootHasPopulatedTarget))
{
Expand Down
Loading