Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugs/173 dictionary values to target #194

Merged
merged 2 commits into from
Apr 14, 2020
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
558 changes: 3 additions & 555 deletions AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public void ShouldErrorIfABlankElementKeyPartIsSpecified()
{
var configEx = Should.Throw<MappingConfigurationException>(() =>
Mapper.WhenMapping
.Dictionaries
.DictionariesWithValueType<DateTime>()
.UseElementKeyPattern(string.Empty));

configEx.Message.ShouldContain(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public void ShouldApplyACustomEnumerableElementPatternToASpecificTargetType()
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.Dictionaries
.DictionariesWithValueType<string>()
.UseMemberNameSeparator("-")
.UseElementKeyPattern("i")
.AndWhenMapping
Expand Down Expand Up @@ -616,6 +616,34 @@ public void ShouldMapANestedComplexTypeSourceDictionaryToTarget()
}
}

// See https://github.com/agileobjects/AgileMapper/issues/173
[Fact]
public void ShouldMapDictionaryComplexTypeValuesToANestedToTargetList()
{
using (var mapper = Mapper.CreateNew())
{
mapper.WhenMapping
.FromDictionariesWithValueType<PublicField<string>>()
.ToANew<List<PublicField<string>>>()
.Map((src, _) => src.Values.ToList()).ToTarget();

var source = new PublicProperty<Dictionary<string, PublicField<string>>>
{
Value = new Dictionary<string, PublicField<string>>
{
["test1"] = new PublicField<string> { Value = "value1" },
["test2"] = new PublicField<string> { Value = "value2" }
}
};

var result = mapper.Map(source).ToANew<PublicField<List<PublicField<string>>>>();

result.Value.Count.ShouldBe(2);
result.Value.First().Value.ShouldBe("value1");
result.Value.Second().Value.ShouldBe("value2");
}
}

#region Helper Classes

public static class Issue152
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,44 @@
{
using AgileMapper.Configuration;
using AgileMapper.Configuration.Dictionaries;
using AgileObjects.AgileMapper.Members;
using Members;
using Members.Dictionaries;
using NetStandardPolyfills;

internal static class DictionaryConfigurationExtensions
{
public static MappingConfigInfo ForDictionary(this MappingConfigInfo configInfo)
=> configInfo.Set(DictionaryType.Dictionary);

public static MappingConfigInfo ForSourceDictionary<TValue>(this MappingConfigInfo configInfo)
{
return configInfo
.ForDictionary()
.Set<SourceTypeComparer>(SourceDictionaryTypeComparer<TValue>);
}

private static bool SourceDictionaryTypeComparer<TValue>(ITypePair typePair, ITypePair otherTypePair)
{
if (!TypePairExtensions.IsForSourceType(typePair, otherTypePair))
{
return false;
}

if (typeof(TValue) == Constants.AllTypes)
{
return true;
}

if ((otherTypePair as IQualifiedMemberContext)?.SourceMember is DictionarySourceMember dictionaryMember)
{
return typeof(TValue).IsAssignableTo(dictionaryMember.ValueType);
}

return false;
}

public static MappingConfigInfo ForTargetDictionary(this MappingConfigInfo configInfo)
=> configInfo.Set(DictionaryType.Dictionary).WithMemberTypeComparers();
=> configInfo.ForDictionary().WithMemberTypeComparers();

public static MappingConfigInfo WithMemberTypeComparers(this MappingConfigInfo configInfo)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,24 +127,24 @@ MappingConfigStartingPoint IGlobalDynamicSettings.AndWhenMapping
#region Dictionaries

public ISourceDictionaryMappingConfigurator<TValue, TTarget> To<TTarget>()
=> CreateDictionaryConfigurator<TTarget>(ConfigInfo.ForAllRuleSets());
=> CreateSourceDictionaryConfigurator<TTarget>(ConfigInfo.ForAllRuleSets());

public ISourceDictionaryMappingConfigurator<TValue, TTarget> ToANew<TTarget>()
=> CreateDictionaryConfigurator<TTarget>(Constants.CreateNew);
=> CreateSourceDictionaryConfigurator<TTarget>(Constants.CreateNew);

public ISourceDictionaryMappingConfigurator<TValue, TTarget> OnTo<TTarget>()
=> CreateDictionaryConfigurator<TTarget>(Constants.Merge);
=> CreateSourceDictionaryConfigurator<TTarget>(Constants.Merge);

public ISourceDictionaryMappingConfigurator<TValue, TTarget> Over<TTarget>()
=> CreateDictionaryConfigurator<TTarget>(Constants.Overwrite);
=> CreateSourceDictionaryConfigurator<TTarget>(Constants.Overwrite);

private SourceDictionaryMappingConfigurator<TValue, TTarget> CreateDictionaryConfigurator<TTarget>(
private SourceDictionaryMappingConfigurator<TValue, TTarget> CreateSourceDictionaryConfigurator<TTarget>(
string ruleSetName)
{
return CreateDictionaryConfigurator<TTarget>(ConfigInfo.ForRuleSet(ruleSetName));
return CreateSourceDictionaryConfigurator<TTarget>(ConfigInfo.ForRuleSet(ruleSetName));
}

private static SourceDictionaryMappingConfigurator<TValue, TTarget> CreateDictionaryConfigurator<TTarget>(
private static SourceDictionaryMappingConfigurator<TValue, TTarget> CreateSourceDictionaryConfigurator<TTarget>(
MappingConfigInfo configInfo)
{
return new SourceDictionaryMappingConfigurator<TValue, TTarget>(configInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class SourceDictionaryMappingConfigurator<TValue, TTarget> :
ISourceDictionaryMappingConfigurator<TValue, TTarget>
{
public SourceDictionaryMappingConfigurator(MappingConfigInfo configInfo)
: base(configInfo)
: base(configInfo.ForSourceDictionary<TValue>())
{
}

Expand Down
8 changes: 2 additions & 6 deletions AgileMapper/Api/Configuration/MappingConfigStartingPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.Linq.Expressions;
using System.Reflection;
using AgileMapper.Configuration;
using AgileMapper.Configuration.Dictionaries;
using AgileMapper.Configuration.MemberIgnores;
using AgileMapper.Configuration.MemberIgnores.SourceValueFilters;
using Dictionaries;
Expand Down Expand Up @@ -570,15 +569,12 @@ private DictionaryMappingConfigurator<TValue> CreateExpandoObjectConfigurator<TV
});
}
#endif
private DictionaryMappingConfigurator<TValue> CreateDictionaryConfigurator<TValue>()
=> CreateDictionaryConfigurator<TValue>(cfg => cfg.Set(DictionaryType.Dictionary));

private DictionaryMappingConfigurator<TValue> CreateDictionaryConfigurator<TValue>(
Action<MappingConfigInfo> configSetup)
Action<MappingConfigInfo> configSetup = null)
{
var configInfo = _configInfo.ForAllSourceTypes().ForSourceValueType(typeof(TValue));

configSetup.Invoke(configInfo);
configSetup?.Invoke(configInfo);

return new DictionaryMappingConfigurator<TValue>(configInfo);
}
Expand Down
2 changes: 1 addition & 1 deletion AgileMapper/MappingDataExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static IConfiguredDataSource GetToTargetDataSourceOrNullForTargetType(thi

foreach (var dataSource in toTargetDataSources)
{
mappingData = mappingData.WithSource(dataSource.SourceMember);
mappingData = mappingData.WithToTargetSource(dataSource.SourceMember);

if (mappingData.IsTargetConstructable())
{
Expand Down
2 changes: 1 addition & 1 deletion AgileMapper/ObjectPopulation/IObjectMappingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal interface IObjectMappingData : IObjectMappingDataUntyped, IMappingConte

object MapStart();

IObjectMappingData WithSource(IQualifiedMember newSourceMember);
IObjectMappingData WithToTargetSource(IQualifiedMember newSourceMember);

IObjectMappingData WithTypes(Type newSourceType, Type newTargetType, bool isForDerivedTypeMapping = true);
}
Expand Down
43 changes: 25 additions & 18 deletions AgileMapper/ObjectPopulation/MappingCreationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ namespace AgileObjects.AgileMapper.ObjectPopulation
using System.Linq;
#if NET35
using Microsoft.Scripting.Ast;
using static Microsoft.Scripting.Ast.ExpressionType;
#else
using System.Linq.Expressions;
using static System.Linq.Expressions.ExpressionType;
#endif
using DataSources;
using Extensions;
using Members;
#if NET35
using static Microsoft.Scripting.Ast.ExpressionType;
#else
using static System.Linq.Expressions.ExpressionType;
#endif
using static CallbackPosition;

internal class MappingCreationContext
{
private bool _mapperDataHasRootEnumerableVariables;
private IList<Expression> _memberMappingExpressions;

public MappingCreationContext(IObjectMappingData mappingData)
Expand All @@ -31,10 +33,10 @@ public MappingCreationContext(IObjectMappingData mappingData)
return;
}

var basicMapperData = MapperData.WithNoTargetMember();
var callbackQueryMapperData = MapperData.WithNoTargetMember();

PreMappingCallback = basicMapperData.GetMappingCallbackOrNull(Before, MapperData);
PostMappingCallback = basicMapperData.GetMappingCallbackOrNull(After, MapperData);
PreMappingCallback = callbackQueryMapperData.GetMappingCallbackOrNull(Before, MapperData);
PostMappingCallback = callbackQueryMapperData.GetMappingCallbackOrNull(After, MapperData);
}

private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData)
Expand Down Expand Up @@ -101,7 +103,7 @@ private static bool IsMapRepeatedCall(Expression expression)

public MappingCreationContext WithDataSource(IDataSource newDataSource)
{
var newSourceMappingData = MappingData.WithSource(newDataSource.SourceMember);
var newSourceMappingData = MappingData.WithToTargetSource(newDataSource.SourceMember);

var newContext = new MappingCreationContext(newSourceMappingData)
{
Expand All @@ -115,32 +117,37 @@ public MappingCreationContext WithDataSource(IDataSource newDataSource)
{
newContext.MapperData.TargetInstance = MapperData.TargetInstance;
}
else if (_mapperDataHasRootEnumerableVariables)
else if (TargetMember.IsEnumerable)
{
UpdateEnumerableVariables(MapperData, newContext.MapperData);
UpdateEnumerableVariablesIfAppropriate(MapperData, newContext.MapperData);
}

return newContext;
}

public void UpdateFrom(MappingCreationContext childSourceContext)
public void UpdateFrom(MappingCreationContext toTargetContext)
{
MappingData.MapperKey.AddSourceMemberTypeTesterIfRequired(childSourceContext.MappingData);
MappingData.MapperKey.AddSourceMemberTypeTesterIfRequired(toTargetContext.MappingData);

if (TargetMember.IsComplex || _mapperDataHasRootEnumerableVariables)
if (TargetMember.IsComplex)
{
return;
}

_mapperDataHasRootEnumerableVariables = true;

UpdateEnumerableVariables(childSourceContext.MapperData, MapperData);
UpdateEnumerableVariablesIfAppropriate(toTargetContext.MapperData, MapperData);
}

private static void UpdateEnumerableVariables(ObjectMapperData sourceMapperData, ObjectMapperData targetMapperData)
private static void UpdateEnumerableVariablesIfAppropriate(
ObjectMapperData fromMapperData,
ObjectMapperData toMapperData)
{
targetMapperData.LocalVariable = sourceMapperData.LocalVariable;
targetMapperData.EnumerablePopulationBuilder.TargetVariable = sourceMapperData.EnumerablePopulationBuilder.TargetVariable;
if (fromMapperData.EnumerablePopulationBuilder.TargetVariable == null)
{
return;
}

toMapperData.LocalVariable = fromMapperData.LocalVariable;
toMapperData.EnumerablePopulationBuilder.TargetVariable = fromMapperData.EnumerablePopulationBuilder.TargetVariable;
}
}
}
7 changes: 3 additions & 4 deletions AgileMapper/ObjectPopulation/MappingDataCreationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ internal static class MappingDataCreationFactory
[DebuggerStepThrough]
public static Expression ForToTarget(
ObjectMapperData parentMapperData,
Expression toTargetDataSource)
Expression toTargetSourceValue)
{
var withSourceMethod = parentMapperData
.MappingDataObject
.Type
.GetPublicInstanceMethod("WithSource")
.MakeGenericMethod(toTargetDataSource.Type);
.MakeGenericMethod(toTargetSourceValue.Type);

var withSourceCall = Expression.Call(
parentMapperData.MappingDataObject,
withSourceMethod,
toTargetDataSource);
toTargetSourceValue);

return withSourceCall;
}
Expand All @@ -35,7 +35,6 @@ public static Expression ForToTarget(
public static Expression ForDerivedType(ObjectMapperData childMapperData)
{
UseAsConversion(childMapperData, out var asConversion);

return asConversion;
}

Expand Down
30 changes: 15 additions & 15 deletions AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,44 +111,44 @@ private void AddPopulationsAndCallbacks(MappingCreationContext context)

private IEnumerable<Expression> GetConfiguredToTargetDataSourceMappings(MappingCreationContext context)
{
if (!HasConfiguredToTargetDataSources(context.MapperData, out var configuredToTargetDataSources))
if (!HasConfiguredToTargetDataSources(context.MapperData, out var toTargetDataSources))
{
yield break;
}

for (var i = 0; i < configuredToTargetDataSources.Count; ++i)
for (var i = 0; i < toTargetDataSources.Count; ++i)
{
var configuredToTargetDataSource = configuredToTargetDataSources[i];
var newSourceContext = context.WithDataSource(configuredToTargetDataSource);
var toTargetDataSource = toTargetDataSources[i];
var toTargetContext = context.WithDataSource(toTargetDataSource);

AddPopulationsAndCallbacks(newSourceContext);
AddPopulationsAndCallbacks(toTargetContext);

if (newSourceContext.MappingExpressions.None())
if (toTargetContext.MappingExpressions.None())
{
continue;
}

context.UpdateFrom(newSourceContext);
context.UpdateFrom(toTargetContext);

var mapping = newSourceContext.MappingExpressions.HasOne()
? newSourceContext.MappingExpressions.First()
: Expression.Block(newSourceContext.MappingExpressions);
var mapping = toTargetContext.MappingExpressions.HasOne()
? toTargetContext.MappingExpressions.First()
: Expression.Block(toTargetContext.MappingExpressions);

mapping = MappingFactory.UseLocalToTargetDataSourceVariableIfAppropriate(
context.MapperData,
newSourceContext.MapperData,
configuredToTargetDataSource.Value,
toTargetContext.MapperData,
toTargetDataSource.Value,
mapping);

if (!configuredToTargetDataSource.IsConditional)
if (!toTargetDataSource.IsConditional)
{
yield return mapping;
continue;
}

if (context.MapperData.TargetMember.IsComplex || (i > 0))
{
yield return Expression.IfThen(configuredToTargetDataSource.Condition, mapping);
yield return Expression.IfThen(toTargetDataSource.Condition, mapping);
continue;
}

Expand All @@ -158,7 +158,7 @@ private IEnumerable<Expression> GetConfiguredToTargetDataSourceMappings(MappingC

var assignFallback = context.MapperData.LocalVariable.AssignTo(fallback);

yield return Expression.IfThenElse(configuredToTargetDataSource.Condition, mapping, assignFallback);
yield return Expression.IfThenElse(toTargetDataSource.Condition, mapping, assignFallback);
}
}

Expand Down
2 changes: 1 addition & 1 deletion AgileMapper/ObjectPopulation/ObjectMappingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ public IObjectMappingData<TNewSource, TNewTarget> WithTargetType<TNewSource, TNe
return With(default(TNewSource), Target as TNewTarget, isForDerivedTypeMapping);
}

public IObjectMappingData WithSource(IQualifiedMember newSourceMember)
public IObjectMappingData WithToTargetSource(IQualifiedMember newSourceMember)
{
var newSourceMappingData = WithTypes(
GetSourceMemberRuntimeType(newSourceMember),
Expand Down