diff --git a/src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs b/src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs
new file mode 100644
index 00000000..d459e1eb
--- /dev/null
+++ b/src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs
@@ -0,0 +1,188 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Shouldly;
+using System;
+using static Mapster.Tests.DynamicTypeGeneratorTests;
+
+namespace Mapster.Tests
+{
+ ///
+ /// https://github.com/MapsterMapper/Mapster/issues/947
+ ///
+ [TestClass]
+ public class WhenImplicitInheritanceMapWithDerivedDestination
+ {
+ [TestCleanup]
+ public void Cleanup()
+ {
+ TypeAdapterConfig.GlobalSettings.Clear();
+ TypeAdapterConfig.GlobalSettings.AllowImplicitDestinationInheritance = false;
+ }
+
+ [TestMethod]
+ public void Inherited_MapWith_On_Base_Destination_Casts_To_Derived_Destination()
+ {
+ var config = new TypeAdapterConfig();
+ config.AllowImplicitDestinationInheritance = true;
+ config.NewConfig()
+ .MapWith(src => src.Type == "Bird"
+ ? (Animal947)new Bird947 { AnimalValue = src.AnimalValueDto }
+ : new Dog947 { AnimalValue = src.AnimalValueDto });
+
+ var source = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" };
+ var sourceInsaider = new AnimalDtoInsaider947() { Animal = source };
+
+ var dog = source.Adapt(config);
+ var dogInsaider = sourceInsaider.Adapt(config);
+
+ dog.ShouldBeOfType();
+ dog.AnimalValue.ShouldBe("Hello");
+
+ dogInsaider.Animal.ShouldBeOfType();
+ dogInsaider.Animal.AnimalValue.ShouldBe("Hello");
+ }
+
+ [TestMethod]
+ public void Inherited_MapWith_Works_For_Explicit_Source_Destination_Pair()
+ {
+ var config = new TypeAdapterConfig();
+ config.AllowImplicitDestinationInheritance = true;
+ config.NewConfig()
+ .MapWith(src => src.Type == "Bird"
+ ? (Animal947)new Bird947 { AnimalValue = src.AnimalValueDto }
+ : new Dog947 { AnimalValue = src.AnimalValueDto });
+
+ var source = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" };
+
+ var dog = source.Adapt(config);
+
+ dog.ShouldBeOfType();
+ dog.AnimalValue.ShouldBe("Hello");
+ }
+
+
+ [TestMethod]
+ public void Inherited_MapWith_On_Base_Destination_ReturnDefault_When_In_Runtime_ResultType_IsNot_Achievable()
+ {
+ var config = new TypeAdapterConfig();
+ config.AllowImplicitDestinationInheritance = true;
+ config.NewConfig()
+ .MapWith(src => src.Type == "Bird"
+ ? (Animal947)new Bird947 { AnimalValue = src.AnimalValueDto }
+ : new Dog947 { AnimalValue = src.AnimalValueDto });
+
+ var source = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Bird" };
+
+ var dog = source.Adapt(config);
+
+ dog.ShouldBeNull();
+ dog.ShouldBe(default);
+ }
+
+
+ [TestMethod]
+ public void Inherited_MapWith_On_Base_Destination_Casts_To_Derived_Destination_UsingInterface()
+ {
+ var config = new TypeAdapterConfig();
+ config.AllowImplicitDestinationInheritance = true;
+ config.NewConfig()
+ .MapWith(src => src.Type == "Bird"
+ ? new ValueTypeBird947 { AnimalValue = src.AnimalValueDto }
+ : new ValueTypeDog947 { AnimalValue = src.AnimalValueDto });
+
+ var dog = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" }.Adapt(config);
+ var defaultdata = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Bird" }.Adapt(config);
+
+ dog.ShouldBeOfType();
+ dog.AnimalValue.ShouldBe("Hello");
+
+ defaultdata.ShouldBeOfType();
+ defaultdata.AnimalValue.ShouldBe(default);
+ }
+
+ [TestMethod]
+ public void Inherited_MapWith_On_Base_Destination_Casts_To_Derived_Destination_NullableValueType()
+ {
+ var config = new TypeAdapterConfig();
+ config.AllowImplicitDestinationInheritance = true;
+ config.NewConfig()
+ .MapWith(src => src.Type == "Bird"
+ ? new ValueTypeBird947 { AnimalValue = src.AnimalValueDto }
+ : new ValueTypeDog947 { AnimalValue = src.AnimalValueDto });
+
+ var validSrc = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" };
+ var invalidSrc = new AnimalDto947 { AnimalValueDto = "Tweet", Type = "Bird" }; ;
+
+ var dog = validSrc.Adapt(config);
+ var Nulldata = invalidSrc.Adapt(config);
+ var InsaiderNullableDog = new AnimalDtoInsaider947() { Animal = validSrc}.Adapt(config);
+ var NullInsaiderNullableDog = new AnimalDtoInsaider947() { Animal = invalidSrc }.Adapt(config);
+
+ dog.ShouldNotBeNull();
+ dog?.AnimalValue.ShouldBe("Hello");
+ InsaiderNullableDog.Animal.ShouldNotBeNull();
+ InsaiderNullableDog.Animal?.AnimalValue.ShouldBe("Hello");
+
+
+ Nulldata.ShouldBeNull();
+ NullInsaiderNullableDog.Animal.ShouldBeNull();
+ }
+
+
+ #region TestClases
+
+ public abstract class Animal947
+ {
+ public string AnimalValue { get; set; } = null!;
+ }
+
+ public class Dog947 : Animal947
+ {
+ }
+
+ public class Bird947 : Animal947
+ {
+ }
+
+
+ public class AnimalDto947
+ {
+ public string AnimalValueDto { get; set; } = null!;
+
+ public string Type { get; set; } = null!;
+ }
+
+ public class AnimalDtoInsaider947
+ {
+ public AnimalDto947 Animal { get; set; }
+ }
+
+ public class DogInsaider947
+ {
+ public Dog947 Animal { get; set; }
+ }
+
+ public interface IAnimal
+ {
+ public string AnimalValue { get; set; }
+ }
+
+ public struct ValueTypeBird947 : IAnimal
+ {
+ public string AnimalValue { get; set; }
+ }
+
+ public struct ValueTypeDog947 : IAnimal
+ {
+ public string AnimalValue { get; set; }
+
+ }
+
+ public class DogValueTypeNullableInsaider947
+ {
+ public ValueTypeDog947? Animal { get; set; }
+ }
+
+ #endregion TestClases
+ }
+}
diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs
index b31a0dbe..0e9c7923 100644
--- a/src/Mapster/Adapters/BaseAdapter.cs
+++ b/src/Mapster/Adapters/BaseAdapter.cs
@@ -485,12 +485,7 @@ protected Expression CreateAdaptExpression(Expression source, Type destinationTy
}
internal Expression CreateAdaptExpression(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping, Expression? destination = null)
{
- Expression _source;
-
- if (arg.MapType != MapType.Projection)
- _source = source.NullableEnumExtractor(); // Extraction Nullable Enum
- else
- _source = source;
+ Expression _source = source;
if (_source.Type == destinationType && arg.MapType == MapType.Projection)
return _source;
@@ -506,20 +501,14 @@ internal Expression CreateAdaptExpression(Expression source, Type destinationTyp
if (_source.Type == destinationType && arg.Settings.ShallowCopyForSameType == true
&& notUsingDestinationValue && rule == null)
exp = _source;
- else if (source is ConditionalExpression cond && mapping != null)
- {
- // convert ApplyNullable Propagation for NotPrimitive Nullable types
- if (mapping.Getter.Type.IsNotPrimitiveNullableType() && !mapping.DestinationMember.Type.IsNullable())
- {
- var adapt = CreateAdaptExpressionCore(cond.IfTrue.GetNotPrimitiveNullableValue(), mapping.DestinationMember.Type, arg, mapping);
- exp = Expression.Condition(cond.Test, adapt, mapping.DestinationMember.Type.CreateDefault());
- }
- else
- exp = CreateAdaptExpressionCore(_source, destinationType, arg, mapping, destination);
- }
else
exp = CreateAdaptExpressionCore(_source, destinationType, arg, mapping, destination);
+ // NullablePropagation when for member using Custom converter MapWith
+ if (notUsingDestinationValue && arg.MapType != MapType.Projection
+ && mapping != null && mapping.Getter.CanBeNull())
+ exp = mapping.Getter.NotNullReturn(exp);
+
//transform(adapt(_source));
if (notUsingDestinationValue)
{
diff --git a/src/Mapster/Adapters/NullableAdapter.cs b/src/Mapster/Adapters/NullableAdapter.cs
new file mode 100644
index 00000000..bfc7d836
--- /dev/null
+++ b/src/Mapster/Adapters/NullableAdapter.cs
@@ -0,0 +1,36 @@
+using Mapster.Utils;
+using System.Linq.Expressions;
+
+namespace Mapster.Adapters
+{
+ internal class NullableAdapter : BaseAdapter
+ {
+
+ protected override int Score => 0; //must do first
+
+ protected override bool CanMap(PreCompileArgument arg)
+ {
+ return arg.SourceType.IsNullable() || arg.DestinationType.IsNullable();
+ }
+ protected override bool CanInline(Expression source, Expression? destination, CompileArgument arg)
+ {
+ return true;
+ }
+
+ protected override Expression? CreateInlineExpression(Expression source, CompileArgument arg, bool IsRequiredOnly = false)
+ {
+ var _source = source.Type.IsNullable()
+ ? Expression.Convert(source, source.Type.GetGenericArguments()[0])
+ : source;
+
+ Expression adapt = CreateAdaptExpression(_source, arg.DestinationType.GetNotNullableTypeDefenition(),arg);
+
+ return adapt.ToNullableExp(arg);
+ }
+
+ protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg)
+ {
+ return Expression.Empty();
+ }
+ }
+}
diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs
index 1dce2053..9463072b 100644
--- a/src/Mapster/TypeAdapterConfig.cs
+++ b/src/Mapster/TypeAdapterConfig.cs
@@ -1,13 +1,14 @@
-using System;
+using Mapster.Adapters;
+using Mapster.Models;
+using Mapster.Utils;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Data.SqlTypes;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
-using Mapster.Adapters;
-using Mapster.Models;
-using Mapster.Utils;
namespace Mapster
{
@@ -32,6 +33,7 @@ private static List CreateRuleTemplate()
new ObjectAdapter().CreateRule(), //-111
new StringAdapter().CreateRule(), //-110
new EnumAdapter().CreateRule(), //-109
+ new NullableAdapter().CreateRule(), // 0
//fallback rules
new TypeAdapterRule
@@ -266,6 +268,10 @@ private static TypeAdapterRule CreateDestinationTypeRule(TypeTuple key)
private static int? GetSubclassDistance(Type type1, Type type2, bool allowInheritance)
{
+ //Support for using ValueType mapping configurations of types, for mapping cases on Nulllable ValueType values
+ if (type1.IsNullable() && !type1.ContainsGenericParameters)
+ type1 = type1.GetGenericArguments().FirstOrDefault();
+
if (type1 == type2)
return 50;
@@ -431,7 +437,7 @@ private static LambdaExpression CreateMapExpression(CompileArgument arg)
throw new CompileException(arg, new InvalidOperationException("ConverterFactory is not found"));
try
{
- return fn(arg);
+ return AdjustInheritedConverterReturnType(fn(arg), arg);
}
catch (Exception ex)
{
@@ -439,6 +445,66 @@ private static LambdaExpression CreateMapExpression(CompileArgument arg)
}
}
+ private static LambdaExpression AdjustInheritedConverterReturnType(LambdaExpression lambda, CompileArgument arg)
+ {
+ var destinationType = arg.DestinationType;
+ var returnType = lambda.ReturnType;
+ var lamdaBody = lambda.Body;
+
+ // Support for using ValueType mapping configurations of types, for mapping cases on Nulllable ValueType values
+ if (arg.DestinationType.IsNullable() && !returnType.IsNullable() && returnType.IsValueType)
+ {
+ lamdaBody = Expression.Convert(lambda.Body, typeof(Nullable<>).MakeGenericType(lambda.ReturnType));
+ lambda = Expression.Lambda(lamdaBody, lambda.Parameters);
+
+ returnType = lambda.ReturnType;
+ }
+
+ if (returnType == destinationType)
+ return lambda;
+
+ //Support for using ValueType mapping configurations of types, for mapping cases on Nulllable ValueType values
+ if (destinationType.IsNullable() && lambda.ReturnType.IsInterface)
+ {
+ var realDestType = destinationType.GetGenericArguments().FirstOrDefault();
+
+ if (realDestType is null || !returnType.IsAssignableFrom(realDestType))
+ return lambda;
+ }
+ // MapWith configured on a base destination type returns the base type, but implicit
+ // destination inheritance can compile the converter for a derived destination.
+ else if (!returnType.IsAssignableFrom(destinationType))
+ return lambda;
+
+ Expression body;
+
+ if(destinationType.CanBeNull())
+ body = Expression.TypeAs(lamdaBody, destinationType);
+ else
+ {
+ var tempDest = Expression.Variable(returnType, "tempDest");
+
+ var variables = new[] {tempDest};
+
+ var blockbody = new List() { Expression.Assign(tempDest, lamdaBody) };
+
+ var condition = Expression.TypeIs(tempDest, destinationType);
+ UnaryExpression ifTrue = Expression.Convert(tempDest, destinationType);
+ DefaultExpression ifFalse = Expression.Default(destinationType);
+
+ ConditionalExpression conditionalExpr = Expression.Condition(condition, ifTrue, ifFalse);
+ blockbody.Add(conditionalExpr);
+
+ BlockExpression body2 = Expression.Block(variables, blockbody);
+
+ return Expression.Lambda(body2,lambda.Parameters);
+
+ }
+
+
+ return Expression.Lambda(body, lambda.Parameters);
+ }
+
private LambdaExpression CreateDynamicMapExpression(TypeTuple tuple)
{
var lambda = CreateMapExpression(tuple, MapType.Map);
diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs
index b7ffc365..df0b3058 100644
--- a/src/Mapster/Utils/ExpressionEx.cs
+++ b/src/Mapster/Utils/ExpressionEx.cs
@@ -537,22 +537,11 @@ internal static Expression GetNameConverterExpression(Func conve
return Expression.Constant(converter);
}
- public static bool IsNotPrimitiveNullableType(this Type type)
+ public static Expression ToNullableExp(this Expression adapt, CompileArgument arg)
{
- return Nullable.GetUnderlyingType(type) != null && !type.IsMapsterPrimitive();
- }
-
- public static Expression GetNotPrimitiveNullableValue(this Expression exp)
- {
- if (exp.Type.IsNotPrimitiveNullableType())
- {
- var getValueOrDefaultMethod = exp.Type.GetMethod("GetValueOrDefault", Type.EmptyTypes);
- var getValue = Expression.Call(exp, getValueOrDefaultMethod);
-
- return getValue;
- }
-
- return exp;
+ if (arg.DestinationType.IsNullable())
+ return Expression.Convert(adapt, arg.DestinationType);
+ return adapt;
}
}
diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs
index 81c3e21e..36a792fc 100644
--- a/src/Mapster/Utils/ReflectionUtils.cs
+++ b/src/Mapster/Utils/ReflectionUtils.cs
@@ -479,5 +479,13 @@ public static bool IsNotCustomConverterFactory(this CompileArgument arg, TypeAda
return true;
}
+
+ public static Type GetNotNullableTypeDefenition(this Type inputType)
+ {
+ if (inputType.IsNullable())
+ return inputType.GetGenericArguments()[0];
+
+ return inputType;
+ }
}
}