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
76 changes: 76 additions & 0 deletions AgileMapper.UnitTests.Orms.EfCore2/WhenCreatingProjections.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace AgileObjects.AgileMapper.UnitTests.Orms.EfCore2
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -67,5 +68,80 @@ public Task ShouldMapAQueryableAsAnEnumerable()
result.ShouldHaveSingleItem().Value.ShouldBeTrue();
});
}

// See https://github.com/agileobjects/AgileMapper/issues/204
[Fact]
public void ShouldHandleEnumerableQueryableMappingUnmappableElements()
{
var source =
new[] { new PublicBool { Value = true } }
.AsQueryable().Where(b => b.Value);

var result = source.Project().To<Issue204.Dto>().First();

result.ShouldNotBeNull().CanWrite.ShouldBeFalse();
}

// See https://github.com/agileobjects/AgileMapper/issues/204
[Fact]
public void ShouldIgnoreConfiguredServiceProviderInEnumerableQueryableMapping()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.UseServiceProvider(new Issue204.ServiceProvider());

mapper.WhenMapping
.From<Issue204.Entity>()
.To<Issue204.EntityDto>()
.Map(ctx => ctx.GetService<Issue204.User>().HasAccess())
.To(dto => dto.CanWrite);

var source =
new List<Issue204.Entity> { new Issue204.Entity { Id = 1 } }
.AsQueryable().Where(e => e.Id == 1);

var result = source.ProjectUsing(mapper).To<Issue204.EntityDto>().First();

result.ShouldNotBeNull();
result.Id.ShouldBe(1);
result.CanWrite.ShouldBeFalse();
}
}

#region Helper Classes

private static class Issue204
{
public class ServiceProvider : IServiceProvider
{
public object GetService(Type serviceType)
=> Activator.CreateInstance(serviceType);
}

public class User
{
public bool HasAccess() => true;
}

public class Entity
{
public long Id { get; set; }
}

public class Dto
{
public bool CanWrite { get; set; }
}

public class EntityDto
{
public long Id { get; set; }

public bool CanWrite { get; set; }
}
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ public void ShouldFlattenAComplexTypeCollectionToANestedObjectDictionaryImplemen

// See https://github.com/agileobjects/AgileMapper/issues/200
[Fact]
public void ShouldMapListDictionaries()
public void ShouldMapBetweenListDictionaries()
{
var source = new PublicField<Dictionary<string, List<string>>>
{
Expand All @@ -364,7 +364,7 @@ public void ShouldMapListDictionaries()
}

[Fact]
public void ShouldMapArrayDictionaries()
public void ShouldMapBetweenArrayDictionaries()
{
var source = new PublicField<Dictionary<string, int[]>>
{
Expand All @@ -384,7 +384,7 @@ public void ShouldMapArrayDictionaries()
.Value
.ShouldNotBeNull();

resultDictionary.Count.ShouldBe(3); ;
resultDictionary.Count.ShouldBe(3);
resultDictionary["1"].ShouldHaveSingleItem().ShouldBe(1L);

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

// See https://github.com/agileobjects/AgileMapper/issues/203
[Fact]
public void ShouldMapBetweenComplexTypeArrayDictionaries()
{
var source = new PublicField<Dictionary<string, Address[]>>
{
Value = new Dictionary<string, Address[]>
{
["1"] = new[] { new Address { Line1 = "1.1.1" } },
["2"] = new[]
{
new Address { Line1 = "2.1.1" },
new Address { Line1 = "2.2.1", Line2 = "2.2.2" }
}
}
};

var result = Mapper.Map(source)
.ToANew<PublicProperty<Dictionary<string, Address[]>>>();

var resultDictionary = result
.ShouldNotBeNull()
.Value
.ShouldNotBeNull();

resultDictionary.Count.ShouldBe(2);
var address11 = resultDictionary["1"].ShouldHaveSingleItem().ShouldNotBeNull();
address11.Line1.ShouldBe("1.1.1");
address11.Line2.ShouldBeNull();

resultDictionary["2"].Length.ShouldBe(2);
var address21 = resultDictionary["2"][0].ShouldNotBeNull();
address21.Line1.ShouldBe("2.1.1");
address21.Line2.ShouldBeNull();

var address22 = resultDictionary["2"][1].ShouldNotBeNull();
address22.Line1.ShouldBe("2.2.1");
address22.Line2.ShouldBe("2.2.2");
}

#region Helper Members

private static class Issue97
Expand Down
2 changes: 2 additions & 0 deletions AgileMapper.UnitTests/WhenMappingToNewEnumerableMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
#if !NETCOREAPP1_0
using System.ComponentModel;
#endif
using System.Linq;
using Common;
using TestClasses;
Expand Down
4 changes: 2 additions & 2 deletions AgileMapper/AgileMapper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netstandard1.0' ">$(PackageTargetFallback);dnxcore50</PackageTargetFallback>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>./Icon.png</PackageIcon>
<PackageReleaseNotes>- Fixing Dictionary-of-collections mapping, re: #200
- Updating to ReadableExpressions v2.5.1
<PackageReleaseNotes>- Fixing complex type collection Dictionary mapping, re: #203
- Removing service injections from projections, re: #204
</PackageReleaseNotes>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ private static bool IsNonSourceMappingDataMember(MemberExpression memberAccess)
return false;
}

protected override Expression VisitMethodCall(MethodCallExpression methodCall)
{
if (methodCall.Method.DeclaringType == typeof(IServiceProviderAccessor))
{
_isNotProjectable = true;
}

return base.VisitMethodCall(methodCall);
}

protected override Expression VisitParameter(ParameterExpression parameter)
{
if (_lambdaParameters.IndexOf(parameter) > 0)
Expand Down
3 changes: 3 additions & 0 deletions AgileMapper/MappingRuleSetSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static MappingRuleSetSettings ForInMemoryMapping(
CheckDerivedSourceTypes = true,
AllowGuardedBindings = allowGuardedBindings,
AllowCloneEntityKeyMapping = allowCloneEntityKeyMapping,
RemoveEmptyElementMappings = true,
GuardAccessTo = value => true,
ExpressionIsSupported = value => true,
AllowObjectTracking = true,
Expand Down Expand Up @@ -52,6 +53,8 @@ public static MappingRuleSetSettings ForInMemoryMapping(

public bool AllowCloneEntityKeyMapping { get; set; }

public bool RemoveEmptyElementMappings { get; set; }

public Func<Expression, bool> GuardAccessTo { get; set; }

public Func<LambdaExpression, bool> ExpressionIsSupported { get; set; }
Expand Down
23 changes: 12 additions & 11 deletions AgileMapper/Members/Dictionaries/DictionaryTargetMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ private Expression GetKey(IMemberMapperData mapperData)

private Expression GetDictionaryAccess(IMemberMapperData mapperData)
{
var parentContextAccess = mapperData.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type);
var parentContextAccess = mapperData
.GetAppropriateMappingContextAccess(typeof(object), _rootDictionaryMember.Type);

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

public override bool CheckExistingElementValue => !HasObjectEntries && !HasSimpleEntries;
public override bool CheckExistingElementValue
=> !(HasObjectEntries || HasSimpleEntries || HasEnumerableEntries);

public override Expression GetHasDefaultValueCheck(IMemberMapperData mapperData)
{
Expand Down Expand Up @@ -346,23 +348,22 @@ public DictionaryTargetMember WithTypeOf(Member sourceMember)

public override void MapCreating(Type sourceType)
{
if (CreateNonDictionaryChildMembers(sourceType))
if (DoNotFlattenSourceObjects(sourceType))
{
_createDictionaryChildMembers = false;
}

base.MapCreating(sourceType);
}

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

#region ExcludeFromCodeCoverage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private Expression AssignDictionaryEntry(
return GetPopulation(loopData, dictionaryEntryMember, mappingData);
}

mappingData = GetMappingData(mappingData);
mappingData = GetEntryMappingData(mappingData);

if (dictionaryEntryMember.HasComplexEntries)
{
Expand Down Expand Up @@ -275,14 +275,12 @@ private Expression GetPopulation(
return populationExpression;
}

private IObjectMappingData GetMappingData(IObjectMappingData mappingData)
private IObjectMappingData GetEntryMappingData(IObjectMappingData mappingData)
{
var sourceElementType = _wrappedBuilder.Context.SourceElementType;
var targetElementType = _targetDictionaryMember.GetElementType(sourceElementType);

mappingData = ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData);

return mappingData;
return ObjectMappingDataFactory.ForElement(sourceElementType, targetElementType, mappingData);
}

private static Expression GetDerivedTypeMapping(
Expand Down
Loading