From 41259a284739ad13fb29c9c04ab1596b70ebf7e0 Mon Sep 17 00:00:00 2001 From: chaowlert Date: Wed, 6 Mar 2019 22:04:49 +0700 Subject: [PATCH] wip --- src/Mapster/Adapters/ArrayAdapter.cs | 1 - src/Mapster/Adapters/BaseAdapter.cs | 30 ++++++++----------- src/Mapster/Adapters/BaseClassAdapter.cs | 11 +++++-- src/Mapster/Adapters/ClassAdapter.cs | 4 +-- src/Mapster/Adapters/CollectionAdapter.cs | 18 +++++++---- src/Mapster/Adapters/DictionaryAdapter.cs | 6 ++-- src/Mapster/Adapters/EnumAdapter.cs | 1 - .../Adapters/MultiDimensionalArrayAdapter.cs | 1 - src/Mapster/Adapters/ObjectAdapter.cs | 1 - src/Mapster/Adapters/PrimitiveAdapter.cs | 1 - src/Mapster/Adapters/RecordTypeAdapter.cs | 3 +- src/Mapster/Adapters/StringAdapter.cs | 1 - src/Mapster/Compile/CompileArgument.cs | 17 +++++++++-- .../Settings/ValueAccessingStrategy.cs | 2 +- src/Mapster/TypeAdapterSetter.cs | 28 ++++++++++------- src/Mapster/TypeAdapterSettings.cs | 5 ---- src/Mapster/Utils/MapsterHelper.cs | 19 ++++++++++++ src/Mapster/Utils/ReflectionUtils.cs | 5 +++- 18 files changed, 97 insertions(+), 57 deletions(-) diff --git a/src/Mapster/Adapters/ArrayAdapter.cs b/src/Mapster/Adapters/ArrayAdapter.cs index fc0d6870..64859d0b 100644 --- a/src/Mapster/Adapters/ArrayAdapter.cs +++ b/src/Mapster/Adapters/ArrayAdapter.cs @@ -9,7 +9,6 @@ namespace Mapster.Adapters public class ArrayAdapter : BaseAdapter { protected override int Score => -123; - protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index 6e337300..e88e1acf 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -10,7 +10,8 @@ namespace Mapster.Adapters public abstract class BaseAdapter { protected virtual int Score => 0; - protected virtual bool CheckExplicitMapping => true; + protected virtual bool CheckExplicitMapping => false; + protected virtual bool UseTargetValue => false; public virtual int? Priority(PreCompileArgument arg) { @@ -72,14 +73,8 @@ protected virtual bool CanInline(Expression source, Expression destination, Comp protected virtual Expression CreateExpressionBody(Expression source, Expression destination, CompileArgument arg) { - if (this.CheckExplicitMapping) - { - if (arg.Settings.MaxDepth <= 0) - return arg.DestinationType.CreateDefault(); - - if (arg.Context.Config.RequireExplicitMapping && !arg.ExplicitMapping) - throw new InvalidOperationException("Implicit mapping is not allowed (check GlobalSettings.RequireExplicitMapping) and no configuration exists"); - } + if (this.CheckExplicitMapping && arg.Context.Config.RequireExplicitMapping && !arg.ExplicitMapping) + throw new InvalidOperationException("Implicit mapping is not allowed (check GlobalSettings.RequireExplicitMapping) and no configuration exists"); if (CanInline(source, destination, arg) && arg.Settings.AvoidInlineMapping != true) return CreateInlineExpressionBody(source, arg).To(arg.DestinationType, true); @@ -100,6 +95,11 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression des if (set.NodeType != ExpressionType.Throw) { + if (arg.MapType == MapType.MapToTarget && this.UseTargetValue && arg.GetConstructUsing()?.Parameters.Count != 2) + { + set = Expression.Coalesce(destination, set); + } + var actions = new List { Expression.Assign(result, set) @@ -280,19 +280,18 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex //new TDestination() //if there is constructUsing, use constructUsing - var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg); - Expression newObj; + var constructUsing = arg.GetConstructUsing(); if (constructUsing != null) - newObj = constructUsing.Apply(source).TrimConversion(true).To(arg.DestinationType); + return constructUsing.Apply(source, destination).TrimConversion(true).To(arg.DestinationType); //if there is default constructor, use default constructor else if (arg.DestinationType.HasDefaultConstructor()) - newObj = Expression.New(arg.DestinationType); + return Expression.New(arg.DestinationType); //if mapToTarget or include derived types, allow mapping & throw exception on runtime //instantiation is not needed else if (destination != null || arg.Settings.Includes.Count > 0) - newObj = Expression.Throw( + return Expression.Throw( Expression.New( // ReSharper disable once AssignNullToNotNullAttribute typeof(InvalidOperationException).GetConstructor(new[] { typeof(string) }), @@ -302,9 +301,6 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Ex //otherwise throw else throw new InvalidOperationException($"No default constructor for type '{arg.DestinationType.Name}', please use 'ConstructUsing'"); - - //dest ?? new TDest(); - return newObj; } protected Expression CreateAdaptExpression(Expression source, Type destinationType, CompileArgument arg) diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index a7a3bea3..4d1b831c 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -10,6 +10,9 @@ namespace Mapster.Adapters { internal abstract class BaseClassAdapter : BaseAdapter { + protected override bool CheckExplicitMapping => true; + protected override bool UseTargetValue => true; + #region Build the Adapter Model protected ClassMapping CreateClassConverter(Expression source, ClassModel classModel, CompileArgument arg) @@ -17,18 +20,20 @@ protected ClassMapping CreateClassConverter(Expression source, ClassModel classM var destinationMembers = classModel.Members; var unmappedDestinationMembers = new List(); var properties = new List(); + var includeResolvers = arg.Settings.Resolvers + .Where(it => it.DestinationMemberName == "") + .ToList(); foreach (var destinationMember in destinationMembers) { if (ProcessIgnores(arg, destinationMember, out var setterCondition)) continue; - var member = destinationMember; var resolvers = arg.Settings.ValueAccessingStrategies.AsEnumerable(); if (arg.Settings.IgnoreNonMapped == true) resolvers = resolvers.Where(ValueAccessingStrategy.CustomResolvers.Contains); var getter = resolvers - .Select(fn => fn(source, member, arg)) + .Select(fn => fn(source, destinationMember, arg)) .FirstOrDefault(result => result != null); if (getter != null) @@ -43,7 +48,7 @@ protected ClassMapping CreateClassConverter(Expression source, ClassModel classM } else if (classModel.ConstructorInfo != null) { - var info = (ParameterInfo)member.Info; + var info = (ParameterInfo)destinationMember.Info; if (!info.IsOptional) return null; var propertyModel = new MemberMapping diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index e8a52f69..ca6a4c3e 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -30,7 +30,7 @@ protected override bool CanInline(Expression source, Expression destination, Com if (arg.MapType == MapType.MapToTarget) return false; - var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg); + var constructUsing = arg.GetConstructUsing(); if (constructUsing != null && constructUsing.Body.NodeType != ExpressionType.New && constructUsing.Body.NodeType != ExpressionType.MemberInit) @@ -54,7 +54,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E { //new TDestination(src.Prop1, src.Prop2) - if (arg.Settings.ConstructUsingFactory != null || arg.Settings.MapToConstructor != true) + if (arg.GetConstructUsing() != null || arg.Settings.MapToConstructor != true) return base.CreateInstantiationExpression(source, destination, arg); var classConverter = arg.DestinationType.GetConstructors() diff --git a/src/Mapster/Adapters/CollectionAdapter.cs b/src/Mapster/Adapters/CollectionAdapter.cs index 764722f1..dce00887 100644 --- a/src/Mapster/Adapters/CollectionAdapter.cs +++ b/src/Mapster/Adapters/CollectionAdapter.cs @@ -11,7 +11,7 @@ namespace Mapster.Adapters internal class CollectionAdapter : BaseAdapter { protected override int Score => -125; - protected override bool CheckExplicitMapping => false; + protected override bool UseTargetValue => true; protected override bool CanMap(PreCompileArgument arg) { @@ -79,9 +79,16 @@ protected override Expression CreateBlockExpression(Expression source, Expressio ? typeof(ICollection<>).MakeGenericType(destinationElementType) : typeof(IList); var tmp = Expression.Variable(listType, "list"); - var assign = ExpressionEx.Assign(tmp, destination); //convert to list type - var set = CreateListSet(source, tmp, arg); - return Expression.Block(new[] { tmp }, assign, set); + var actions = new List { + ExpressionEx.Assign(tmp, destination) //convert to list type + }; + if (arg.MapType == MapType.MapToTarget) + { + var clear = listType.GetMethod("Clear", Type.EmptyTypes); + actions.Add(Expression.Call(tmp, clear)); + } + actions.Add(CreateListSet(source, tmp, arg)); + return Expression.Block(new[] { tmp }, actions); } protected override Expression CreateInlineExpression(Expression source, CompileArgument arg) @@ -159,8 +166,7 @@ private Expression CreateListSet(Expression source, Expression destination, Comp destination, addMethod, getter); - var loop = ExpressionEx.ForLoop(source, item, set); - return loop; + return ExpressionEx.ForLoop(source, item, set); } } } diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index 1886ec31..4e1ca181 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -54,7 +54,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio Expression.Assign( key, Expression.Call( - Expression.Constant(arg.Settings.NameMatchingStrategy.SourceMemberNameConverter), + MapsterHelper.GetConverterExpression(arg.Settings.NameMatchingStrategy.SourceMemberNameConverter), "Invoke", null, key)), @@ -205,7 +205,7 @@ private ClassModel GetClassModel(CompileArgument arg) var getMethod = typeof(MapsterHelper).GetMethods() .First(m => m.Name == nameof(MapsterHelper.FlexibleGet)) .MakeGenericMethod(args[1]); - var destNameConverter = Expression.Constant(strategy.DestinationMemberNameConverter); + var destNameConverter = MapsterHelper.GetConverterExpression(strategy.DestinationMemberNameConverter); return (dict, key) => Expression.Call(getMethod, dict, key, destNameConverter); } else @@ -228,7 +228,7 @@ private ClassModel GetClassModel(CompileArgument arg) var setMethod = typeof(MapsterHelper).GetMethods() .First(m => m.Name == nameof(MapsterHelper.FlexibleSet)) .MakeGenericMethod(args[1]); - var destNameConverter = Expression.Constant(strategy.DestinationMemberNameConverter); + var destNameConverter = MapsterHelper.GetConverterExpression(strategy.DestinationMemberNameConverter); return (dict, key, value) => Expression.Call(setMethod, dict, key, destNameConverter, value); } else diff --git a/src/Mapster/Adapters/EnumAdapter.cs b/src/Mapster/Adapters/EnumAdapter.cs index 160f56f1..4613e680 100644 --- a/src/Mapster/Adapters/EnumAdapter.cs +++ b/src/Mapster/Adapters/EnumAdapter.cs @@ -8,7 +8,6 @@ namespace Mapster.Adapters internal class EnumAdapter : PrimitiveAdapter { protected override int Score => -109; //must do before StringAdapter - protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Adapters/MultiDimensionalArrayAdapter.cs b/src/Mapster/Adapters/MultiDimensionalArrayAdapter.cs index 1d97ccf0..15d1d036 100644 --- a/src/Mapster/Adapters/MultiDimensionalArrayAdapter.cs +++ b/src/Mapster/Adapters/MultiDimensionalArrayAdapter.cs @@ -10,7 +10,6 @@ namespace Mapster.Adapters public class MultiDimensionalArrayAdapter : BaseAdapter { protected override int Score => -122; - protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Adapters/ObjectAdapter.cs b/src/Mapster/Adapters/ObjectAdapter.cs index 8291ea54..79bcbc14 100644 --- a/src/Mapster/Adapters/ObjectAdapter.cs +++ b/src/Mapster/Adapters/ObjectAdapter.cs @@ -5,7 +5,6 @@ namespace Mapster.Adapters internal class ObjectAdapter : BaseAdapter { protected override int Score => -111; //must do before all class adapters - protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Adapters/PrimitiveAdapter.cs b/src/Mapster/Adapters/PrimitiveAdapter.cs index 576b371b..f961e660 100644 --- a/src/Mapster/Adapters/PrimitiveAdapter.cs +++ b/src/Mapster/Adapters/PrimitiveAdapter.cs @@ -8,7 +8,6 @@ namespace Mapster.Adapters internal class PrimitiveAdapter : BaseAdapter { protected override int Score => -200; //must do last - protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Adapters/RecordTypeAdapter.cs b/src/Mapster/Adapters/RecordTypeAdapter.cs index 418514fb..a8701952 100644 --- a/src/Mapster/Adapters/RecordTypeAdapter.cs +++ b/src/Mapster/Adapters/RecordTypeAdapter.cs @@ -6,6 +6,7 @@ namespace Mapster.Adapters internal class RecordTypeAdapter : BaseClassAdapter { protected override int Score => -149; + protected override bool UseTargetValue => false; protected override bool CanMap(PreCompileArgument arg) { @@ -16,7 +17,7 @@ protected override Expression CreateInstantiationExpression(Expression source, E { //new TDestination(src.Prop1, src.Prop2) - if (arg.Settings.ConstructUsingFactory != null) + if (arg.GetConstructUsing() != null) return base.CreateInstantiationExpression(source, destination, arg); var ctor = arg.DestinationType.GetConstructors()[0]; diff --git a/src/Mapster/Adapters/StringAdapter.cs b/src/Mapster/Adapters/StringAdapter.cs index 749c1f35..0ed9ecff 100644 --- a/src/Mapster/Adapters/StringAdapter.cs +++ b/src/Mapster/Adapters/StringAdapter.cs @@ -7,7 +7,6 @@ namespace Mapster.Adapters internal class StringAdapter : PrimitiveAdapter { protected override int Score => -110; //must do before all class adapters - protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Compile/CompileArgument.cs b/src/Mapster/Compile/CompileArgument.cs index 94ada109..f525dc1b 100644 --- a/src/Mapster/Compile/CompileArgument.cs +++ b/src/Mapster/Compile/CompileArgument.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; namespace Mapster { @@ -15,7 +16,7 @@ public class CompileArgument public CompileContext Context; private HashSet _srcNames; - public HashSet GetSourceNames() + internal HashSet GetSourceNames() { return _srcNames ?? (_srcNames = (from it in Settings.Resolvers where it.SourceMemberName != null @@ -23,11 +24,23 @@ public HashSet GetSourceNames() } private HashSet _destNames; - public HashSet GetDestinationNames() + internal HashSet GetDestinationNames() { return _destNames ?? (_destNames = (from it in Settings.Resolvers where it.DestinationMemberName != null select it.DestinationMemberName.Split('.').First()).ToHashSet()); } + + private bool _fetchConstructUsing; + private LambdaExpression _constructUsing; + internal LambdaExpression GetConstructUsing() + { + if (!_fetchConstructUsing) + { + _constructUsing = Settings.ConstructUsingFactory?.Invoke(this); + _fetchConstructUsing = true; + } + return _constructUsing; + } } } \ No newline at end of file diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index f28379aa..92593e35 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -148,7 +148,7 @@ private static Expression DictionaryFn(Expression source, IMemberModel destinati if (strategy.SourceMemberNameConverter != NameMatchingStrategy.Identity) { var method = typeof(MapsterHelper).GetMethods().First(m => m.Name == nameof(MapsterHelper.FlexibleGet)).MakeGenericMethod(args[1]); - return Expression.Call(method, source.To(dictType), key, Expression.Constant(strategy.SourceMemberNameConverter)); + return Expression.Call(method, source.To(dictType), key, MapsterHelper.GetConverterExpression(strategy.SourceMemberNameConverter)); } else { diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 75caedf4..3b4ffd74 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -211,14 +211,6 @@ public static class TypeAdapterSetterExtensions return setter; } - public static TSetter MaxDepth(this TSetter setter, int? value) where TSetter : TypeAdapterSetter - { - setter.CheckCompiled(); - - setter.Settings.MaxDepth = value; - return setter; - } - public static TSetter MapToConstructor(this TSetter setter, bool value) where TSetter : TypeAdapterSetter { setter.CheckCompiled(); @@ -240,7 +232,7 @@ public TypeAdapterSetter Ignore(params Expression BeforeMappingInline(Expression> action) + { + this.CheckCompiled(); + + Settings.BeforeMappingFactories.Add(arg => action); + return this; + } + + public TypeAdapterSetter AfterMappingInline(Expression> action) + { + this.CheckCompiled(); + + Settings.AfterMappingFactories.Add(arg => action); + return this; + } + public TypeAdapterSetter Include() where TDerivedSource: class, TSource where TDerivedDestination: class, TDestination diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs index ff8d9c85..2ed991b8 100644 --- a/src/Mapster/TypeAdapterSettings.cs +++ b/src/Mapster/TypeAdapterSettings.cs @@ -20,11 +20,6 @@ public NameMatchingStrategy NameMatchingStrategy get => Get("NameMatchingStrategy", () => new NameMatchingStrategy()); set => Set("NameMatchingStrategy", value); } - public int? MaxDepth - { - get => Get("MaxDepth"); - set => Set("MaxDepth", value); - } public bool? PreserveReference { diff --git a/src/Mapster/Utils/MapsterHelper.cs b/src/Mapster/Utils/MapsterHelper.cs index fe7fccb4..c9aa3f84 100644 --- a/src/Mapster/Utils/MapsterHelper.cs +++ b/src/Mapster/Utils/MapsterHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; namespace Mapster.Utils { @@ -50,5 +51,23 @@ public static IEnumerable ToEnumerable(IEnumerable source) } } } + + public static readonly Func Identity = NameMatchingStrategy.Identity; + public static readonly Func PascalCase = NameMatchingStrategy.PascalCase; + public static readonly Func CamelCase = NameMatchingStrategy.CamelCase; + public static readonly Func LowerCase = NameMatchingStrategy.LowerCase; + + internal static Expression GetConverterExpression(Func converter) + { + if (converter == NameMatchingStrategy.Identity) + return Expression.Field(null, typeof(MapsterHelper), nameof(Identity)); + if (converter == NameMatchingStrategy.PascalCase) + return Expression.Field(null, typeof(MapsterHelper), nameof(PascalCase)); + if (converter == NameMatchingStrategy.CamelCase) + return Expression.Field(null, typeof(MapsterHelper), nameof(CamelCase)); + if (converter == NameMatchingStrategy.LowerCase) + return Expression.Field(null, typeof(MapsterHelper), nameof(LowerCase)); + return Expression.Constant(converter); + } } } diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index a91df1b8..826765f8 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -127,12 +127,15 @@ public static Type UnwrapNullable(this Type type) return type.IsNullable() ? type.GetGenericArguments()[0] : type; } - public static string GetMemberPath(Expression expr) + public static string GetMemberPath(Expression expr, bool firstLevelOnly = false) { var props = new List(); expr = expr.TrimConversion(true); while (expr?.NodeType == ExpressionType.MemberAccess) { + if (firstLevelOnly && props.Count > 0) + throw new ArgumentException("Only first level members are allowed (eg. obj => obj.Child)", nameof(expr)); + var memEx = (MemberExpression)expr; props.Add(memEx.Member.Name); expr = memEx.Expression;