From 9a2cf671b47bef2d1a2e1d46f5ba9092f63867f4 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sun, 15 Oct 2023 16:57:17 +0500 Subject: [PATCH 1/3] Fix Issue #640 --- .../WhenMappingNullableEnumRegression.cs | 108 ++++++++++++++++++ src/Mapster/Adapters/BaseAdapter.cs | 36 +++--- src/Mapster/Utils/ExpressionEx.cs | 24 ++++ 3 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 src/Mapster.Tests/WhenMappingNullableEnumRegression.cs diff --git a/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs new file mode 100644 index 00000000..c3a605e1 --- /dev/null +++ b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs @@ -0,0 +1,108 @@ +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 }; + + var _result = myClass?.Adapt(); // Work + + _result.TypeEmployer.Key.ShouldBe(MyEnum.User.ToString()); + } + + /// + /// 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..91fb6c07 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, _genericType); + return ExtractionExpression; + } + + return param; + } + + return param; + } public static Expression ApplyNullPropagation(this Expression getter) { var current = getter; From 60878d834843ee75a6c720225da407a4b332c417 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sun, 15 Oct 2023 17:05:41 +0500 Subject: [PATCH 2/3] del empty lines --- src/Mapster.Tests/WhenMappingNullableEnumRegression.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs index c3a605e1..4f105219 100644 --- a/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs +++ b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs @@ -34,8 +34,7 @@ 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) }; @@ -45,8 +44,6 @@ public void UpdateNullEnumToClass() _result.TypeEmployer.Key.ShouldBe(MyEnum.User.ToString()); } - - } #region TestClasses @@ -75,9 +72,7 @@ class FakeResourceManager class Enums { protected Enums(string data) {} - public static FakeResourceManager Manager { get; set; } - } record KeyValueData From bb765229616aaae771d17a1ed791f59155ac46b9 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sun, 15 Oct 2023 17:25:48 +0500 Subject: [PATCH 3/3] Full work only cast Emun to object --- src/Mapster.Tests/WhenMappingNullableEnumRegression.cs | 7 +++++++ src/Mapster/Utils/ExpressionEx.cs | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs index 4f105219..c6b4281e 100644 --- a/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs +++ b/src/Mapster.Tests/WhenMappingNullableEnumRegression.cs @@ -19,9 +19,16 @@ public void NullEnumToNullClass() 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(); } /// diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index 91fb6c07..a1ab8550 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -312,11 +312,11 @@ public static Expression NullableEnumExtractor(this Expression param) if (_SourceType.IsNullable()) { - var _genericType = param.Type.GetGenericArguments()[0]!; + var _genericType = param.Type.GetGenericArguments()[0]; if (_genericType.IsEnum) { - var ExtractionExpression = Expression.Convert(param, _genericType); + var ExtractionExpression = Expression.Convert(param, typeof(object)); return ExtractionExpression; }