Skip to content
Closed
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
115 changes: 115 additions & 0 deletions src/Mapster.Tests/WhenCtorNullableParamMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

namespace Mapster.Tests
{
[TestClass]
public class WhenCtorNullableParamMapping
{
[TestMethod]
public void Dto_To_Domain_MapsCorrectly()
{
var config = new TypeAdapterConfig();

config.Default.MapToConstructor(true);
config
.NewConfig<AbstractDtoTestClass, AbstractDomainTestClass>()
.Include<DerivedDtoTestClass, DerivedDomainTestClass>();


var dtoDerived = new DerivedDtoTestClass
{
DerivedProperty = "DerivedValue",
AbstractProperty = "AbstractValue"
};

var dto = new DtoTestClass
{
AbstractType = dtoDerived
};

var domain = dto.Adapt<DomainTestClass>(config);

domain.AbstractType.ShouldNotBe(null);
domain.AbstractType.ShouldBeOfType<DerivedDomainTestClass>();

var domainDerived = (DerivedDomainTestClass)domain.AbstractType;
domainDerived.DerivedProperty.ShouldBe(dtoDerived.DerivedProperty);
domainDerived.AbstractProperty.ShouldBe(dtoDerived.AbstractProperty);

}

[TestMethod]
public void Dto_To_Domain_AbstractClassNull_MapsCorrectly()
{
var config = new TypeAdapterConfig();

config.Default.MapToConstructor(true);
config
.NewConfig<AbstractDtoTestClass, AbstractDomainTestClass>()
.Include<DerivedDtoTestClass, DerivedDomainTestClass>();

var dto = new DtoTestClass
{
AbstractType = null
};

var domain = dto.Adapt<DomainTestClass>(config);

domain.AbstractType.ShouldBeNull();
}


#region Immutable classes with private setters, map via ctors
private abstract class AbstractDomainTestClass
{
public string AbstractProperty { get; private set; }

protected AbstractDomainTestClass(string abstractProperty)
{
AbstractProperty = abstractProperty;
}
}

private class DerivedDomainTestClass : AbstractDomainTestClass
{
public string DerivedProperty { get; private set; }

/// <inheritdoc />
public DerivedDomainTestClass(string abstractProperty, string derivedProperty)
: base(abstractProperty)
{
DerivedProperty = derivedProperty;
}
}

private class DomainTestClass
{
public AbstractDomainTestClass? AbstractType { get; private set; }

public DomainTestClass(
AbstractDomainTestClass? abstractType)
{
AbstractType = abstractType;
}
}
#endregion

#region DTO classes
private abstract class AbstractDtoTestClass
{
public string AbstractProperty { get; set; }
}

private class DerivedDtoTestClass : AbstractDtoTestClass
{
public string DerivedProperty { get; set; }
}

private class DtoTestClass
{
public AbstractDtoTestClass? AbstractType { get; set; }
}
#endregion
}
}
12 changes: 11 additions & 1 deletion src/Mapster/Adapters/BaseClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,17 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi
}
else
{
getter = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);

if (member.Getter.CanBeNull() && member.Ignore.Condition == null)
{
var compareNull = Expression.Equal(member.Getter, Expression.Constant(null, member.Getter.Type));
getter = Expression.Condition(ExpressionEx.Not(compareNull),
CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member),
defaultConst);
}
else
getter = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member);

if (member.Ignore.Condition != null)
{
var body = member.Ignore.IsChildPath
Expand Down
2 changes: 1 addition & 1 deletion src/Mapster/Adapters/ClassAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre
var exp = CreateInstantiationExpression(source, arg);
var memberInit = exp as MemberInitExpression;
var newInstance = memberInit?.NewExpression ?? (NewExpression)exp;
var contructorMembers = newInstance.Arguments.OfType<MemberExpression>().Select(me => me.Member).ToArray();
var contructorMembers = newInstance.GetAllMemberExpressionsMemberInfo().ToArray();
var classModel = GetSetterModel(arg);
var classConverter = CreateClassConverter(source, classModel, arg);
var members = classConverter.Members;
Expand Down
152 changes: 152 additions & 0 deletions src/Mapster/Utils/MemberExpressionExtractor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

public static class MemberExpressionExtractor
{
public static IEnumerable<MemberInfo> GetAllMemberExpressionsMemberInfo(this Expression expression)
{
var result = new List<MemberInfo>();
CollectMemberInfos(expression, result);
return result;
}

private static void CollectMemberInfos(Expression expression, ICollection<MemberInfo> results)
{
if (expression == null) return;

if (expression is MemberExpression memberExpression)
{
results.Add(memberExpression.Member);
}
else if (expression is BinaryExpression binaryExpression && binaryExpression.NodeType == ExpressionType.Assign)
{
ProcessBinaryAssign(binaryExpression, results);
}

foreach (var subExpression in expression.GetSubExpressions())
{
CollectMemberInfos(subExpression, results);
}
}

private static void ProcessBinaryAssign(BinaryExpression assignExpression, ICollection<MemberInfo> results)
{
if (assignExpression == null) return;

if (assignExpression.Left is MemberExpression leftMember)
{
results.Add(leftMember.Member);
}

CollectMemberInfos(assignExpression.Right, results);
}

private static IEnumerable<Expression> GetSubExpressions(this Expression expression)
{
if (expression == null) yield break;

switch (expression.NodeType)
{
case ExpressionType.MemberAccess:
yield return ((MemberExpression)expression).Expression;
break;

case ExpressionType.Call:
foreach (var arg in ((MethodCallExpression)expression).Arguments)
yield return arg;
yield return ((MethodCallExpression)expression).Object;
break;

case ExpressionType.Lambda:
yield return ((LambdaExpression)expression).Body;
break;

case ExpressionType.Block:
foreach (var blockExpr in ((BlockExpression)expression).Expressions)
yield return blockExpr;
break;

case ExpressionType.Conditional:
yield return ((ConditionalExpression)expression).Test;
yield return ((ConditionalExpression)expression).IfTrue;
yield return ((ConditionalExpression)expression).IfFalse;
break;

case ExpressionType.NewArrayInit or ExpressionType.NewArrayBounds:
foreach (var arrayItem in ((NewArrayExpression)expression).Expressions)
yield return arrayItem;
break;

case ExpressionType.New:
foreach (var arg in ((NewExpression)expression).Arguments)
yield return arg;
break;

case ExpressionType.Invoke:
yield return ((InvocationExpression)expression).Expression;
break;

case ExpressionType.Assign:
yield return ((BinaryExpression)expression).Left;
yield return ((BinaryExpression)expression).Right;
break;

case ExpressionType.Add:
case ExpressionType.Subtract:
case ExpressionType.Multiply:
case ExpressionType.Divide:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.GreaterThan:
case ExpressionType.LessThan:
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
yield return ((BinaryExpression)expression).Left;
yield return ((BinaryExpression)expression).Right;
break;

case ExpressionType.Not:
case ExpressionType.Negate:
case ExpressionType.Convert:
case ExpressionType.Increment:
case ExpressionType.Decrement:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
case ExpressionType.OnesComplement:
yield return ((UnaryExpression)expression).Operand;
break;

case ExpressionType.TypeIs:
yield return ((TypeBinaryExpression)expression).Expression;
break;

case ExpressionType.Coalesce:
yield return ((BinaryExpression)expression).Left;
yield return ((BinaryExpression)expression).Conversion;
yield return ((BinaryExpression)expression).Right;
break;

case ExpressionType.Index:
yield return ((IndexExpression)expression).Object;
foreach (var indexArg in ((IndexExpression)expression).Arguments)
yield return indexArg;
break;

case ExpressionType.Loop:
yield return ((LoopExpression)expression).Body;
break;

case ExpressionType.Try:
yield return ((TryExpression)expression).Body;
foreach (var handler in ((TryExpression)expression).Handlers)
yield return handler.Body;
yield return ((TryExpression)expression).Finally;
break;


default:
break;
}
}
}
Loading