From f3141a1a622e06afb701099a6becf5afd76d618b Mon Sep 17 00:00:00 2001 From: wuyangfan Date: Mon, 25 May 2026 16:05:04 +0800 Subject: [PATCH] fix: honor per-type ShallowCopyForSameType override (#938) Resolve ShallowCopyForSameType from merged settings for same-type member adaptation so a parent mapping's deep-copy override does not suppress global shallow-copy behavior on nested same-type members, while explicit per-type false overrides still force deep copy for self-mappings. Co-authored-by: Cursor --- .../WhenPerTypeShallowCopyOverride.cs | 105 ++++++++++++++++++ src/Mapster/Adapters/BaseAdapter.cs | 10 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/Mapster.Tests/WhenPerTypeShallowCopyOverride.cs diff --git a/src/Mapster.Tests/WhenPerTypeShallowCopyOverride.cs b/src/Mapster.Tests/WhenPerTypeShallowCopyOverride.cs new file mode 100644 index 00000000..aabb8456 --- /dev/null +++ b/src/Mapster.Tests/WhenPerTypeShallowCopyOverride.cs @@ -0,0 +1,105 @@ +using Mapster.Models; +using MapsterMapper; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenPerTypeShallowCopyOverride + { + [TestMethod] + public void GlobalShallowCopy_WithPerTypeDeepCopy_ShouldRestoreViaMapToTarget() + { + var cfg = new TypeAdapterConfig(); + cfg.RequireExplicitMapping = true; + cfg.Default.ShallowCopyForSameType(true); + cfg.Default.AvoidInlineMapping(true); + + cfg.NewConfig(); + cfg.NewConfig(); + + cfg.NewConfig() + .ShallowCopyForSameType(false); + + cfg.GetMergedSettings(new TypeTuple(typeof(MyFailStuff), typeof(MyFailStuff)), MapType.Map) + .ShallowCopyForSameType.ShouldBe(false); + + cfg.Compile(); + + var mapper = new Mapper(cfg); + + var dynamicStuff = new MyFailStuff(); + dynamicStuff.Item1 = new RandomObject1(); + dynamicStuff.Item1.SampleName = "SN1"; + dynamicStuff.Item2 = new RandomObject2(); + dynamicStuff.Item2.SampleNumber = 2; + + var originalStuff = mapper.Map(dynamicStuff); + + dynamicStuff.Item1.SampleName = "SN1CHANGED"; + dynamicStuff.Item2.SampleNumber = 3; + + mapper.Map(originalStuff, dynamicStuff); + + dynamicStuff.Item1.SampleName.ShouldBe("SN1"); + dynamicStuff.Item2.SampleNumber.ShouldBe(2); + ReferenceEquals(dynamicStuff.Item1, originalStuff.Item1).ShouldBeFalse(); + ReferenceEquals(dynamicStuff.Item2, originalStuff.Item2).ShouldBeFalse(); + } + + [TestMethod] + public void ParentDeepCopyOverride_ShouldNotDisableImplicitNestedShallowCopyForSameType() + { + var cfg = new TypeAdapterConfig(); + cfg.Default.ShallowCopyForSameType(true); + cfg.NewConfig() + .ShallowCopyForSameType(false); + + cfg.Compile(); + + var src = new Container { Child = new NestedChild { Value = 1 } }; + var dest = src.Adapt(cfg); + + ReferenceEquals(src.Child, dest.Child).ShouldBeTrue(); + } + + public class Container + { + public NestedChild? Child { get; set; } + } + + public class NestedChild + { + public int Value { get; set; } + } + + public class MyFailStuff + { + public MyFailStuff() + { + CreateEmptyEntities(); + } + + public void CreateEmptyEntities() + { + Item1 ??= new RandomObject1(); + Item2 ??= new RandomObject2(); + } + + public RandomObject1? Item1 { get; set; } + + public RandomObject2? Item2 { get; set; } + } + + public class RandomObject1 + { + public string? SampleName { get; set; } + } + + public class RandomObject2 + { + public int SampleNumber { get; set; } + } + } +} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index b31a0dbe..0d729570 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -503,7 +503,15 @@ internal Expression CreateAdaptExpression(Expression source, Type destinationTyp var notUsingDestinationValue = mapping is not { UseDestinationValue: true }; Expression exp; - if (_source.Type == destinationType && arg.Settings.ShallowCopyForSameType == true + var shallowCopy = arg.Settings.ShallowCopyForSameType; + if (_source.Type == destinationType) + { + var typeSettings = arg.Context.Config.GetMergedSettings(tuple, arg.MapType); + if (typeSettings.ShallowCopyForSameType != null) + shallowCopy = typeSettings.ShallowCopyForSameType; + } + + if (_source.Type == destinationType && shallowCopy == true && notUsingDestinationValue && rule == null) exp = _source; else if (source is ConditionalExpression cond && mapping != null)