Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to issue #640 - Nullable Emun to Nullable Destination (Class or param) - Need Sample for testing #643

Open
wants to merge 3 commits into
base: development
Choose a base branch
from
Open
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
110 changes: 110 additions & 0 deletions src/Mapster.Tests/WhenMappingNullableEnumRegression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System;

namespace Mapster.Tests
{
[TestClass]
public class WhenMappingNullableEnumRegression
{
/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/640
/// </summary>
[TestMethod]
public void NullEnumToNullClass()
{
TypeAdapterConfig<Enum?, KeyValueData?>
.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<MyDestination?>(); // Work

var _resultNull = myClassNull.Adapt<MyDestination>(); // Null Not Error When (object)s if (MyEnum)s - NullReferenceException

_result.TypeEmployer.Key.ShouldBe(MyEnum.User.ToString());

_resultNull.TypeEmployer.ShouldBeNull();
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/640
/// </summary>
[Ignore] // Will work after RecordType fix
[TestMethod]
public void UpdateNullEnumToClass()
{
TypeAdapterConfig<Enum?, KeyValueData?>
.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<MyDestination?>(); // 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
}
36 changes: 19 additions & 17 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de
var blocks = new List<Expression>();
var label = Expression.Label(arg.DestinationType);

//var drvdSource = source as TDerivedSource
//var drvdSource = _source as TDerivedSource
//if (drvdSource != null)
// return adapt<TSource, TDest>(drvdSource);
foreach (var tuple in arg.Settings.Includes)
Expand Down Expand Up @@ -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())
{
Expand All @@ -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<Expression> {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);

Expand All @@ -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;
//}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
Expand Down
24 changes: 24 additions & 0 deletions src/Mapster/Utils/ExpressionEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,30 @@ public static Expression NotNullReturn(this Expression exp, Expression value)
value);
}

/// <summary>
/// Unpack Enum Nullable TSource value
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
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;
Expand Down
Loading