diff --git a/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs
new file mode 100644
index 00000000..c6b4281e
--- /dev/null
+++ b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs
@@ -0,0 +1,110 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Shouldly;
+using System;
+
+namespace Mapster.Tests
+{
+ [TestClass]
+ public class WhenMappingNullableEnumRegression
+ {
+ ///
+ /// https://github.com/MapsterMapper/Mapster/issues/640
+ ///
+ [TestMethod]
+ public void NullEnumToNullClass()
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .MapWith(s => s == null ? null : new KeyValueData(s.ToString(), Enums.Manager));
+
+ MyClass myClass = new() { TypeEmployer = MyEnum.User };
+
+ MyClass myClassNull = new() { TypeEmployer = null};
+
+
+ var _result = myClass?.Adapt(); // Work
+
+ var _resultNull = myClassNull.Adapt(); // Null Not Error When (object)s if (MyEnum)s - NullReferenceException
+
+ _result.TypeEmployer.Key.ShouldBe(MyEnum.User.ToString());
+
+ _resultNull.TypeEmployer.ShouldBeNull();
+ }
+
+ ///
+ /// https://github.com/MapsterMapper/Mapster/issues/640
+ ///
+ [Ignore] // Will work after RecordType fix
+ [TestMethod]
+ public void UpdateNullEnumToClass()
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .MapWith(s => s == null ? null : new KeyValueData(s.ToString(), Enums.Manager));
+
+ MyClass myClass = new() { TypeEmployer = MyEnum.User };
+
+ var mDest2 = new MyDestination() { TypeEmployer = new KeyValueData("Admin", null) };
+
+ var _MyDestination = myClass?.Adapt(); // Work
+ var _result = _MyDestination.Adapt(mDest2);
+
+ _result.TypeEmployer.Key.ShouldBe(MyEnum.User.ToString());
+ }
+ }
+
+ #region TestClasses
+
+ class MyDestination
+ {
+ public KeyValueData? TypeEmployer { get; set; }
+ }
+
+ class MyClass
+ {
+ public MyEnum? TypeEmployer { get; set; }
+ }
+
+ enum MyEnum
+ {
+ Anonymous = 0,
+ User = 2,
+ }
+
+ class FakeResourceManager
+ {
+
+ }
+
+ class Enums
+ {
+ protected Enums(string data) {}
+ public static FakeResourceManager Manager { get; set; }
+ }
+
+ record KeyValueData
+ {
+ private readonly string? keyHolder;
+ private string? description;
+
+ public KeyValueData(string key, FakeResourceManager manager)
+ {
+ this.keyHolder = key?.ToString();
+ Description = manager?.ToString();
+ }
+
+ public string Key
+ {
+ get => keyHolder!;
+ set { }
+ }
+
+ public string? Description
+ {
+ get => description;
+ set => description ??= value;
+ }
+ }
+
+ #endregion TestClasses
+}
diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs
index b7b6fc67..4450e3f0 100644
--- a/src/Mapster/Adapters/BaseAdapter.cs
+++ b/src/Mapster/Adapters/BaseAdapter.cs
@@ -143,7 +143,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
var blocks = new List();
var label = Expression.Label(arg.DestinationType);
- //var drvdSource = source as TDerivedSource
+ //var drvdSource = _source as TDerivedSource
//if (drvdSource != null)
// return adapt(drvdSource);
foreach (var tuple in arg.Settings.Includes)
@@ -218,7 +218,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
else
{
//TDestination result;
- //if (source == null)
+ //if (_source == null)
// return default(TDestination);
if (source.CanBeNull())
{
@@ -237,15 +237,15 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
assignActions.Add(Expression.Assign(transformedSource, transform));
assignActions.Add(assign);
- //before(source, result, destination);
+ //before(_source, result, destination);
var beforeMappings = arg.Settings.BeforeMappingFactories.Select(it => InvokeMapping(it, source, result, destination, arg, true)).Reverse();
assignActions.AddRange(beforeMappings);
- //result.prop = adapt(source.prop);
+ //result.prop = adapt(_source.prop);
var mapping = CreateBlockExpression(transformedSource, result, arg);
var settingActions = new List {mapping};
- //after(source, result, destination);
+ //after(_source, result, destination);
var afterMappings = arg.Settings.AfterMappingFactories.Select(it => InvokeMapping(it, source, result, destination, arg, false)).Reverse();
settingActions.AddRange(afterMappings);
@@ -254,13 +254,13 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
//using (var scope = new MapContextScope()) {
// var references = scope.Context.Reference;
- // var key = new ReferenceTuple(source, typeof(TDestination));
+ // var key = new ReferenceTuple(_source, typeof(TDestination));
// if (references.TryGetValue(key, out var cache))
// return (TDestination)cache;
//
// var result = new TDestination();
- // references[source] = (object)result;
- // result.prop = adapt(source.prop);
+ // references[_source] = (object)result;
+ // result.prop = adapt(_source.prop);
// return result;
//}
@@ -348,7 +348,7 @@ private static Expression InvokeMapping(
protected Expression? CreateInlineExpressionBody(Expression source, CompileArgument arg)
{
- //source == null ? default(TDestination) : adapt(source)
+ //_source == null ? default(TDestination) : adapt(_source)
var exp = CreateInlineExpression(source, arg);
if (exp == null)
@@ -450,17 +450,19 @@ protected Expression CreateAdaptExpression(Expression source, Type destinationTy
}
internal Expression CreateAdaptExpression(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping, Expression? destination = null)
{
- if (source.Type == destinationType && arg.MapType == MapType.Projection)
- return source;
+ var _source = source.NullableEnumExtractor(); // Extraction Nullable Enum
- //adapt(source);
+ if (_source.Type == destinationType && arg.MapType == MapType.Projection)
+ return _source;
+
+ //adapt(_source);
var notUsingDestinationValue = mapping is not { UseDestinationValue: true };
- var exp = source.Type == destinationType && arg.Settings.ShallowCopyForSameType == true && notUsingDestinationValue &&
- !arg.Context.Config.HasRuleFor(source.Type, destinationType)
- ? source
- : CreateAdaptExpressionCore(source, destinationType, arg, mapping, destination);
+ var exp = _source.Type == destinationType && arg.Settings.ShallowCopyForSameType == true && notUsingDestinationValue &&
+ !arg.Context.Config.HasRuleFor(_source.Type, destinationType)
+ ? _source
+ : CreateAdaptExpressionCore(_source, destinationType, arg, mapping, destination);
- //transform(adapt(source));
+ //transform(adapt(_source));
if (notUsingDestinationValue)
{
var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(exp.Type));
diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs
index 031b29d7..a1ab8550 100644
--- a/src/Mapster/Utils/ExpressionEx.cs
+++ b/src/Mapster/Utils/ExpressionEx.cs
@@ -301,6 +301,30 @@ public static Expression NotNullReturn(this Expression exp, Expression value)
value);
}
+ ///
+ /// Unpack Enum Nullable TSource value
+ ///
+ ///
+ ///
+ public static Expression NullableEnumExtractor(this Expression param)
+ {
+ var _SourceType = param.Type;
+
+ if (_SourceType.IsNullable())
+ {
+ var _genericType = param.Type.GetGenericArguments()[0];
+
+ if (_genericType.IsEnum)
+ {
+ var ExtractionExpression = Expression.Convert(param, typeof(object));
+ return ExtractionExpression;
+ }
+
+ return param;
+ }
+
+ return param;
+ }
public static Expression ApplyNullPropagation(this Expression getter)
{
var current = getter;