Skip to content
Merged
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
188 changes: 188 additions & 0 deletions src/Mapster.Tests/WhenImplicitInheritanceMapWithDerivedDestination.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/947
/// </summary>
[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<AnimalDto947, Animal947>()
.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<Dog947>(config);
var dogInsaider = sourceInsaider.Adapt<DogInsaider947>(config);

dog.ShouldBeOfType<Dog947>();
dog.AnimalValue.ShouldBe("Hello");

dogInsaider.Animal.ShouldBeOfType<Dog947>();
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<AnimalDto947, Animal947>()
.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<AnimalDto947, Dog947>(config);

dog.ShouldBeOfType<Dog947>();
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<AnimalDto947, Animal947>()
.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<Dog947>(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<AnimalDto947, IAnimal>()
.MapWith(src => src.Type == "Bird"
? new ValueTypeBird947 { AnimalValue = src.AnimalValueDto }
: new ValueTypeDog947 { AnimalValue = src.AnimalValueDto });

var dog = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Dog" }.Adapt<ValueTypeDog947>(config);
var defaultdata = new AnimalDto947 { AnimalValueDto = "Hello", Type = "Bird" }.Adapt<ValueTypeDog947>(config);

dog.ShouldBeOfType<ValueTypeDog947>();
dog.AnimalValue.ShouldBe("Hello");

defaultdata.ShouldBeOfType<ValueTypeDog947>();
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<AnimalDto947, IAnimal>()
.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<ValueTypeDog947?>(config);
var Nulldata = invalidSrc.Adapt<ValueTypeDog947?>(config);
var InsaiderNullableDog = new AnimalDtoInsaider947() { Animal = validSrc}.Adapt<DogValueTypeNullableInsaider947>(config);
var NullInsaiderNullableDog = new AnimalDtoInsaider947() { Animal = invalidSrc }.Adapt<DogValueTypeNullableInsaider947>(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
}
}
46 changes: 46 additions & 0 deletions src/Mapster.Tests/WhenPropertyNullablePropagationRegression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,55 @@ public void MapToTargetWorkCorrect()
}


/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/987
/// </summary>
[TestMethod]
public void CustomMapUsingNullableValueTypesWorkCorrect()
{
var config = new TypeAdapterConfig();
config.ForType<SourceClass987, DestinationClass987>()
.Map(dest => dest.Value, src => src.UseSecondaryValue
? src.SecondaryValue1 + src.SecondaryValue2
: src.PrimaryValue);


var source1 = new SourceClass987
{
UseSecondaryValue = false,
PrimaryValue = 100
};
var source2 = new SourceClass987
{
UseSecondaryValue = true,
SecondaryValue1 = 10,
SecondaryValue2 = 20
};

var result1 = source1.Adapt<DestinationClass987>(config);
var result2 = source2.Adapt<DestinationClass987>(config);

result1.Value.ShouldBe(100);
result2.Value.ShouldBe(30);
}

}

#region TestClasses

public class SourceClass987
{
public bool UseSecondaryValue { get; set; }
public int? PrimaryValue { get; set; }
public int? SecondaryValue1 { get; set; }
public int? SecondaryValue2 { get; set; }
}

public class DestinationClass987
{
public int Value { get; set; }
}

public enum Currency858
{
Eur,
Expand Down
23 changes: 6 additions & 17 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
{
Expand Down
36 changes: 36 additions & 0 deletions src/Mapster/Adapters/NullableAdapter.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Loading
Loading