Skip to content

Commit efea0be

Browse files
committed
Removing projection mappers from static mapper caching
Organising Mapper key classes Removing need for RootObjectMapperKey cache Changing stateless classes to structs where possible Using KeyComparers in ArrayCache Lazy-loading MappingDataFactory MethodInfos Removing ObjectMapper.Unmappable Extending unmappable type test coverage Skipping null-checks of some Linq operation results Propogating source member type checks into complex and enumerable type data sources Removing unused 'repeat mapping' code
1 parent 91e6678 commit efea0be

File tree

62 files changed

+530
-383
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+530
-383
lines changed

AgileMapper.UnitTests.Orms.EfCore2/WhenProjectingFlatTypes.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Orms.EfCore2
22
{
3+
using System;
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Linq.Expressions;
38
using System.Threading.Tasks;
49
using Infrastructure;
10+
using Microsoft.EntityFrameworkCore;
11+
using MoreTestClasses;
512
using Orms;
13+
using TestClasses;
614
using Xunit;
715

816
public class WhenProjectingFlatTypes : WhenProjectingFlatTypes<EfCore2TestDbContext>
@@ -14,5 +22,67 @@ public WhenProjectingFlatTypes(InMemoryEfCore2TestContext context)
1422

1523
[Fact]
1624
public Task ShouldProjectStructCtorParameters() => RunShouldProjectStructCtorParameters();
25+
26+
[Fact]
27+
public Task ShouldVaryMappersByProviderType()
28+
{
29+
return RunTest(async (context, mapper) =>
30+
{
31+
await context.BoolItems.AddAsync(new PublicBool { Value = true });
32+
await context.SaveChangesAsync();
33+
34+
await context
35+
.BoolItems
36+
.ProjectUsing(mapper).To<PublicBoolDto>()
37+
.FirstOrDefaultAsync();
38+
39+
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
40+
new PublicBoolQueryable()
41+
.ProjectUsing(mapper).To<PublicBoolDto>()
42+
.FirstOrDefault();
43+
44+
mapper.RootMapperCountShouldBe(2);
45+
});
46+
}
47+
48+
#region Helper Class
49+
50+
private class PublicBoolQueryable : IQueryable<PublicBool>
51+
{
52+
private readonly PublicBoolProvider _provider;
53+
private readonly IQueryable<PublicBool> _values;
54+
55+
public PublicBoolQueryable()
56+
{
57+
_provider = new PublicBoolProvider();
58+
_values = new PublicBool[0].AsQueryable();
59+
}
60+
61+
IEnumerator<PublicBool> IEnumerable<PublicBool>.GetEnumerator()
62+
=> _values.GetEnumerator();
63+
64+
public IEnumerator GetEnumerator() => _values.GetEnumerator();
65+
66+
public Expression Expression => Expression.Constant(_values);
67+
68+
public Type ElementType => typeof(PublicBool);
69+
70+
public IQueryProvider Provider => _provider;
71+
}
72+
73+
private class PublicBoolProvider : IQueryProvider
74+
{
75+
public IQueryable CreateQuery(Expression expression)
76+
=> Enumerable.Empty<PublicBool>().AsQueryable();
77+
78+
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
79+
=> Enumerable.Empty<PublicBool>().Cast<TElement>().AsQueryable();
80+
81+
public object Execute(Expression expression) => default(PublicBool);
82+
83+
public TResult Execute<TResult>(Expression expression) => default(TResult);
84+
}
85+
86+
#endregion
1787
}
1888
}

AgileMapper.UnitTests/WhenMappingToNewComplexTypeMembers.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace AgileObjects.AgileMapper.UnitTests
22
{
3+
using System.Collections.Generic;
34
using TestClasses;
45
using Xunit;
56

@@ -281,6 +282,58 @@ public void ShouldHandleANullUnconstructableNestedMember()
281282
result.Value.ShouldBeNull();
282283
}
283284

285+
[Fact]
286+
public void ShouldHandleAnUnconstructableRuntimeTypedChildMember()
287+
{
288+
var result = Mapper
289+
.Map(new { Value = (object)new { Test = "Nah" } })
290+
.ToANew<PublicField<PublicCtor<int>>>();
291+
292+
result.ShouldNotBeNull();
293+
result.Value.ShouldBeNull();
294+
}
295+
296+
[Fact]
297+
public void ShouldHandleRuntimeTypedComplexAndEnumerableChildMembers()
298+
{
299+
using (var mapper = Mapper.CreateNew())
300+
{
301+
var intArraySource = new PublicTwoFields<object, object>
302+
{
303+
Value1 = new Product { ProductId = "kijerf" },
304+
Value2 = new[] { 1, 2, 3 }
305+
};
306+
307+
var intArrayResult = mapper
308+
.Map(intArraySource)
309+
.ToANew<PublicTwoFields<ProductDto, List<int?>>>();
310+
311+
intArrayResult.ShouldNotBeNull();
312+
313+
intArrayResult.Value1.ShouldNotBeNull();
314+
intArrayResult.Value1.ProductId.ShouldBe("kijerf");
315+
316+
intArrayResult.Value2.ShouldBe(1, 2, 3);
317+
318+
var stringArraySource = new PublicTwoFields<object, object>
319+
{
320+
Value1 = new Product { ProductId = "kdjhdgs" },
321+
Value2 = new[] { "3", "2", "1" }
322+
};
323+
324+
var stringArrayResult = mapper
325+
.Map(stringArraySource)
326+
.ToANew<PublicTwoFields<ProductDto, List<int?>>>();
327+
328+
stringArrayResult.ShouldNotBeNull();
329+
330+
stringArrayResult.Value1.ShouldNotBeNull();
331+
stringArrayResult.Value1.ProductId.ShouldBe("kdjhdgs");
332+
333+
stringArrayResult.Value2.ShouldBe(3, 2, 1);
334+
}
335+
}
336+
284337
#region Helper Classes
285338

286339
private class Country

AgileMapper.UnitTests/WhenMappingToNewComplexTypes.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,13 @@ public void ShouldMapFromAnInterface()
8484

8585
result.Value.ShouldBe("Interfaces!");
8686
}
87+
88+
[Fact]
89+
public void ShouldHandleAnUnconstructableRootTargetType()
90+
{
91+
var result = Mapper.Map(new { Test = "Nope" }).ToANew<PublicCtor<int>>();
92+
93+
result.ShouldBeNull();
94+
}
8795
}
8896
}

AgileMapper.UnitTests/WhenViewingMappingPlans.cs

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
public class WhenViewingMappingPlans
1212
{
1313
[Fact]
14-
public void ShouldIncludeASimpleTypeMemberMapping()
14+
public void ShouldShowASimpleTypeMemberMapping()
1515
{
1616
string plan = Mapper
1717
.GetPlanFor<PublicField<string>>()
@@ -96,7 +96,7 @@ public void ShouldSupportStructsFromTheInstanceApi()
9696
}
9797

9898
[Fact]
99-
public void ShouldIncludeAComplexTypeMemberMapping()
99+
public void ShouldShowAComplexTypeMemberMapping()
100100
{
101101
string plan = Mapper
102102
.GetPlanFor<PersonViewModel>()
@@ -107,7 +107,7 @@ public void ShouldIncludeAComplexTypeMemberMapping()
107107
}
108108

109109
[Fact]
110-
public void ShouldIncludeASimpleTypeEnumerableMemberMapping()
110+
public void ShouldShowASimpleTypeEnumerableMemberMapping()
111111
{
112112
string plan = Mapper
113113
.GetPlanFor<PublicProperty<int[]>>()
@@ -121,7 +121,7 @@ public void ShouldIncludeASimpleTypeEnumerableMemberMapping()
121121
}
122122

123123
[Fact]
124-
public void ShouldIncludeASimpleTypeMemberConversion()
124+
public void ShouldShowASimpleTypeMemberConversion()
125125
{
126126
string plan = Mapper
127127
.GetPlanFor<PublicProperty<Guid>>()
@@ -131,7 +131,7 @@ public void ShouldIncludeASimpleTypeMemberConversion()
131131
}
132132

133133
[Fact]
134-
public void ShouldIncludeARootComplexTypeEnumerableMapping()
134+
public void ShouldShowARootComplexTypeEnumerableMapping()
135135
{
136136
string plan = Mapper
137137
.GetPlanFor<IEnumerable<Person>>()
@@ -142,7 +142,7 @@ public void ShouldIncludeARootComplexTypeEnumerableMapping()
142142
}
143143

144144
[Fact]
145-
public void ShouldIncludeAComplexTypeEnumerableMemberMapping()
145+
public void ShouldShowAComplexTypeEnumerableMemberMapping()
146146
{
147147
string plan = Mapper
148148
.GetPlanFor<IList<PersonViewModel>>()
@@ -158,7 +158,7 @@ public void ShouldIncludeAComplexTypeEnumerableMemberMapping()
158158
}
159159

160160
[Fact]
161-
public void ShouldIncludeAMemberWithNoDataSource()
161+
public void ShouldShowAMemberWithNoDataSource()
162162
{
163163
string plan = Mapper
164164
.GetPlanFor<PersonViewModel>()
@@ -265,6 +265,41 @@ public void ShouldNotRangeCheckNullableToNonNullableValues()
265265
plan.ShouldNotContain("Value.HasValue");
266266
}
267267

268+
[Fact]
269+
public void ShouldNotNullCheckStringSplitCallResults()
270+
{
271+
using (var mapper = Mapper.CreateNew())
272+
{
273+
mapper.WhenMapping
274+
.From<PublicField<string>>()
275+
.ToANew<PublicField<string[]>>()
276+
.Map((i, l) => i.Value.Split(new[] { ',' }))
277+
.To(l => l.Value);
278+
279+
string plan = mapper.GetPlanFor<PublicField<string>>().ToANew<PublicField<string[]>>();
280+
281+
plan.ShouldNotContain("Value.Split(',') != null");
282+
}
283+
}
284+
285+
[Fact]
286+
public void ShouldNotNullCheckLinqMethodCallResults()
287+
{
288+
using (var mapper = Mapper.CreateNew())
289+
{
290+
mapper.WhenMapping
291+
.From<PublicField<int[]>>()
292+
.ToANew<PublicField<long[]>>()
293+
.Map((i, l) => i.Value.Select(v => v * 2).ToArray())
294+
.To(l => l.Value);
295+
296+
string plan = mapper.GetPlanFor<PublicField<int[]>>().ToANew<PublicField<long[]>>();
297+
298+
plan.ShouldNotContain("Select(v => v * 2) != null");
299+
plan.ShouldNotContain("ToArray() != null");
300+
}
301+
}
302+
268303
[Fact]
269304
public void ShouldNotAttemptUnnecessaryObjectCreationCallbacks()
270305
{
@@ -315,15 +350,15 @@ public void ShouldNotAssignATargetMemberToItself()
315350
}
316351

317352
[Fact]
318-
public void ShouldIncludeUnmappableEntityKeyMemberDetails()
353+
public void ShouldShowUnmappableEntityKeyMemberDetails()
319354
{
320355
string plan = Mapper.GetPlanFor<OrderDto>().ToANew<OrderEntity>();
321356

322357
plan.ShouldContain("Entity key member");
323358
}
324359

325360
[Fact]
326-
public void ShouldIncludeUnmappableStructComplexTypeMemberDetails()
361+
public void ShouldShowUnmappableStructComplexTypeMemberDetails()
327362
{
328363
using (var mapper = Mapper.CreateNew())
329364
{
@@ -337,7 +372,7 @@ public void ShouldIncludeUnmappableStructComplexTypeMemberDetails()
337372
}
338373

339374
[Fact]
340-
public void ShouldIncludeUnmappableNoChildDataSourcesComplexTypeMemberDetails()
375+
public void ShouldShowUnmappableNoChildDataSourcesComplexTypeMemberDetails()
341376
{
342377
string plan = Mapper
343378
.GetPlanFor(new { Int = default(int) })

AgileMapper/Caching/ArrayCache.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@ internal class ArrayCache<TKey, TValue> : ICache<TKey, TValue>
77
{
88
private const int DefaultCapacity = 10;
99
private readonly object _keyLock = new object();
10+
private readonly IEqualityComparer<TKey> _keyComparer;
1011

1112
private TKey[] _keys;
1213
private TValue[] _values;
1314
private int _capacity;
1415
private int _length;
1516

16-
public ArrayCache(int capacity = DefaultCapacity)
17+
public ArrayCache(IEqualityComparer<TKey> keyComparer)
1718
{
18-
_capacity = capacity;
19+
_capacity = DefaultCapacity;
1920
_length = 0;
20-
_keys = new TKey[capacity];
21-
_values = new TValue[capacity];
21+
_keys = new TKey[DefaultCapacity];
22+
_values = new TValue[DefaultCapacity];
23+
_keyComparer = keyComparer ?? default(DefaultComparer<TKey>);
2224
}
2325

2426
KeyValuePair<TKey, TValue> ICache<TKey, TValue>.this[int index]
@@ -78,7 +80,7 @@ private bool TryGetValue(TKey key, int startIndex, out TValue value)
7880
{
7981
var thisKey = _keys[i];
8082

81-
if (ReferenceEquals(thisKey, key) || thisKey.Equals(key))
83+
if (_keyComparer.Equals(thisKey, key))
8284
{
8385
value = _values[i];
8486
return true;

AgileMapper/Caching/CacheSet.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
namespace AgileObjects.AgileMapper.Caching
22
{
33
using System;
4+
using System.Collections.Generic;
45

56
internal class CacheSet
67
{
78
private readonly ICache<Type, ICache> _cachesByType;
89

910
public CacheSet()
1011
{
11-
_cachesByType = CreateNew<Type, ICache>();
12+
_cachesByType = CreateNew<Type, ICache>(default(ReferenceEqualsComparer<Type>));
1213
}
1314

1415
public TValue GetOrAdd<TKey, TValue>(TKey key, Func<TKey, TValue> valueFactory)
1516
=> CreateScoped<TKey, TValue>().GetOrAdd(key, valueFactory);
1617

17-
public ICache<TKey, TValue> CreateScoped<TKey, TValue>()
18+
public ICache<TKey, TValue> CreateScoped<TKey, TValue>(IEqualityComparer<TKey> keyComparer = null)
1819
{
1920
var cache = _cachesByType.GetOrAdd(
2021
typeof(ICache<TKey, TValue>),
21-
t => CreateNew<TKey, TValue>());
22+
t => CreateNew<TKey, TValue>(keyComparer));
2223

2324
return (ICache<TKey, TValue>)cache;
2425
}
2526

26-
public ICache<TKey, TValue> CreateNew<TKey, TValue>() => new ArrayCache<TKey, TValue>();
27+
public ICache<TKey, TValue> CreateNew<TKey, TValue>(IEqualityComparer<TKey> keyComparer = null)
28+
=> new ArrayCache<TKey, TValue>(keyComparer);
2729

2830
public void Empty()
2931
{
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace AgileObjects.AgileMapper.Caching
2+
{
3+
using System.Collections.Generic;
4+
5+
internal struct DefaultComparer<T> : IEqualityComparer<T>
6+
{
7+
public bool Equals(T x, T y)
8+
{
9+
// ReSharper disable once PossibleNullReferenceException
10+
return ReferenceEquals(x, y) || x.Equals(y);
11+
}
12+
13+
public int GetHashCode(T obj) => 0;
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace AgileObjects.AgileMapper.Caching
2+
{
3+
using System.Collections.Generic;
4+
5+
internal struct ReferenceEqualsComparer<T> : IEqualityComparer<T>
6+
{
7+
public bool Equals(T x, T y) => ReferenceEquals(x, y);
8+
9+
public int GetHashCode(T obj) => 0;
10+
}
11+
}

0 commit comments

Comments
 (0)