Skip to content

Commit 6f51655

Browse files
authored
Releases/1.7.2 (#207)
* Bugs/204 (#205) * Handling unmappable complex types in query projection * Filtering out injected service values in query projections, re: #204 * Only filtering empty mappings in element projections * Updating release number + documentation * Bugs/203 (#206) * Support for mapping between complex-type enumerable Dictionaries, re: #203 * Tidying * Updating release notes / Adding v1.7.2 NuGet package
1 parent 9034fb3 commit 6f51655

File tree

15 files changed

+204
-48
lines changed

15 files changed

+204
-48
lines changed

AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace AgileObjects.AgileMapper.UnitTests.Orms.EfCore2
22
{
3+
using System;
34
using System.Collections.Generic;
45
using System.Linq;
56
using System.Threading.Tasks;
@@ -67,5 +68,80 @@ public Task ShouldMapAQueryableAsAnEnumerable()
6768
result.ShouldHaveSingleItem().Value.ShouldBeTrue();
6869
});
6970
}
71+
72+
// See https://github.com/agileobjects/AgileMapper/issues/204
73+
[Fact]
74+
public void ShouldHandleEnumerableQueryableMappingUnmappableElements()
75+
{
76+
var source =
77+
new[] { new PublicBool { Value = true } }
78+
.AsQueryable().Where(b => b.Value);
79+
80+
var result = source.Project().To<Issue204.Dto>().First();
81+
82+
result.ShouldNotBeNull().CanWrite.ShouldBeFalse();
83+
}
84+
85+
// See https://github.com/agileobjects/AgileMapper/issues/204
86+
[Fact]
87+
public void ShouldIgnoreConfiguredServiceProviderInEnumerableQueryableMapping()
88+
{
89+
using (var mapper = Mapper.CreateNew())
90+
{
91+
mapper.WhenMapping
92+
.UseServiceProvider(new Issue204.ServiceProvider());
93+
94+
mapper.WhenMapping
95+
.From<Issue204.Entity>()
96+
.To<Issue204.EntityDto>()
97+
.Map(ctx => ctx.GetService<Issue204.User>().HasAccess())
98+
.To(dto => dto.CanWrite);
99+
100+
var source =
101+
new List<Issue204.Entity> { new Issue204.Entity { Id = 1 } }
102+
.AsQueryable().Where(e => e.Id == 1);
103+
104+
var result = source.ProjectUsing(mapper).To<Issue204.EntityDto>().First();
105+
106+
result.ShouldNotBeNull();
107+
result.Id.ShouldBe(1);
108+
result.CanWrite.ShouldBeFalse();
109+
}
110+
}
111+
112+
#region Helper Classes
113+
114+
private static class Issue204
115+
{
116+
public class ServiceProvider : IServiceProvider
117+
{
118+
public object GetService(Type serviceType)
119+
=> Activator.CreateInstance(serviceType);
120+
}
121+
122+
public class User
123+
{
124+
public bool HasAccess() => true;
125+
}
126+
127+
public class Entity
128+
{
129+
public long Id { get; set; }
130+
}
131+
132+
public class Dto
133+
{
134+
public bool CanWrite { get; set; }
135+
}
136+
137+
public class EntityDto
138+
{
139+
public long Id { get; set; }
140+
141+
public bool CanWrite { get; set; }
142+
}
143+
}
144+
145+
#endregion
70146
}
71147
}

AgileMapper.UnitTests/Dictionaries/WhenMappingToNewDictionaryMembers.cs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public void ShouldFlattenAComplexTypeCollectionToANestedObjectDictionaryImplemen
341341

342342
// See https://github.com/agileobjects/AgileMapper/issues/200
343343
[Fact]
344-
public void ShouldMapListDictionaries()
344+
public void ShouldMapBetweenListDictionaries()
345345
{
346346
var source = new PublicField<Dictionary<string, List<string>>>
347347
{
@@ -364,7 +364,7 @@ public void ShouldMapListDictionaries()
364364
}
365365

366366
[Fact]
367-
public void ShouldMapArrayDictionaries()
367+
public void ShouldMapBetweenArrayDictionaries()
368368
{
369369
var source = new PublicField<Dictionary<string, int[]>>
370370
{
@@ -384,7 +384,7 @@ public void ShouldMapArrayDictionaries()
384384
.Value
385385
.ShouldNotBeNull();
386386

387-
resultDictionary.Count.ShouldBe(3); ;
387+
resultDictionary.Count.ShouldBe(3);
388388
resultDictionary["1"].ShouldHaveSingleItem().ShouldBe(1L);
389389

390390
resultDictionary["2"].Length.ShouldBe(2);
@@ -397,6 +397,46 @@ public void ShouldMapArrayDictionaries()
397397
resultDictionary["3"][2].ShouldBe(3L);
398398
}
399399

400+
// See https://github.com/agileobjects/AgileMapper/issues/203
401+
[Fact]
402+
public void ShouldMapBetweenComplexTypeArrayDictionaries()
403+
{
404+
var source = new PublicField<Dictionary<string, Address[]>>
405+
{
406+
Value = new Dictionary<string, Address[]>
407+
{
408+
["1"] = new[] { new Address { Line1 = "1.1.1" } },
409+
["2"] = new[]
410+
{
411+
new Address { Line1 = "2.1.1" },
412+
new Address { Line1 = "2.2.1", Line2 = "2.2.2" }
413+
}
414+
}
415+
};
416+
417+
var result = Mapper.Map(source)
418+
.ToANew<PublicProperty<Dictionary<string, Address[]>>>();
419+
420+
var resultDictionary = result
421+
.ShouldNotBeNull()
422+
.Value
423+
.ShouldNotBeNull();
424+
425+
resultDictionary.Count.ShouldBe(2);
426+
var address11 = resultDictionary["1"].ShouldHaveSingleItem().ShouldNotBeNull();
427+
address11.Line1.ShouldBe("1.1.1");
428+
address11.Line2.ShouldBeNull();
429+
430+
resultDictionary["2"].Length.ShouldBe(2);
431+
var address21 = resultDictionary["2"][0].ShouldNotBeNull();
432+
address21.Line1.ShouldBe("2.1.1");
433+
address21.Line2.ShouldBeNull();
434+
435+
var address22 = resultDictionary["2"][1].ShouldNotBeNull();
436+
address22.Line1.ShouldBe("2.2.1");
437+
address22.Line2.ShouldBe("2.2.2");
438+
}
439+
400440
#region Helper Members
401441

402442
private static class Issue97

AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
{
33
using System.Collections.Generic;
44
using System.Collections.ObjectModel;
5+
#if !NETCOREAPP1_0
56
using System.ComponentModel;
7+
#endif
68
using System.Linq;
79
using Common;
810
using TestClasses;

AgileMapper/AgileMapper.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.0' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
2121
<PackageLicenseExpression>MIT</PackageLicenseExpression>
2222
<PackageIcon>./Icon.png</PackageIcon>
23-
<PackageReleaseNotes>- Fixing Dictionary-of-collections mapping, re: #200
24-
- Updating to ReadableExpressions v2.5.1
23+
<PackageReleaseNotes>- Fixing complex type collection Dictionary mapping, re: #203
24+
- Removing service injections from projections, re: #204
2525
</PackageReleaseNotes>
2626
</PropertyGroup>
2727

AgileMapper/Extensions/Internal/ExpressionExtensions.CanBeProjected.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ private static bool IsNonSourceMappingDataMember(MemberExpression memberAccess)
6262
return false;
6363
}
6464

65+
protected override Expression VisitMethodCall(MethodCallExpression methodCall)
66+
{
67+
if (methodCall.Method.DeclaringType == typeof(IServiceProviderAccessor))
68+
{
69+
_isNotProjectable = true;
70+
}
71+
72+
return base.VisitMethodCall(methodCall);
73+
}
74+
6575
protected override Expression VisitParameter(ParameterExpression parameter)
6676
{
6777
if (_lambdaParameters.IndexOf(parameter) > 0)

AgileMapper/MappingRuleSetSettings.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public static MappingRuleSetSettings ForInMemoryMapping(
2323
CheckDerivedSourceTypes = true,
2424
AllowGuardedBindings = allowGuardedBindings,
2525
AllowCloneEntityKeyMapping = allowCloneEntityKeyMapping,
26+
RemoveEmptyElementMappings = true,
2627
GuardAccessTo = value => true,
2728
ExpressionIsSupported = value => true,
2829
AllowObjectTracking = true,
@@ -52,6 +53,8 @@ public static MappingRuleSetSettings ForInMemoryMapping(
5253

5354
public bool AllowCloneEntityKeyMapping { get; set; }
5455

56+
public bool RemoveEmptyElementMappings { get; set; }
57+
5558
public Func<Expression, bool> GuardAccessTo { get; set; }
5659

5760
public Func<LambdaExpression, bool> ExpressionIsSupported { get; set; }

AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ private Expression GetKey(IMemberMapperData mapperData)
190190

191191
private Expression GetDictionaryAccess(IMemberMapperData mapperData)
192192
{
193-
var parentContextAccess = mapperData.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type);
193+
var parentContextAccess = mapperData
194+
.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type);
194195

195196
if (parentContextAccess.NodeType != Parameter)
196197
{
@@ -207,7 +208,8 @@ private Expression GetDictionaryAccess(IMemberMapperData mapperData)
207208
return dictionaryMapperData.TargetInstance;
208209
}
209210

210-
public override bool CheckExistingElementValue => !HasObjectEntries && !HasSimpleEntries;
211+
public override bool CheckExistingElementValue
212+
=> !(HasObjectEntries || HasSimpleEntries || HasEnumerableEntries);
211213

212214
public override Expression GetHasDefaultValueCheck(IMemberMapperData mapperData)
213215
{
@@ -346,23 +348,22 @@ public DictionaryTargetMember WithTypeOf(Member sourceMember)
346348

347349
public override void MapCreating(Type sourceType)
348350
{
349-
if (CreateNonDictionaryChildMembers(sourceType))
351+
if (DoNotFlattenSourceObjects(sourceType))
350352
{
351353
_createDictionaryChildMembers = false;
352354
}
353355

354356
base.MapCreating(sourceType);
355357
}
356358

357-
private bool CreateNonDictionaryChildMembers(Type sourceType)
359+
private bool DoNotFlattenSourceObjects(Type sourceType)
358360
{
359-
// If this DictionaryTargetMember represents an object-typed dictionary
360-
// entry and we're mapping from a source of type object, we switch from
361-
// mapping to flattened entries to mapping entire objects:
362-
return HasObjectEntries &&
363-
this.IsEnumerableElement() &&
364-
(MemberChain[Depth - 2] == _rootDictionaryMember.LeafMember) &&
365-
(sourceType == typeof(object));
361+
// If this target Dictionary member's type matches the type of source
362+
// objects being mapped into it, we switch from flattening source
363+
// objects into Dictionary entries to mapping entire objects:
364+
return this.IsEnumerableElement() &&
365+
(ValueType == sourceType) &&
366+
(MemberChain[Depth - 2] == _rootDictionaryMember.LeafMember);
366367
}
367368

368369
#region ExcludeFromCodeCoverage

AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private Expression AssignDictionaryEntry(
124124
return GetPopulation(loopData, dictionaryEntryMember, mappingData);
125125
}
126126

127-
mappingData = GetMappingData(mappingData);
127+
mappingData = GetEntryMappingData(mappingData);
128128

129129
if (dictionaryEntryMember.HasComplexEntries)
130130
{
@@ -275,14 +275,12 @@ private Expression GetPopulation(
275275
return populationExpression;
276276
}
277277

278-
private IObjectMappingData GetMappingData(IObjectMappingData mappingData)
278+
private IObjectMappingData GetEntryMappingData(IObjectMappingData mappingData)
279279
{
280280
var sourceElementType = _wrappedBuilder.Context.SourceElementType;
281281
var targetElementType = _targetDictionaryMember.GetElementType(sourceElementType);
282282

283-
mappingData = ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData);
284-
285-
return mappingData;
283+
return ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData);
286284
}
287285

288286
private static Expression GetDerivedTypeMapping(

0 commit comments

Comments
 (0)