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;