diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 99ef89c6..50545c59 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ false - 10.0.12 + 10.0.13-pre01 netstandard2.0;net10.0;net9.0;net8.0 netstandard2.0;net10.0;net9.0;net8.0 net10.0;net9.0;net8.0 diff --git a/src/Mapster.Core/Enums/MapType.cs b/src/Mapster.Core/Enums/MapType.cs index fa8762f1..8c0646bc 100644 --- a/src/Mapster.Core/Enums/MapType.cs +++ b/src/Mapster.Core/Enums/MapType.cs @@ -9,5 +9,6 @@ public enum MapType MapToTarget = 2, Projection = 4, ApplyNullPropagation = 8, + CtorParam = 16, } } \ No newline at end of file diff --git a/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs b/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs index 847d2068..70dfee7b 100644 --- a/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs +++ b/src/Mapster.EF6/TypeAdapterBuilderExtensions.cs @@ -60,10 +60,10 @@ public static ITypeAdapterBuilder EntityFromContext(this IType var getters = keys.Select(key => arg.DestinationType.GetProperty(key)) .Select(prop => new PropertyModel(prop)) .Select(model => arg.Settings.ValueAccessingStrategies - .Select(s => s(src, model, arg)) + .Select(s => s((ResolverSourceInput)src, model, arg)) .FirstOrDefault(exp => exp != null)) .Where(exp => exp != null) - .Select(exp => Expression.Convert(exp, typeof(object))) + .Select(exp => Expression.Convert(exp.Exp, typeof(object))) .ToArray(); if (getters.Length != keys.Length) throw new InvalidOperationException($"Cannot get key for sourceType={arg.SourceType.Name}, destinationType={arg.DestinationType.Name}"); diff --git a/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs b/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs index f417651c..88a207a5 100644 --- a/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs +++ b/src/Mapster.EFCore/TypeAdapterBuilderExtensions.cs @@ -66,10 +66,10 @@ public static ITypeAdapterBuilder EntityFromContext(this IType var getters = keys.Select(key => arg.DestinationType.GetProperty(key)) .Select(prop => new PropertyModel(prop!)) .Select(model => arg.Settings.ValueAccessingStrategies - .Select(s => s(src, model, arg)) + .Select(s => s((ResolverSourceInput)src, model, arg)) .FirstOrDefault(exp => exp != null)) .Where(exp => exp != null) - .Select(exp => Expression.Convert(exp, typeof(object))) + .Select(exp => Expression.Convert(exp.Exp, typeof(object))) .ToArray(); if (getters.Length != keys.Length) throw new InvalidOperationException($"Cannot get key for sourceType={arg.SourceType.Name}, destinationType={arg.DestinationType.Name}"); diff --git a/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs new file mode 100644 index 00000000..ddacef3a --- /dev/null +++ b/src/Mapster.Tests/WhenMapUsingOverrideTypesSettings.cs @@ -0,0 +1,327 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenMapUsingOverrideTypesSettings + { + [TestMethod] + public void OverrideDestinationTramsformIsWorked() + { + var config = new TypeAdapterConfig(); + config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + + config + .NewConfig() + .MapUsing(src => src.Children, dest => dest.Children, + cfg => + { + cfg.SkipDestinationTransforms(); + }) + .MapUsing(src => src.Array, dest => dest.Array, + cfg => + { + cfg + .ReConfigurate() + .MapWith(x => x ?? new[] { 42 }); + }); + + + var source = new CollectionPocoOverride(); + var destination = source.Adapt(config); + + destination.MultiDimentionalArray.Length.ShouldBe(0); + destination.ChildDict.Count.ShouldBe(0); + destination.Set.Count.ShouldBe(0); + + + destination.Children.ShouldBeNull(); // Destination Transforms from global context settings is skipped for this property + destination.Array[0].ShouldBe(42); // Custom converter for types is worked, Destination Transforms is not achievable because the custom converter never returns null + + + var destWithNotTypesSettingOverride = new CollectionPocoOverride().Adapt(config); + + // Destination Transforms correct work from other mapping types + destWithNotTypesSettingOverride.Array.Length.ShouldBe(0); + } + + [TestMethod] + public void UsingDefaultValueIsWorked() + { + var config = new TypeAdapterConfig(); + + config.ForDestinationType() + .DefaultValue(x => 32); + + config.ForDestinationType() + .DefaultValue(x=>42); + + int? src = null; + var srcInsaider = new NullableIntInsaider() { Data = null }; + + + var resultCD = src.Adapt(config); + var resultCDInsaider = srcInsaider.Adapt(config); + + resultCD.ShouldBe(32); + resultCDInsaider.Data.ShouldBe(42); + + config. + NewConfig() + .MapUsing(dest => dest.Data, src => src.Data, cfg => + { + cfg.ReConfigurate() + .DefaultValue(x => 35); + }); + + var resultCDInsaiderReconfig = srcInsaider.Adapt(config); + + resultCDInsaiderReconfig.Data.ShouldBe(35); + } + + [TestMethod] + public void CustomDefaultValueIsWorkedWhenUsingAsCtorParam() + { + var config = new TypeAdapterConfig(); + + config.ForDestinationType() + .DefaultValue(x => 42); + + config. + NewConfig() + .MapUsing(dest => dest.Data, src => src.Data, cfg => + { + cfg.ReConfigurate() + .DefaultValue(x => 35); + }); + + var src = new NullableIntInsaider() { Data = null }; + + var result = src.Adapt(config); + + result.Data.ShouldBe(35); + } + + [TestMethod] + public void ExtraSourceUsingCustomConfig() + { + var config = new TypeAdapterConfig(); + config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + config.NewConfig() + .MapUsing(dest=> dest, src => src.SrcData, cfg => + { + cfg.SkipDestinationTransforms() + .ReConfigurate() + .Map(dest=>dest.Data, src => 42) + .MapUsing(dest => dest.Collection, src => src.Collection, cfg => + { + cfg + .SkipDestinationTransforms(); + }) + ; + }); + + var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello" } }; + + var result = src.Adapt(config); + + result.Collection.ShouldBeNull(); + result.Data.ShouldBe(42); + } + + + [TestMethod] + public void ReMapSettersIsWorked() + { + var config = new TypeAdapterConfig(); + config.Default.AddDestinationTransform(DestinationTransform.EmptyCollectionIfNull); + config.ForDestinationType() + .Ignore(x => x.Value) + .Ignore(x => x.Data); + config.NewConfig() + .ReMap(dest => dest, src => src.SrcData, true); + config.NewConfig() + .ReMap(dest => dest.Data, src => src.Data); + + var src = new SourceFlattentInsaider() { SrcData = new() { Value = "Hello", Data = 42 } }; + var reMapSrc = new RemapMemberMappings { Data = 21, Value = "World" }; + + + + var result = src.Adapt(config); + + result.Collection.ShouldBeNull(); + result.Value.ShouldBe("Hello"); + result.Data.ShouldBe(42); + + var reMapResut = reMapSrc.Adapt(config); + + reMapResut.Data.ShouldBe(21); + reMapResut.Value.ShouldBe(default); + } + + [TestMethod] + public void ApplyPropagantionUsingDeepSrcAnalize() + { + var config = new TypeAdapterConfig(); + + config.NewConfig() + .Map(dest => dest.ProductNames, src => src.Products.Select(x => x.Name).ToArray()); + + config.NewConfig() + .Map(dest => dest.ProductNames, src => src.Products.Select(x => x.Name).ToArray()); + + config.NewConfig() + .Map(dest => dest.Result, src => $"{src.Value1.ToString()}"); + + config.NewConfig() + .Map(dest => dest.Result, src => $"{src.Value1.ToString()}"); + + var src = new Source1004(); + var srcStrings = new NullableStrings(); + + //var str = src.BuildAdapter(config).CreateMapExpression(); + //var str2 = src.BuildAdapter(config).CreateMapExpression(); + + Should.NotThrow(() => + { + src.Adapt(config); + src.Adapt(config); + + srcStrings.Adapt(config); + srcStrings.Adapt(config); + }); + } + + #region TestClasses + + class Source1004 + { + public Product1004[]? Products { get; set; } + } + + class Product1004 + { + public required string Name { get; set; } + } + + class Destination1004 + { + public string[]? ProductNames { get; set; } + } + + class DestinationCtor1004 + { + public DestinationCtor1004(string[]? productNames) + { + ProductNames = productNames; + } + + public string[]? ProductNames { get; } + } + + public class NullableStrings + { + public string Value1 { get; set; } + + public string Value2 { get; set; } + } + + public class NullableStringsDest + { + public string Result { get; set; } + } + public class NullableStringsDestCtor + { + public NullableStringsDestCtor(string result) + { + Result = result; + } + + public string Result { get;} + } + + public class RemapMemberMappings + { + public int Data { get; set; } + public string Value { get; set; } + } + + public class DestinationFlattentData + { + public int Data { get; set; } + public string Value { get; set; } + public List Collection { get; set; } + } + + public class SourceFlattentData + { + public int Data { get; set; } + public string Value { get; set; } + public List Collection { get; set; } + } + + public class SourceFlattentInsaider + { + public SourceFlattentData SrcData { get; set; } + } + + public class NullableIntCtorParam + { + public NullableIntCtorParam(int? data) + { + Data = data; + } + public int? Data { get; } + } + + + public class NullableIntInsaider + { + public int? Data { get; set; } + } + + public class NullableIntInsaiderReconfig + { + public int? Data { get; set; } + } + class CollectionPocoWithArray + { + public int[] Array { get; set; } + } + + class CollectionDtoWithArray + { + public int[] Array { get; set; } + } + + class CollectionPocoOverride + { + public Guid Id { get; set; } + public string Name { get; set; } + + public List Children { get; set; } + public int[] Array { get; set; } + public double[,] MultiDimentionalArray { get; set; } + public Dictionary ChildDict { get; set; } + public HashSet Set { get; set; } + } + + class CollectionDtoOverride + { + public Guid Id { get; set; } + public string Name { get; set; } + + public IReadOnlyList Children { get; internal set; } + public int[] Array { get; set; } + public double[,] MultiDimentionalArray { get; set; } + public IReadOnlyDictionary ChildDict { get; set; } + public ISet Set { get; set; } + } + #endregion TestClasses + } +} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 58e95e86..cf2c212d 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -97,7 +97,7 @@ protected virtual Expression CreateExpressionBody(Expression source, Expression? if (arg.Context.MaxDepth.HasValue) { if (ObjectType != ObjectType.Primitive && arg.Context.Depth >= arg.Context.MaxDepth.Value) - return arg.DestinationType.CreateDefault(); + return arg.DestinationType.CreateDefault(arg); if (ObjectType == ObjectType.Class) arg.Context.Depth++; } @@ -208,7 +208,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de /// Not create destination is abstract type if source is null if (arg.DestinationType.IsAbstract) blocks.Add(Expression.IfThen(Expression.Equal(source, Expression.Constant(null, arg.SourceType)), - Expression.Return(label, Expression.Default(arg.DestinationType)))); + Expression.Return(label, arg.DestinationType.CreateDefault(arg)))); //new TDest(); Expression transformedSource = source; @@ -259,7 +259,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de var compareNull = Expression.Equal(source, Expression.Constant(null, source.Type)); blocks.Add( Expression.IfThen(compareNull, - Expression.Return(label, arg.DestinationType.CreateDefault())) + Expression.Return(label, arg.DestinationType.CreateDefault(arg))) ); } @@ -351,7 +351,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de } } - blocks.Add(Expression.Label(label, arg.DestinationType.CreateDefault())); + blocks.Add(Expression.Label(label, arg.DestinationType.CreateDefault(arg))); return Expression.Block(vars, blocks); } @@ -388,9 +388,12 @@ private static Expression InvokeMapping( if (exp == null) return null; + if(arg.MapType == MapType.CtorParam) + return exp; + //projection null is handled by EF if (arg.MapType != MapType.Projection) - exp = source.NotNullReturn(exp); + exp = source.NotNullReturn(exp,arg); return exp; } @@ -448,9 +451,10 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex } } - internal static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null) + internal static Expression CreateAdaptExpressionCore(Expression source, Type destinationType, CompileArgument arg, MemberMapping? mapping = null, Expression? destination = null, MapType? mapTypeCtor = null) { - var mapType = arg.MapType == MapType.MapToTarget && destination == null ? MapType.Map : + var mapType = mapTypeCtor != null ? mapTypeCtor.Value: + arg.MapType == MapType.MapToTarget && destination == null ? MapType.Map : mapping?.UseDestinationValue == true ? MapType.MapToTarget : arg.MapType; var extraParams = new HashSet(); @@ -512,7 +516,9 @@ internal Expression CreateAdaptExpression(Expression source, Type destinationTyp //transform(adapt(_source)); if (notUsingDestinationValue) { - var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(exp.Type)); + var settings = mapping?.OverrideSettings ?? arg.Settings; + + var transform = settings.DestinationTransforms.Find(it => it.Condition(exp.Type)); if (transform != null) exp = transform.TransformFunc(exp.Type).Apply(arg.MapType, exp); } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 140f8b18..418270e2 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -25,24 +25,31 @@ protected ClassMapping CreateClassConverter(Expression source, ClassModel classM if (arg.Settings.IgnoreNonMapped == true) IgnoreNonMapped(classModel,arg); - var sources = new List {source}; + var sources = new List {new ResolverSourceInput(source)}; sources.AddRange( - arg.Settings.ExtraSources.Select(src => - src is LambdaExpression lambda - ? lambda.Apply(arg.MapType, source) - : ExpressionEx.PropertyOrFieldPath(source, (string)src))); + arg.Settings.ExtraSources.Select(src => ResolverSourceInput.ConvertFrom(src,source,arg))); foreach (var destinationMember in destinationMembers) { - if (ProcessIgnores(arg, destinationMember, out var ignore) && !ctorMapping) + if (!destinationMember.ShouldMapMember(arg, MemberSide.Destination)) continue; var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable(); if (arg.Settings.IgnoreNonMapped == true) resolvers = resolvers.Where(ValueAccessingStrategy.CustomResolvers.Contains); - var getter = (from fn in resolvers + var resolver = (from fn in resolvers from src in sources select fn(src, destinationMember, arg)) .FirstOrDefault(result => result != null); + var getter = resolver?.Exp; + var overideSettings = resolver?.Settings; + + if (ProcessIgnores(arg, destinationMember,out var ignore, resolver) && !ctorMapping) + continue; + + // ReadyToCleanUp + // source in overideSettings is not source in this context + // if (overideSettings != null && getter != null) + // getter = ReplaceOvverideExpressionParam.Replace(getter, source); if (arg.MapType == MapType.Projection && getter != null) { @@ -66,7 +73,7 @@ select fn(src, destinationMember, arg)) getter = (from fn in resolvers from src in sources select fn(src, destinationMember, arg)) - .FirstOrDefault(result => result != null); + .FirstOrDefault(result => result != null)?.Exp; } @@ -98,7 +105,6 @@ select fn(src, destinationMember, arg)) } - var nextIgnore = arg.Settings.Ignore.Next((ParameterExpression)source, (ParameterExpression?)destination, destinationMember.Name); var nextResolvers = arg.Settings.Resolvers.Next(arg.Settings.Ignore, (ParameterExpression)source, destinationMember.Name) .ToList(); @@ -112,6 +118,7 @@ select fn(src, destinationMember, arg)) Source = (ParameterExpression)source, Destination = (ParameterExpression?)destination, UseDestinationValue = IsCanUsingDestinationValue(arg, destinationMember), + OverrideSettings = overideSettings }; if(arg.MapType == MapType.ApplyNullPropagation && getter == null && !arg.DestinationType.IsRecordType() @@ -120,19 +127,19 @@ select fn(src, destinationMember, arg)) if (propinfo.GetCustomAttributes() .Any(y => y.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute")) { - getter = destinationMember.Type.CreateDefault(); + getter = destinationMember.Type.CreateDefault(arg); } } if (arg.MapType == MapType.MapToTarget && getter == null && arg.DestinationType.IsRecordType()) { - getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination) ?? getter; + getter = TryRestoreRecordMember(destinationMember, recordRestorMemberModel, destination, arg) ?? getter; } if (getter != null) { - propertyModel.Getter = arg.MapType == MapType.Projection - ? getter - : getter.ApplyPropertyNullPropagation(); + propertyModel.Getter = arg.MapType == MapType.Projection || ctorMapping + ? getter + : getter.ApplyPropertyNullPropagation(arg, source); properties.Add(propertyModel); } else @@ -202,10 +209,20 @@ protected static bool IsCanUsingDestinationValue(CompileArgument arg, IMemberMod protected static bool ProcessIgnores( CompileArgument arg, - IMemberModel destinationMember, - out IgnoreDictionary.IgnoreItem ignore) + IMemberModel destinationMember, + out IgnoreDictionary.IgnoreItem ignore, + ResolverResult? resolver = null) { ignore = new IgnoreDictionary.IgnoreItem(); + + if (resolver?.Settings != null) + { + if(resolver.Settings.ReMapExtraSource.GetValueOrDefault() + || resolver.Settings.ReMapDestination.Contains(destinationMember.Name) + || arg.Settings.ReMapDestinationMembers.Contains(destinationMember.Name)) + return false; + } + if (!destinationMember.ShouldMapMember(arg, MemberSide.Destination)) return true; @@ -218,7 +235,8 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi var members = classConverter.Members; var arguments = new List(); - arg.Context.NullChecks.UnionWith(members.Where(x => x.Getter != null).Select(x => (x.Getter, arg))); + // ReadyToCleanUp + // arg.Context.NullChecks.UnionWith(members.Where(x => x.Getter != null).Select(x => (x.Getter, arg))); foreach (var member in members) { var parameterInfo = (ParameterInfo)member.DestinationMember.Info!; @@ -230,17 +248,17 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi { defaultConst = parameterInfo.IsOptional && parameterInfo.DefaultValue != null ? Expression.Constant(parameterInfo.DefaultValue, member.DestinationMember.Type) - : parameterInfo.ParameterType.CreateDefault(); + : parameterInfo.ParameterType.CreateDefault(arg); } catch (FormatException) { - defaultConst = parameterInfo.ParameterType.CreateDefault(); + defaultConst = parameterInfo.ParameterType.CreateDefault(arg); } #else defaultConst = parameterInfo.IsOptional && parameterInfo.DefaultValue != null ? Expression.Constant(parameterInfo.DefaultValue, member.DestinationMember.Type) - : parameterInfo.ParameterType.CreateDefault(); + : parameterInfo.ParameterType.CreateDefault(arg); #endif if (member.Getter == null) @@ -248,7 +266,7 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) - getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination) ?? getter; + getter = TryRestoreRecordMember(member.DestinationMember,recordRestorParamModel,destination, arg) ?? getter; } else { @@ -264,14 +282,15 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi } else getter = member.Getter - .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member), arg); + .ApplyNullPropagationFromCtor(CreateAdaptExpressionCore(member.Getter, member.DestinationMember.Type, arg, member,mapTypeCtor:MapType.CtorParam), arg, member); + if (member.Ignore.Condition != null) { var body = member.Ignore.IsChildPath ? member.Ignore.Condition.Body - : member.Ignore.Condition.Apply(arg.MapType, source, arg.DestinationType.CreateDefault()); + : member.Ignore.Condition.Apply(arg.MapType, source, arg.DestinationType.CreateDefault(arg)); var condition = ExpressionEx.Not(body); getter = Expression.Condition(condition, getter, defaultConst); } @@ -281,8 +300,9 @@ protected Expression CreateInstantiationExpression(Expression source, ClassMappi getter = defaultConst; if (arg.MapType == MapType.MapToTarget && arg.DestinationType.IsRecordType()) - getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination) ?? getter; + getter = TryRestoreRecordMember(member.DestinationMember, recordRestorParamModel, destination, arg) ?? getter; } + } arguments.Add(getter); } @@ -335,7 +355,7 @@ protected virtual ClassModel GetOnlyRequiredPropertySetterModel(CompileArgument }; } - protected Expression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination) + protected Expression? TryRestoreRecordMember(IMemberModelEx member, ClassModel? restorRecordModel, Expression? destination, CompileArgument arg) { if (restorRecordModel != null && destination != null) { @@ -345,7 +365,7 @@ protected virtual ClassModel GetOnlyRequiredPropertySetterModel(CompileArgument if (find != null) { var compareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type)); - return Expression.Condition(compareNull, member.Type.CreateDefault(), Expression.MakeMemberAccess(destination, (MemberInfo)find.Info)); + return Expression.Condition(compareNull, member.Type.CreateDefault(arg), Expression.MakeMemberAccess(destination, (MemberInfo)find.Info)); } } diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index d71067b0..c7e9d253 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -253,8 +253,8 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre if (member.UseDestinationValue) return null; - if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) - && member.Getter is MemberExpression memberExp && contructorMembers.Contains(memberExp.Member)) + if (!arg.Settings.Resolvers.Any(r => r.DestinationMemberName == member.DestinationMember.Name) + && contructorMembers.Select(x => x.Name).Contains(member.DestinationMember.Name, new MapsterStringComparer())) continue; if (member.DestinationMember.SetterModifier == AccessModifier.None) @@ -271,7 +271,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre && !member.DestinationMember.Type.IsCollection() && member.Getter.Type.GetTypeInfo().GetCustomAttributesData().All(attr => attr.GetAttributeType().Name != "ComplexTypeAttribute")) { - value = member.Getter.NotNullReturn(value); + value = member.Getter.NotNullReturn(value,arg); } var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, value); lines.Add(bind); @@ -282,7 +282,7 @@ private static Expression SetValueByReflection(MemberMapping member, MemberExpre static Expression CreateIncludeProjectionExpression(Expression source, CompileArgument arg) { - Expression body = Expression.Default(arg.DestinationType); + Expression body = arg.DestinationType.CreateDefault(arg); foreach (var tuple in arg.Settings.Includes) { var itemTuple = tuple; diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index 1f5ef77e..cc2ad2db 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -159,7 +159,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio actions.Add(loop); if (label != null) - actions.Add(Expression.Label(label, arg.DestinationType.CreateDefault())); + actions.Add(Expression.Label(label, arg.DestinationType.CreateDefault(arg))); return shouldConvert ? Expression.Block(new[] {(ParameterExpression)dict}, actions) diff --git a/src/Mapster/Adapters/NullableAdapter.cs b/src/Mapster/Adapters/NullableAdapter.cs index 178c3266..ca83790e 100644 --- a/src/Mapster/Adapters/NullableAdapter.cs +++ b/src/Mapster/Adapters/NullableAdapter.cs @@ -26,6 +26,9 @@ protected override bool CanInline(Expression source, Expression? destination, Co ? Expression.Convert(source, source.Type.GetGenericArguments()[0]) : source; + //var destType = arg.DestinationType.GetNotNullableTypeDefenition(); + //var customArg = arg.Context.Config.GetCompileArgument(_source.Type, destType, arg.MapType, arg.Context); + Expression adapt = CreateAdaptExpression(_source, arg.DestinationType.GetNotNullableTypeDefenition(),arg); return adapt.ToNullableExp(arg); diff --git a/src/Mapster/Adapters/PrimitiveAdapter.cs b/src/Mapster/Adapters/PrimitiveAdapter.cs index 622f32b1..ead180d1 100644 --- a/src/Mapster/Adapters/PrimitiveAdapter.cs +++ b/src/Mapster/Adapters/PrimitiveAdapter.cs @@ -27,7 +27,7 @@ protected override Expression CreateExpressionBody(Expression source, Expression if (destination == null) { - dest = arg.DestinationType.CreateDefault(); + dest = arg.DestinationType.CreateDefault(arg); } else dest = destination; @@ -56,7 +56,7 @@ protected override Expression CreateExpressionBody(Expression source, Expression { //src == null ? default(TDestination) : convert(src) var compareNull = Expression.Equal(source, Expression.Constant(null, sourceType)); - convert = Expression.Condition(compareNull, destinationType.CreateDefault(), convert); + convert = Expression.Condition(compareNull, destinationType.CreateDefault(arg), convert); } } diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index ab318caa..18954ef2 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -98,7 +98,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E } var destinationCompareNull = Expression.Equal(destination, Expression.Constant(null, destination.Type)); var sourceCondition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); - var destinationCanbeNull = Expression.Condition(destinationCompareNull, member.DestinationMember.Type.CreateDefault(), member.DestinationMember.GetExpression(destination)); + var destinationCanbeNull = Expression.Condition(destinationCompareNull, member.DestinationMember.Type.CreateDefault(arg), member.DestinationMember.GetExpression(destination)); adapt = Expression.Condition(sourceCondition, adapt, destinationCanbeNull); } } @@ -134,7 +134,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E && !member.DestinationMember.Type.IsCollection() && member.Getter.Type.GetTypeInfo().GetCustomAttributesData().All(attr => attr.GetAttributeType().Name != "ComplexTypeAttribute")) { - adapt = member.Getter.NotNullReturn(adapt); + adapt = member.Getter.NotNullReturn(adapt,arg); } var bind = Expression.Bind((MemberInfo)member.DestinationMember.Info!, adapt); lines.Add(bind); diff --git a/src/Mapster/Compile/CompileContext.cs b/src/Mapster/Compile/CompileContext.cs index 72d640c8..0c21abc7 100644 --- a/src/Mapster/Compile/CompileContext.cs +++ b/src/Mapster/Compile/CompileContext.cs @@ -12,7 +12,9 @@ public class CompileContext public int? MaxDepth { get; set; } public int Depth { get; set; } public HashSet ExtraParameters { get; } = new(); - public HashSet<(Expression param, CompileArgument arg)> NullChecks { get; } = new(); + + // ReadyToCleanUp + // public HashSet<(Expression param, CompileArgument arg)> NullChecks { get; } = new(); internal bool IsSubFunction() { diff --git a/src/Mapster/Models/ExtraSourceModel.cs b/src/Mapster/Models/ExtraSourceModel.cs new file mode 100644 index 00000000..94469111 --- /dev/null +++ b/src/Mapster/Models/ExtraSourceModel.cs @@ -0,0 +1,10 @@ +using System.Linq.Expressions; + +namespace Mapster.Models +{ + public record ExtraSourceModel(object Src, OverrideTypesSettings? Settings = null) + { + public static explicit operator ExtraSourceModel(Expression src) => new ExtraSourceModel(src); + public static explicit operator ExtraSourceModel(string src) => new ExtraSourceModel(src); + } +} diff --git a/src/Mapster/Models/InvokerModel.cs b/src/Mapster/Models/InvokerModel.cs index b5a4546d..bad43ecc 100644 --- a/src/Mapster/Models/InvokerModel.cs +++ b/src/Mapster/Models/InvokerModel.cs @@ -1,5 +1,6 @@ -using System.Linq.Expressions; -using Mapster.Utils; +using Mapster.Utils; +using System.Collections.Generic; +using System.Linq.Expressions; namespace Mapster.Models { @@ -9,6 +10,7 @@ public class InvokerModel public LambdaExpression? Invoker { get; set; } public string? SourceMemberName { get; set; } public LambdaExpression? Condition { get; set; } + public TypeAdapterSettings? OvverideSettings { get; set; } public bool IsChildPath { get; set; } public InvokerModel? Next(ParameterExpression source, string destMemberName) @@ -30,20 +32,34 @@ public class InvokerModel }; } - public Expression GetInvokingExpression(Expression exp, MapType mapType = MapType.Map) + public Expression GetInvokingExpression(Expression exp, MapType mapType = MapType.Map, bool isExtraParam = false) { if (IsChildPath) return Invoker!.Body; return SourceMemberName != null ? ExpressionEx.PropertyOrFieldPath(exp, SourceMemberName) - : Invoker!.Apply(mapType, exp); + : isExtraParam ? Invoker!.ApplyExtraSources(mapType, exp) : Invoker!.Apply(mapType, exp); } - public Expression? GetConditionExpression(Expression exp, MapType mapType = MapType.Map) + public Expression? GetConditionExpression(Expression exp, MapType mapType = MapType.Map, bool isExtraParam = false) { return IsChildPath ? Condition?.Body - : Condition?.Apply(mapType, exp); + : isExtraParam ? Condition?.ApplyExtraSources(mapType, exp) : Condition?.Apply(mapType, exp); + } + } + + public class InvokerModelApplyComparer : IEqualityComparer + { + public bool Equals(InvokerModel? x, InvokerModel? y) + { + if (x is null || y is null) return false; + return string.Equals(x.DestinationMemberName, y.DestinationMemberName, System.StringComparison.InvariantCulture); + } + + public int GetHashCode(InvokerModel obj) + { + return obj?.DestinationMemberName?.GetHashCode() ?? 0; } } } \ No newline at end of file diff --git a/src/Mapster/Models/MemberMapping.cs b/src/Mapster/Models/MemberMapping.cs index d047e4ba..008277d6 100644 --- a/src/Mapster/Models/MemberMapping.cs +++ b/src/Mapster/Models/MemberMapping.cs @@ -13,6 +13,7 @@ internal class MemberMapping public ParameterExpression Source; public ParameterExpression? Destination; public bool UseDestinationValue; + public TypeAdapterSettings? OverrideSettings; public bool HasSettings() { diff --git a/src/Mapster/Settings/SettingStore.cs b/src/Mapster/Settings/SettingStore.cs index 821b8569..fbf34b9b 100644 --- a/src/Mapster/Settings/SettingStore.cs +++ b/src/Mapster/Settings/SettingStore.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; namespace Mapster { @@ -46,19 +48,18 @@ public T Get(string key, Func initializer) where T : class return (T)_objectStore.GetOrAdd(key, _ => initializer()); } - public virtual void Apply(object other) - { - if (other is SettingStore settingStore) - Apply(settingStore); - } - public void Apply(SettingStore other) + + private void ApplyBoolSettings (IEnumerable> otherBoolStore) { - foreach (var kvp in other._booleanStore) + foreach (var kvp in otherBoolStore) { _booleanStore.TryAdd(kvp.Key, kvp.Value); } + } - foreach (var kvp in other._objectStore) + private void ApplyObjectSettings(IEnumerable> otherBoolStore) + { + foreach (var kvp in otherBoolStore) { var self = _objectStore.GetOrAdd(kvp.Key, key => { @@ -80,5 +81,26 @@ public void Apply(SettingStore other) } } } + + + public virtual void Apply(object other) + { + if (other is SettingStore settingStore) + Apply(settingStore); + } + + + public virtual void Apply(SettingStore other) + { + ApplyBoolSettings(other._booleanStore); + ApplyObjectSettings(other._objectStore); + } + + public virtual void ApplyWithSkipSettings(SettingStore other, List skipSettingNames) + { + ApplyBoolSettings(other._booleanStore.Where(x => !skipSettingNames.Contains(x.Key))); + ApplyObjectSettings(other._objectStore.Where(x => !skipSettingNames.Contains(x.Key))); + } + } } \ No newline at end of file diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index 4fb608dc..c045d750 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -5,7 +5,7 @@ using System.Reflection; using Mapster.Models; using Mapster.Utils; -using ValueAccess = System.Func; +using ValueAccess = System.Func; namespace Mapster { @@ -24,12 +24,14 @@ public static class ValueAccessingStrategy CustomResolverForDictionary, }; - private static Expression? CustomResolverFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? CustomResolverFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var config = source.Type == arg.SourceType ? arg.Settings : arg.Context.Config.GetMergedSettings(new TypeTuple(source.Type, arg.DestinationType),arg.MapType); - var resolvers = config.Resolvers; + var resolvers = srcInput.Settings != null ? srcInput.Settings.ApplyResolversOnly(config) : config.Resolvers; if (resolvers.Count == 0) return null; + TypeAdapterSettings? customSettings = null; var invokes = new List>(); @@ -39,7 +41,10 @@ public static class ValueAccessingStrategy if (!destinationMember.Name.Equals(resolver.DestinationMemberName, StringComparison.InvariantCultureIgnoreCase)) continue; - var invoke = resolver.GetInvokingExpression(source, arg.MapType); + if(resolver.OvverideSettings != null && customSettings == null) + customSettings = resolver.OvverideSettings; + + var invoke = resolver.GetInvokingExpression(source, arg.MapType, customSettings != null); var condition = resolver.GetConditionExpression(source, arg.MapType); if (condition == null) { @@ -58,7 +63,7 @@ public static class ValueAccessingStrategy var type = invokes[0].Item2.Type; if (destinationMember.Type.CanBeNull() && !type.CanBeNull()) type = typeof(Nullable<>).MakeGenericType(type); - getter = type.CreateDefault(); + getter = type.CreateDefault(arg); } foreach (var invoke in invokes) { @@ -66,23 +71,33 @@ public static class ValueAccessingStrategy } } - return getter; + if (getter == null) + return null; + return new ResolverResult(getter,(OverrideTypesSettings?)customSettings); } - private static Expression? PropertyOrFieldFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? PropertyOrFieldFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var members = source.Type.GetFieldsAndProperties(true); var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); - return members + var resolver = members .Where(member => member.ShouldMapMember(arg, MemberSide.Source)) .Where(member => member.GetMemberName(MemberSide.Source, arg.Settings.GetMemberNames, strategy.SourceMemberNameConverter, arg) == destinationMemberName) .Select(member => member.GetExpression(source)) .FirstOrDefault(); + + if (resolver == null) + return null; + else + return new ResolverResult(resolver, srcInput.Settings != null ? srcInput.Settings.CloneOnlySkipSettings() : null); + } - private static Expression? GetMethodFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? GetMethodFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; if (arg.MapType == MapType.Projection) return null; var strategy = arg.Settings.NameMatchingStrategy; @@ -92,14 +107,18 @@ public static class ValueAccessingStrategy return null; if (getMethod.Name == "GetType" && destinationMember.Type != typeof(Type)) return null; - return Expression.Call(source, getMethod); + return new ResolverResult( Expression.Call(source, getMethod),null); } - private static Expression? FlattenMemberFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? FlattenMemberFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(MemberSide.Destination, arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter, arg); - return GetDeepFlattening(source, destinationMemberName, arg); + var resolver = GetDeepFlattening(source, destinationMemberName, arg); + if(resolver == null) + return null; + return new ResolverResult(resolver, null); } private static Expression? GetDeepFlattening(Expression source, string propertyName, CompileArgument arg) @@ -177,8 +196,9 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM } } - private static Expression? DictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? DictionaryFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var dictType = source.Type.GetDictionaryType(); if (dictType == null) return null; @@ -192,19 +212,22 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM var method = typeof(MapsterHelper).GetMethods() .First(m => m.Name == nameof(MapsterHelper.FlexibleGet) && m.GetParameters()[0].ParameterType.Name == dictType.Name) .MakeGenericMethod(args[1]); - return Expression.Call(method, source.To(dictType), key, ExpressionEx.GetNameConverterExpression(strategy.SourceMemberNameConverter)); + var resolver = Expression.Call(method, source.To(dictType), key, ExpressionEx.GetNameConverterExpression(strategy.SourceMemberNameConverter)); + return new ResolverResult(resolver); } else { var method = typeof(MapsterHelper).GetMethods() .First(m => m.Name == nameof(MapsterHelper.GetValueOrDefault) && m.GetParameters()[0].ParameterType.Name == dictType.Name) .MakeGenericMethod(args); - return Expression.Call(method, source.To(dictType), key); + var resolver = Expression.Call(method, source.To(dictType), key); + return new ResolverResult(resolver); } } - private static Expression? CustomResolverForDictionaryFn(Expression source, IMemberModel destinationMember, CompileArgument arg) + private static ResolverResult? CustomResolverForDictionaryFn(ResolverSourceInput srcInput, IMemberModel destinationMember, CompileArgument arg) { + var source = srcInput.Src; var config = arg.Settings; var resolvers = config.Resolvers; if (resolvers.Count == 0) @@ -235,8 +258,22 @@ private static IEnumerable GetDeepUnflattening(IMemberModel destinationM break; } if (lastCondition != null) - getter = Expression.Condition(lastCondition, getter!, getter!.Type.CreateDefault()); - return getter; + getter = Expression.Condition(lastCondition, getter!, getter!.Type.CreateDefault(arg)); + return new ResolverResult(getter); } } + + public record ResolverResult(Expression Exp , OverrideTypesSettings? Settings = null); + public record ResolverSourceInput(Expression Src, OverrideTypesSettings? Settings = null) + { + public static explicit operator ResolverSourceInput(Expression src) => new ResolverSourceInput(src); + public static explicit operator ResolverSourceInput(ParameterExpression src) => new ResolverSourceInput(src); + public static ResolverSourceInput ConvertFrom(ExtraSourceModel extraSource,Expression source, CompileArgument arg) + { + if (extraSource.Src is LambdaExpression lambda) + return new ResolverSourceInput(lambda.ApplyExtraSources(arg.MapType, source), extraSource.Settings); + else + return new ResolverSourceInput(ExpressionEx.PropertyOrFieldPath(source, (string)extraSource.Src), extraSource.Settings); + } + }; } diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index b05c1ba7..18e25194 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -43,9 +43,13 @@ public static ITypeAdapterBuilder BuildAdapter(this TSource so public static TDestination? Adapt(this object? source, TypeAdapterConfig config) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse + Type? type; + if (source == null) - return default; - var type = source.GetType(); + type = typeof(Object); + else + type = source.GetType(); + var fn = config.GetDynamicMapFunction(type); return fn(source)!; } diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 08db194a..19e4dc64 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -450,7 +450,7 @@ private static LambdaExpression AdjustInheritedConverterReturnType(LambdaExpress { if(arg.Settings.ApplyCustomConverterFactoryNullPropagation.GetValueOrDefault()) - lambda = Expression.Lambda(lambda.Parameters[0].NotNullReturn(lambda.Body),lambda.Parameters); + lambda = Expression.Lambda(lambda.Parameters[0].NotNullReturn(lambda.Body,arg),lambda.Parameters); var destinationType = arg.DestinationType; var returnType = lambda.ReturnType; @@ -495,7 +495,7 @@ private static LambdaExpression AdjustInheritedConverterReturnType(LambdaExpress var condition = Expression.TypeIs(tempDest, destinationType); UnaryExpression ifTrue = Expression.Convert(tempDest, destinationType); - DefaultExpression ifFalse = Expression.Default(destinationType); + Expression ifFalse = destinationType.CreateDefault(arg); ConditionalExpression conditionalExpr = Expression.Condition(condition, ifTrue, ifFalse); blockbody.Add(conditionalExpr); @@ -544,6 +544,26 @@ internal LambdaExpression CreateInlineMapExpression(Type sourceType, Type destin arg.Settings.Resolvers.AddRange(mapping.NextResolvers); arg.Settings.Ignore.Apply(mapping.NextIgnore); arg.UseDestinationValue = mapping.UseDestinationValue; + + if (mapping.OverrideSettings != null) + { + mapping.OverrideSettings.Apply(arg.Settings); + + if(mapping.OverrideSettings.ConverterFactory == null || mapping.OverrideSettings.ConverterToTargetFactory == null) + { + var defaultfactory = GetOvverideDefaultSettings(tuple, mapType); + + if (mapping.OverrideSettings.ConverterFactory == null) + mapping.OverrideSettings.ConverterFactory = defaultfactory.ConverterFactory; + if (mapping.OverrideSettings.ConverterToTargetFactory == null) + mapping.OverrideSettings.ConverterToTargetFactory = defaultfactory.ConverterToTargetFactory; + + } + + + arg.Settings = mapping.OverrideSettings; + } + } return CreateMapExpression(arg); @@ -697,6 +717,36 @@ orderby priority.Value descending return result; } + internal TypeAdapterSettings GetOvverideDefaultSettings(TypeTuple tuple, MapType mapType) + { + var arg = new PreCompileArgument + { + SourceType = tuple.Source, + DestinationType = tuple.Destination, + MapType = mapType, + ExplicitMapping = true, + }; + + var result = new TypeAdapterSettings(); + + var rules = RulesTemplate.Reverse(); + var settings = from rule in rules + let priority = rule.Priority(arg) + where priority != null + orderby priority.Value descending + select rule.Settings; + foreach (var setting in settings) + { + result.Apply(setting); + } + + return result; + } + + internal CompileArgument GetCompileArgument(Type sourcetype, Type destintaiontype, MapType mapType, CompileContext context) + { + return GetCompileArgument(new TypeTuple(sourcetype, destintaiontype), mapType, context); + } private CompileArgument GetCompileArgument(TypeTuple tuple, MapType mapType, CompileContext context) { var setting = GetMergedSettings(tuple, mapType); diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 7a78af01..01d4bbae 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -413,6 +413,15 @@ public TypeAdapterSetter Ignore(params Expression DefaultValue(Expression> defaultValue) + { + this.CheckCompiled(); + + Settings.CustomDefaultValue = defaultValue.Body; + + return this; + } + public TypeAdapterSetter Map( Expression> member, Expression> source) @@ -422,7 +431,7 @@ public TypeAdapterSetter Map( var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof (object))); if (member.IsIdentity()) { - Settings.ExtraSources.Add(invoker); + Settings.ExtraSources.Add((ExtraSourceModel)invoker); return this; } @@ -443,7 +452,7 @@ public TypeAdapterSetter Map( if (destinationMember.IsIdentity()) { - Settings.ExtraSources.Add(sourceMemberName); + Settings.ExtraSources.Add((ExtraSourceModel)sourceMemberName); return this; } @@ -628,6 +637,81 @@ public TypeAdapterSetter IgnoredRemove(params Expression MapUsing( + Expression> member, + Expression> source, + Action>? configAction = null) + { + this.CheckCompiled(); + + var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof(TSource))); + TypeAdapterSettings? overrideSettings = null; + + if (configAction != null) + { + var Tempsetter = new OverrideTypesSetter(this.Config); + configAction(Tempsetter); + + overrideSettings = Tempsetter.Settings; + } + + if (member.IsIdentity()) + { + Settings.ExtraSources.Add(new ExtraSourceModel(invoker, (OverrideTypesSettings?)overrideSettings)); + return this; + } + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = member.GetMemberPath()!, + Invoker = invoker, + Condition = null, + OvverideSettings = overrideSettings + }); + return this; + } + + public TypeAdapterSetter ReMap( + Expression> member, + Expression> source, + bool SkipDestinationTransforms = false) + { + this.CheckCompiled(); + + + + var invoker = Expression.Lambda(source.Body, Expression.Parameter(typeof(TSource))); + TypeAdapterSettings? overrideSettings = null; + + var Tempsetter = new OverrideTypesSetter(this.Config); + overrideSettings = Tempsetter.Settings; + + + if (SkipDestinationTransforms) + Tempsetter.SkipDestinationTransforms(); + + if (member.IsIdentity()) + { + Tempsetter._Settings.ReMapExtraSource = true; + + Settings.ExtraSources.Add(new ExtraSourceModel(invoker, (OverrideTypesSettings?)overrideSettings)); + return this; + } + + this.Settings.ReMapDestinationMembers.Add(member.GetMemberPath()!); + + Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = member.GetMemberPath()!, + Invoker = invoker, + Condition = null, + OvverideSettings = overrideSettings + }); + return this; + } + + public TypeAdapterSetter IgnoreIf( Expression> condition, @@ -665,7 +749,7 @@ public TypeAdapterSetter Map (OverrideTypesSettings)Settings; } + + public OverrideTypesSetter(TypeAdapterConfig config) : this (new OverrideTypesSettings (), config) { } + public OverrideTypesSetter(TypeAdapterSettings settings, TypeAdapterConfig config) : base(settings, config) { } + } + + public class OverrideTypesSetter : OverrideTypesSetter + { + public OverrideTypesSetter(TypeAdapterConfig config) : base(config) + { + } + + public OverrideTypesSetter(TypeAdapterSettings settings, TypeAdapterConfig config) : base(settings, config) + { + } + + public OverrideTypesSetter SkipAllSettings(bool value) + { + _Settings.SkipAllSettings = value; + return this; + } + + [Obsolete("This method will be removed in the release version." + + "It is used for debugging and finding settings that cannot be overridden by existing settings setters.")] + public OverrideTypesSetter SkipSettings(params Expression>[] settings) + { + foreach (var member in settings) + { + _Settings.SkipSettings.Add(member.GetMemberPath()!); + } + + return this; + } + + public TypeAdapterSetter ReConfigurate() + { + return new TypeAdapterSetter(this.Settings,this.Config); + } + + public OverrideTypesSetter SkipDestinationTransforms() + { + this.SkipSettings(x => x.DestinationTransforms); + return this; + } + } + + +} diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs index 65f8b6e4..f03404c1 100644 --- a/src/Mapster/TypeAdapterSettings.cs +++ b/src/Mapster/TypeAdapterSettings.cs @@ -134,17 +134,17 @@ public Dictionary ProjectToTypeResolvers { get => Get(nameof(ShouldMapMember), () => new List>()); } - public List> ValueAccessingStrategies + public List> ValueAccessingStrategies { - get => Get(nameof(ValueAccessingStrategies), () => new List>()); + get => Get(nameof(ValueAccessingStrategies), () => new List>()); } public List Resolvers { get => Get(nameof(Resolvers), () => new List()); } - public List ExtraSources + public List ExtraSources { - get => Get(nameof(ExtraSources), () => new List()); + get => Get(nameof(ExtraSources), () => new List()); } public List> BeforeMappingFactories { @@ -202,6 +202,17 @@ public List UseDestinationMembers get => Get(nameof(UseDestinationMembers), () => new List()); } + public Expression? CustomDefaultValue + { + get => Get(nameof(CustomDefaultValue)); + set => Set(nameof(CustomDefaultValue), value); + } + + public List ReMapDestinationMembers + { + get => Get(nameof(ReMapDestinationMembers), () => new List()); + } + internal bool Compiled { get; set; } public TypeAdapterSettings Clone() diff --git a/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs new file mode 100644 index 00000000..359b13cf --- /dev/null +++ b/src/Mapster/TypeAdapterSettings/OverrideTypesSettings.cs @@ -0,0 +1,71 @@ +using Mapster.Models; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mapster +{ + [AdaptWith(AdaptDirectives.DestinationAsRecord)] + public class OverrideTypesSettings : TypeAdapterSettings + { + public List SkipSettings + { + get => Get(nameof(SkipSettings), () => new List()); + } + + public IEnumerable ReMapDestination + { + get => this.Resolvers.Select(x=>x.DestinationMemberName).Union(ReMapDestinationMembers); + } + + public bool? ReMapExtraSource + { + get => Get(nameof(ReMapExtraSource)); + set => Set(nameof(ReMapExtraSource), value); + } + + public bool? SkipAllSettings + { + get => Get(nameof(SkipAllSettings)); + set => Set(nameof(SkipAllSettings), value); + } + + public override void Apply(object other) + { + if (other is SettingStore settingStore) + Apply(settingStore); + } + + public override void Apply(SettingStore other) + { + if(!SkipAllSettings.GetValueOrDefault()) + base.ApplyWithSkipSettings(other, SkipSettings); + } + + public List ApplyResolversOnly(TypeAdapterSettings other) + { + var result = new List(this.Resolvers); + var seen = new HashSet(result,new InvokerModelApplyComparer()); + + foreach (var item in other.Resolvers) + { + if (seen.Add(item)) + { + result.Add(item); + } + } + return result; + } + + public OverrideTypesSettings CloneOnlySkipSettings() + { + var result = new OverrideTypesSettings(); + + result.SkipAllSettings = this.SkipAllSettings; + result.SkipSettings.AddRange(this.SkipSettings); + result.ReMapExtraSource = this.ReMapExtraSource; + + return result; + } + } +} diff --git a/src/Mapster/Utils/DirectParameterMemberFinder.cs b/src/Mapster/Utils/DirectParameterMemberFinder.cs new file mode 100644 index 00000000..495055fa --- /dev/null +++ b/src/Mapster/Utils/DirectParameterMemberFinder.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; + +public class DirectParameterMemberFinder : ExpressionVisitor +{ + private readonly bool _isCtrMapping; + private readonly HashSet _TargetParams; + public List FoundMembers { get; } = new(); + + public DirectParameterMemberFinder(bool conctructorMapping = false, params Expression[] targetParams) + { + _TargetParams = new HashSet(targetParams); + _isCtrMapping = conctructorMapping; + } + + protected override Expression VisitMember(MemberExpression node) + { + if (_TargetParams.Contains(GetParametr(node))) + if (_isCtrMapping) + FoundMembers.Add(node); + else + FoundMembers.Add(node.Expression); + + return node; + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node.Object is MemberExpression mem && _TargetParams.Contains(GetParametr(mem))) + FoundMembers.Add(mem); + + foreach (var arg in node.Arguments) + { + if (arg is MemberExpression member && _TargetParams.Contains(GetParametr(member))) + { + // if Method is static for Type && not Extention method + if (node.Object == null && !node.Method.IsDefined(typeof(ExtensionAttribute), inherit: false)) + FoundMembers.Add(member.Expression); + else + FoundMembers.Add(member); + continue; + } + + if (arg.NodeType == ExpressionType.Call) + { + Visit(arg); + } + } + + return node; + } + + protected override Expression VisitUnary(UnaryExpression node) + { + if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked) + { + var result = base.VisitUnary(node); + return result; + } + return base.VisitUnary(node); + } + + public IEnumerable Find(Expression expression) + { + FoundMembers.Clear(); + Visit(expression); + + return FoundMembers; + } + + + private Expression GetParametr(MemberExpression member) + { + Expression current = member; + + while (current != null) + { + if (current is MemberExpression mem) + { + current = mem.Expression; + continue; + } + + if (current is ParameterExpression) + return current; + else + current = new ReturnParametrVisitor().GetParam(current); + } + + return Expression.Empty(); + } + + internal class ReturnParametrVisitor : ExpressionVisitor + { + private Expression parametr; + + protected override Expression VisitParameter(ParameterExpression node) + { + parametr = node; + return node; + } + + public Expression GetParam(Expression expression) + { + Visit(expression); + return parametr; + } + } + + +} \ No newline at end of file diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index c55d96c5..6a768755 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -152,6 +152,11 @@ public static Expression Apply(this LambdaExpression lambda, MapType mapType, pa return lambda.Apply(mapType != MapType.Projection, exps); } + public static Expression ApplyExtraSources(this LambdaExpression lambda, MapType mapType, params Expression[] exps) + { + return lambda.ApplyExtraSources(mapType != MapType.Projection, exps); + } + public static Expression Apply(this LambdaExpression lambda, ParameterExpression p1, ParameterExpression? p2 = null) { if (p2 == null) @@ -169,6 +174,15 @@ private static Expression Apply(this LambdaExpression lambda, bool allowInvoke, return Expression.Invoke(lambda, exps); } + private static Expression ApplyExtraSources(this LambdaExpression lambda, bool allowInvoke, params Expression[] exps) + { + var replacer = new ParameterExpressionReplacer(lambda.Parameters,true, exps); + var result = replacer.Visit(lambda.Body); + if (!allowInvoke || !replacer.ReplaceCounts.Where((n, i) => n > 1 && exps[i].IsComplex()).Any()) + return result!; + return Expression.Invoke(lambda, exps); + } + public static LambdaExpression TrimParameters(this LambdaExpression lambda, int skip = 0) { var replacer = new ParameterExpressionReplacer(lambda.Parameters, lambda.Parameters.ToArray()); @@ -371,7 +385,7 @@ public static bool IsMultiLine(this LambdaExpression lambda) return detector.IsBlockExpression; } - public static Expression NotNullReturn(this Expression exp, Expression value) + public static Expression NotNullReturn(this Expression exp, Expression value, CompileArgument arg) { if (value.IsSingleValue() || !exp.CanBeNull()) return value; @@ -379,7 +393,7 @@ public static Expression NotNullReturn(this Expression exp, Expression value) var compareNull = Expression.Equal(exp, Expression.Constant(null, exp.Type)); return Expression.Condition( compareNull, - value.Type.CreateDefault(), + value.Type.CreateDefault(arg), value); } @@ -407,7 +421,114 @@ public static Expression NullableEnumExtractor(this Expression param) return param; } - public static Expression ApplyPropertyNullPropagation(this Expression getter) + + public static Expression ApplyPropertyNullPropagation(this Expression getter, CompileArgument arg, Expression source) + { + var current = getter; + var result = getter; + Expression? condition = null; + + var finder = new DirectParameterMemberFinder(false,source); + var condition2 = finder.Find(getter) + .Select(x => x.GetNullPropagationChecks(arg)) + .Where(x => x != null) + .ToArray().ConcatPropagationChecks(); + + if (condition2 == null) + return getter; + + if (!getter.Type.CanBeNull()) + { + var transform = Expression.Convert(getter, typeof(Nullable<>).MakeGenericType(getter.Type)); + return Expression.Condition(condition2, transform, transform.Type.CreateDefault()); + } + else + return Expression.Condition(condition2, getter, getter.Type.CreateDefault()); + } + + public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg, MemberMapping mapping) + { + if (getter == null) + return adapt; + + var finder = new DirectParameterMemberFinder(true,mapping.Source); + + Expression? condition = finder.Find(getter) + .Select(x => x.GetNullPropagationChecks(arg)) + .Where(x => x != null) + .ToArray().ConcatPropagationChecks(); + + if (condition == null) + return adapt; + + // add supporting DestinationTransforms + var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(adapt.Type)); + if (transform != null) + return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg))); + + return Expression.Condition(condition, adapt, adapt.Type.CreateDefault(member: mapping)); + } + + + private static Expression? ConcatPropagationChecks(this Expression[] checks) + { + if (checks.Length == 0) + return null; + + if (checks.Length == 1) + return checks.First(); + + Expression? result = null; + + for (int i = 0; i < checks.Length; i++) + { + if (i == 0) + result = checks[i]; + else + { + result = Expression.AndAlso(result, checks[i]); + } + + } + + return result; + } + + private static Expression? GetNullPropagationChecks (this Expression getter, CompileArgument arg) + { + Expression? condition = null; + var current = getter; + + while (current != null) + { + Expression? compareNull = null; + + if (current.Type.CanBeNull() && current is not ParameterExpression) + compareNull = Expression.NotEqual(current, Expression.Constant(null, current.Type)); + + else if (current.Type.CanBeNull() && current is ParameterExpression param + && arg.MapType == MapType.Projection) + + compareNull = Expression.NotEqual(param, Expression.Constant(null, param.Type)); + + if (compareNull != null) + { + if (condition == null) + condition = compareNull; + else + condition = Expression.AndAlso(compareNull, condition); + } + + if (current is MemberExpression member) + current = member.Expression; + else + current = null; + } + + return condition; + } + + public static Expression ApplyPropertyNullPropagationLegasy(this Expression getter, CompileArgument arg) { var current = getter; var result = getter; @@ -415,7 +536,7 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter) while (current.NodeType == ExpressionType.MemberAccess) { - var memEx = (MemberExpression) current; + var memEx = (MemberExpression)current; var expr = memEx.Expression; if (expr == null) break; @@ -445,16 +566,17 @@ public static Expression ApplyPropertyNullPropagation(this Expression getter) return getter; } - public static Expression ApplyNullPropagationFromCtor(this Expression getter, Expression adapt, CompileArgument arg) + public static Expression ApplyNullPropagationFromCtorLegasy(this Expression getter, Expression adapt, CompileArgument arg, MemberMapping mapping) { if (getter == null) return adapt; Expression? condition = null; var current = getter; - var checks = arg.Context.NullChecks - .Where(x => !object.ReferenceEquals(x.arg, arg)) - .Select(x => x.param); + // ReadyToCleanUp + //var checks = arg.Context.NullChecks + // .Where(x => !object.ReferenceEquals(x.arg, arg)) + // .Select(x => x.param); while (current != null) { @@ -462,8 +584,12 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex if (current.CanBeNull() && current is not ParameterExpression) compareNull = Expression.NotEqual(current, Expression.Constant(null, current.Type)); + // ReadyToCleanUp + //else if (current.CanBeNull() && current is ParameterExpression param + // && !checks.Contains(param)) else if (current.CanBeNull() && current is ParameterExpression param - && !checks.Contains(param)) + && arg.MapType == MapType.Projection) + compareNull = Expression.NotEqual(param, Expression.Constant(null, param.Type)); if (compareNull != null) @@ -486,9 +612,9 @@ public static Expression ApplyNullPropagationFromCtor(this Expression getter, Ex // add supporting DestinationTransforms var transform = arg.Settings.DestinationTransforms.Find(it => it.Condition(adapt.Type)); if (transform != null) - return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, Expression.Default(adapt.Type))); + return transform.TransformFunc(adapt.Type).Apply(arg.MapType, Expression.Condition(condition, adapt, adapt.Type.CreateDefault(arg))); - return Expression.Condition(condition, adapt, Expression.Default(adapt.Type)); + return Expression.Condition(condition, adapt, adapt.Type.CreateDefault(member:mapping)); } public static string? GetMemberPath(this LambdaExpression lambda, bool firstLevelOnly = false, bool noError = false) diff --git a/src/Mapster/Utils/ParameterExpressionReplacer.cs b/src/Mapster/Utils/ParameterExpressionReplacer.cs index 16c02465..f1a0ee91 100644 --- a/src/Mapster/Utils/ParameterExpressionReplacer.cs +++ b/src/Mapster/Utils/ParameterExpressionReplacer.cs @@ -8,10 +8,20 @@ sealed class ParameterExpressionReplacer : ExpressionVisitor //fields readonly ReadOnlyCollection _from; readonly Expression[] _to; + readonly bool _FromExtraSource; public int[] ReplaceCounts { get; } //constructors + + public ParameterExpressionReplacer(ReadOnlyCollection from,bool isExtraSource, params Expression[] to ) + { + _from = from; + _to = to; + ReplaceCounts = new int[_to.Length]; + _FromExtraSource = isExtraSource; + } + public ParameterExpressionReplacer(ReadOnlyCollection from, params Expression[] to) { _from = from; @@ -24,7 +34,16 @@ protected override Expression VisitParameter(ParameterExpression node) for (var i = 0; i < _from.Count; i++) { if (node != _from[i]) - continue; + { + if (_FromExtraSource) + { + if (node.Type != _from[i].Type) + continue; + } + else + continue; + } + if (i >= _to.Length) return node.Type.CreateDefault(); diff --git a/src/Mapster/Utils/ParametrExpressionFinder.cs b/src/Mapster/Utils/ParametrExpressionFinder.cs new file mode 100644 index 00000000..c9e0ab13 --- /dev/null +++ b/src/Mapster/Utils/ParametrExpressionFinder.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq.Expressions; + +namespace Mapster.Utils +{ + sealed internal class ParametrExpressionFinder: ExpressionVisitor + { + private readonly List _parameters = new(); + + protected override Expression VisitParameter(ParameterExpression node) + { + if (!_parameters.Contains(node)) + _parameters.Add(node); + + return base.VisitParameter(node); + } + + public ReadOnlyCollection Find(Expression expression) + { + _parameters.Clear(); + this.Visit(expression); + return _parameters.AsReadOnly(); + } + } + + internal static class ReplaceOvverideExpressionParam + { + readonly static ParametrExpressionFinder ParamFinder = new (); + + public static Expression Replace(Expression expression, params Expression[] to) + { + return new ParameterExpressionReplacer(ParamFinder.Find(expression), true, to).Visit(expression); + } + } +} diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 8203858f..96fc5ca4 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -358,8 +358,15 @@ public static bool IsPrimitiveKind(this Type type) return type == typeof(object) || type.UnwrapNullable().IsConvertible(); } - public static Expression CreateDefault(this Type type) + public static Expression CreateDefault(this Type type, CompileArgument? arg = null, MemberMapping? member = null) { + if(arg !=null && arg.Settings.CustomDefaultValue != null) + return arg.Settings.CustomDefaultValue; + + if (member != null && member.OverrideSettings != null + && member.OverrideSettings.CustomDefaultValue != null) + return member.OverrideSettings.CustomDefaultValue; + return type.CanBeNull() ? Expression.Constant(null, type) : Expression.Constant(Activator.CreateInstance(type), type); diff --git a/src/Mapster/Utils/StringComparer.cs b/src/Mapster/Utils/StringComparer.cs new file mode 100644 index 00000000..e3f0d7f4 --- /dev/null +++ b/src/Mapster/Utils/StringComparer.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Mapster.Utils +{ + internal class MapsterStringComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) + { + if(String.IsNullOrEmpty(x) || String.IsNullOrEmpty(y)) + return false; + + return String.Equals(x, y, StringComparison.InvariantCultureIgnoreCase); + } + + public int GetHashCode(string obj) + { + if(obj is null) + return 0; + + return obj.GetHashCode(); + } + } +}