From d7a8397b072fc65b3e9bac17770f5574db30f215 Mon Sep 17 00:00:00 2001 From: chaowlert Date: Sun, 24 Jan 2021 22:43:45 +0700 Subject: [PATCH] fix #295 add CodeGenerationConfig --- .config/dotnet-tools.json | 2 +- src/Mapster.Core/Mapster.Core.csproj | 2 +- .../Mapster.Core.csproj.DotSettings | 3 +- .../Register/AdaptAttributeBuilder.cs | 134 ++++++++++++++ .../Register/CodeGenerationConfig.cs | 39 ++++ .../GenerateMapperAttributeBuilder.cs | 49 +++++ .../Register/ICodeGenerationRegister.cs | 7 + src/Mapster.Core/Register/PropertySetting.cs | 13 ++ .../Register/PropertySettingBuilder.cs | 57 ++++++ src/Mapster.Core/Utils/Extensions.cs | 18 +- src/Mapster.Tool/Extensions.cs | 43 +++++ src/Mapster.Tool/Mapster.Tool.csproj | 2 +- src/Mapster.Tool/Program.cs | 167 +++++++++++++----- src/Mapster/Adapters/ClassAdapter.cs | 13 +- src/Mapster/Mapster.csproj | 2 +- src/Mapster/TypeAdapterConfig.cs | 27 +-- src/Mapster/TypeAdapterSetter.cs | 31 ++++ .../Attributes/DtoPropertyTypeAttribute.cs | 13 -- .../Attributes/GenerateAddAttribute.cs | 31 ---- .../Attributes/GenerateDtoAttribute.cs | 25 --- .../Attributes/GenerateMergeAttribute.cs | 33 ---- .../Attributes/GenerateUpdateAttribute.cs | 32 ---- .../Attributes/NoModifyAttribute.cs | 9 - src/Sample.CodeGen/Domains/Course.cs | 3 - src/Sample.CodeGen/Domains/Enrollment.cs | 13 +- src/Sample.CodeGen/Domains/Student.cs | 7 - src/Sample.CodeGen/MappingRegister.cs | 56 +++++- src/Sample.CodeGen/Models/CourseMapper.g.cs | 41 ++++- src/Sample.CodeGen/Models/PersonMapper.g.cs | 31 ---- src/Sample.CodeGen/Models/StudentMapper.g.cs | 2 +- src/Sample.CodeGen/Program.cs | 1 - src/Sample.CodeGen/Sample.CodeGen.csproj | 2 +- 32 files changed, 615 insertions(+), 293 deletions(-) create mode 100644 src/Mapster.Core/Register/AdaptAttributeBuilder.cs create mode 100644 src/Mapster.Core/Register/CodeGenerationConfig.cs create mode 100644 src/Mapster.Core/Register/GenerateMapperAttributeBuilder.cs create mode 100644 src/Mapster.Core/Register/ICodeGenerationRegister.cs create mode 100644 src/Mapster.Core/Register/PropertySetting.cs create mode 100644 src/Mapster.Core/Register/PropertySettingBuilder.cs delete mode 100644 src/Sample.CodeGen/Attributes/DtoPropertyTypeAttribute.cs delete mode 100644 src/Sample.CodeGen/Attributes/GenerateAddAttribute.cs delete mode 100644 src/Sample.CodeGen/Attributes/GenerateDtoAttribute.cs delete mode 100644 src/Sample.CodeGen/Attributes/GenerateMergeAttribute.cs delete mode 100644 src/Sample.CodeGen/Attributes/GenerateUpdateAttribute.cs delete mode 100644 src/Sample.CodeGen/Attributes/NoModifyAttribute.cs delete mode 100644 src/Sample.CodeGen/Models/PersonMapper.g.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 2d35082c..ae3a2e0b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "mapster.tool": { - "version": "7.0.3", + "version": "7.1.3", "commands": [ "dotnet-mapster" ] diff --git a/src/Mapster.Core/Mapster.Core.csproj b/src/Mapster.Core/Mapster.Core.csproj index b814b85b..089286d7 100644 --- a/src/Mapster.Core/Mapster.Core.csproj +++ b/src/Mapster.Core/Mapster.Core.csproj @@ -15,7 +15,7 @@ Mapster.Core.snk icon.png https://cloud.githubusercontent.com/assets/5763993/26522718/d16f3e42-4330-11e7-9b78-f8c7402624e7.png - 1.0.0 + 1.1.2 8.0 enable Mapster diff --git a/src/Mapster.Core/Mapster.Core.csproj.DotSettings b/src/Mapster.Core/Mapster.Core.csproj.DotSettings index 027fde29..41ce25f7 100644 --- a/src/Mapster.Core/Mapster.Core.csproj.DotSettings +++ b/src/Mapster.Core/Mapster.Core.csproj.DotSettings @@ -1,4 +1,5 @@  True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/src/Mapster.Core/Register/AdaptAttributeBuilder.cs b/src/Mapster.Core/Register/AdaptAttributeBuilder.cs new file mode 100644 index 00000000..4e80e247 --- /dev/null +++ b/src/Mapster.Core/Register/AdaptAttributeBuilder.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Mapster +{ + public class AdaptAttributeBuilder + { + public BaseAdaptAttribute Attribute { get; } + public Dictionary> TypeSettings { get; } = new Dictionary>(); + public List> AlterTypes { get; } = new List>(); + + public AdaptAttributeBuilder(BaseAdaptAttribute attribute) + { + this.Attribute = attribute; + } + + public AdaptAttributeBuilder ForTypes(params Type[] types) + { + foreach (var type in types) + { + if (!this.TypeSettings.ContainsKey(type)) + this.TypeSettings.Add(type, new Dictionary()); + } + + return this; + } + + public AdaptAttributeBuilder ForAllTypesInNamespace(Assembly assembly, string @namespace) + { + foreach (var type in assembly.GetTypes()) + { + if (type.Namespace == @namespace && !this.TypeSettings.ContainsKey(type)) + this.TypeSettings.Add(type, new Dictionary()); + } + + return this; + } + + public AdaptAttributeBuilder ForType(Action>? propertyConfig = null) + { + if (!this.TypeSettings.TryGetValue(typeof(T), out var settings)) + { + settings = new Dictionary(); + this.TypeSettings.Add(typeof(T), settings); + } + + propertyConfig?.Invoke(new PropertySettingBuilder(settings)); + return this; + } + + public AdaptAttributeBuilder ExcludeTypes(params Type[] types) + { + foreach (var type in types) + { + this.TypeSettings.Remove(type); + } + + return this; + } + + public AdaptAttributeBuilder ExcludeTypes(Func predicate) + { + foreach (var type in this.TypeSettings.Keys.ToList()) + { + if (predicate(type)) + this.TypeSettings.Remove(type); + } + + return this; + } + + public AdaptAttributeBuilder IgnoreAttributes(params Type[] attributes) + { + this.Attribute.IgnoreAttributes = attributes; + return this; + } + + public AdaptAttributeBuilder IgnoreNoAttributes(params Type[] attributes) + { + this.Attribute.IgnoreNoAttributes = attributes; + return this; + } + + public AdaptAttributeBuilder IgnoreNamespaces(params string[] namespaces) + { + this.Attribute.IgnoreNamespaces = namespaces; + return this; + } + + public AdaptAttributeBuilder IgnoreNullValues(bool value) + { + this.Attribute.IgnoreNullValues = value; + return this; + } + + public AdaptAttributeBuilder MapToConstructor(bool value) + { + this.Attribute.MapToConstructor = value; + return this; + } + + public AdaptAttributeBuilder MaxDepth(int depth) + { + this.Attribute.MaxDepth = depth; + return this; + } + + public AdaptAttributeBuilder PreserveReference(bool value) + { + this.Attribute.PreserveReference = value; + return this; + } + + public AdaptAttributeBuilder ShallowCopyForSameType(bool value) + { + this.Attribute.ShallowCopyForSameType = value; + return this; + } + + public AdaptAttributeBuilder AlterType() + { + this.AlterTypes.Add(type => type == typeof(TFrom) ? typeof(TTo) : null); + return this; + } + + public AdaptAttributeBuilder AlterType(Func predicate, Type toType) + { + this.AlterTypes.Add(type => predicate(type) ? toType : null); + return this; + } + } +} \ No newline at end of file diff --git a/src/Mapster.Core/Register/CodeGenerationConfig.cs b/src/Mapster.Core/Register/CodeGenerationConfig.cs new file mode 100644 index 00000000..def1f462 --- /dev/null +++ b/src/Mapster.Core/Register/CodeGenerationConfig.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace Mapster +{ + public class CodeGenerationConfig + { + public List AdaptAttributeBuilders { get; } = new List(); + public List GenerateMapperAttributeBuilders { get; } = new List(); + public AdaptAttributeBuilder Default { get; } = new AdaptAttributeBuilder(new AdaptFromAttribute("void")); + + public AdaptAttributeBuilder AdaptTo(string name, MapType? mapType = null) + { + var builder = new AdaptAttributeBuilder(new AdaptToAttribute(name) {MapType = mapType ?? 0}); + AdaptAttributeBuilders.Add(builder); + return builder; + } + + public AdaptAttributeBuilder AdaptFrom(string name, MapType? mapType = null) + { + var builder = new AdaptAttributeBuilder(new AdaptFromAttribute(name) {MapType = mapType ?? 0}); + AdaptAttributeBuilders.Add(builder); + return builder; + } + + public AdaptAttributeBuilder AdaptTwoWays(string name, MapType? mapType = null) + { + var builder = new AdaptAttributeBuilder(new AdaptTwoWaysAttribute(name) {MapType = mapType ?? 0}); + AdaptAttributeBuilders.Add(builder); + return builder; + } + + public GenerateMapperAttributeBuilder GenerateMapper(string name) + { + var builder = new GenerateMapperAttributeBuilder(new GenerateMapperAttribute()); + GenerateMapperAttributeBuilders.Add(builder); + return builder; + } + } +} \ No newline at end of file diff --git a/src/Mapster.Core/Register/GenerateMapperAttributeBuilder.cs b/src/Mapster.Core/Register/GenerateMapperAttributeBuilder.cs new file mode 100644 index 00000000..1576da9f --- /dev/null +++ b/src/Mapster.Core/Register/GenerateMapperAttributeBuilder.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Mapster +{ + public class GenerateMapperAttributeBuilder + { + public GenerateMapperAttribute Attribute { get; } + public HashSet Types { get; } = new HashSet(); + + public GenerateMapperAttributeBuilder(GenerateMapperAttribute attribute) + { + this.Attribute = attribute; + } + + public GenerateMapperAttributeBuilder ForTypes(params Type[] types) + { + this.Types.UnionWith(types); + return this; + } + + public GenerateMapperAttributeBuilder ForAllTypesInNamespace(Assembly assembly, string @namespace) + { + this.Types.UnionWith(assembly.GetTypes().Where(it => it.Namespace == @namespace)); + return this; + } + + public GenerateMapperAttributeBuilder ForType() + { + this.Types.Add(typeof(T)); + return this; + } + + public GenerateMapperAttributeBuilder ExcludeTypes(params Type[] types) + { + this.Types.ExceptWith(types); + return this; + } + + public GenerateMapperAttributeBuilder ExcludeTypes(Predicate predicate) + { + this.Types.RemoveWhere(predicate); + return this; + } + + } +} \ No newline at end of file diff --git a/src/Mapster.Core/Register/ICodeGenerationRegister.cs b/src/Mapster.Core/Register/ICodeGenerationRegister.cs new file mode 100644 index 00000000..f372c19d --- /dev/null +++ b/src/Mapster.Core/Register/ICodeGenerationRegister.cs @@ -0,0 +1,7 @@ +namespace Mapster +{ + public interface ICodeGenerationRegister + { + void Register(CodeGenerationConfig config); + } +} diff --git a/src/Mapster.Core/Register/PropertySetting.cs b/src/Mapster.Core/Register/PropertySetting.cs new file mode 100644 index 00000000..b4c66bf0 --- /dev/null +++ b/src/Mapster.Core/Register/PropertySetting.cs @@ -0,0 +1,13 @@ +using System; +using System.Linq.Expressions; + +namespace Mapster +{ + public class PropertySetting + { + public bool Ignore { get; set; } + public string? TargetPropertyName { get; set; } + public Type? TargetPropertyType { get; set; } + public LambdaExpression? MapFunc { get; set; } + } +} \ No newline at end of file diff --git a/src/Mapster.Core/Register/PropertySettingBuilder.cs b/src/Mapster.Core/Register/PropertySettingBuilder.cs new file mode 100644 index 00000000..03d10634 --- /dev/null +++ b/src/Mapster.Core/Register/PropertySettingBuilder.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Mapster.Utils; + +namespace Mapster +{ + public class PropertySettingBuilder + { + public Dictionary Settings { get; } + public PropertySettingBuilder(Dictionary settings) + { + this.Settings = settings; + } + + private PropertySetting ForProperty(string name) + { + if (!this.Settings.TryGetValue(name, out var setting)) + { + setting = new PropertySetting(); + this.Settings.Add(name, setting); + } + return setting; + } + + public PropertySettingBuilder Ignore(Expression> member) + { + var setting = ForProperty(member.GetMemberName()); + setting.Ignore = true; + return this; + } + + public PropertySettingBuilder Map(Expression> member, string targetPropertyName) + { + var setting = ForProperty(member.GetMemberName()); + setting.TargetPropertyName = targetPropertyName; + return this; + } + + public PropertySettingBuilder Map(Expression> member, Type targetPropertyType, string? targetPropertyName = null) + { + var setting = ForProperty(member.GetMemberName()); + setting.TargetPropertyType = targetPropertyType; + setting.TargetPropertyName = targetPropertyName; + return this; + } + + public PropertySettingBuilder Map(Expression> member, Expression> mapFunc, string? targetPropertyName = null) + { + var setting = ForProperty(member.GetMemberName()); + setting.MapFunc = mapFunc; + setting.TargetPropertyName = targetPropertyName; + return this; + } + + } +} \ No newline at end of file diff --git a/src/Mapster.Core/Utils/Extensions.cs b/src/Mapster.Core/Utils/Extensions.cs index d33f1987..b8c8dc5a 100644 --- a/src/Mapster.Core/Utils/Extensions.cs +++ b/src/Mapster.Core/Utils/Extensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq.Expressions; namespace Mapster.Utils { @@ -9,7 +10,22 @@ public static Type GetTypeInfo(this Type type) { return type; } - #endif + + public static string GetMemberName(this LambdaExpression lambda) + { + string? prop = null; + var expr = lambda.Body; + if (expr.NodeType == ExpressionType.MemberAccess) + { + var memEx = (MemberExpression)expr; + prop = memEx.Member.Name; + expr = (Expression?)memEx.Expression; + } + if (prop == null || expr?.NodeType != ExpressionType.Parameter) + throw new ArgumentException("Allow only first level member access (eg. obj => obj.Name)", nameof(lambda)); + return prop; + } + } } diff --git a/src/Mapster.Tool/Extensions.cs b/src/Mapster.Tool/Extensions.cs index 6b898306..0deba21a 100644 --- a/src/Mapster.Tool/Extensions.cs +++ b/src/Mapster.Tool/Extensions.cs @@ -127,6 +127,36 @@ public static Attribute[] SafeGetCustomAttributes(this MemberInfo element) } } + public static IEnumerable GetAdaptAttributeBuilders(this Type type, CodeGenerationConfig config) + { + foreach (var attribute in type.SafeGetCustomAttributes()) + { + if (attribute is BaseAdaptAttribute adaptAttr) + yield return new AdaptAttributeBuilder(adaptAttr); + } + + foreach (var builder in config.AdaptAttributeBuilders) + { + if (builder.TypeSettings.ContainsKey(type)) + yield return builder; + } + } + + public static IEnumerable GetGenerateMapperAttributes(this Type type, CodeGenerationConfig config) + { + foreach (var attribute in type.SafeGetCustomAttributes()) + { + if (attribute is GenerateMapperAttribute genMapperAttr) + yield return genMapperAttr; + } + + foreach (var builder in config.GenerateMapperAttributeBuilders) + { + if (builder.Types.Contains(type)) + yield return builder.Attribute; + } + } + public static bool IsNullable(this Type type) { return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); @@ -141,5 +171,18 @@ public static Type MakeNullable(this Type type) { return type.CanBeNull() ? type : typeof(Nullable<>).MakeGenericType(type); } + + public static void Scan(this CodeGenerationConfig config, Assembly assembly) + { + var registers = assembly.GetTypes() + .Where(x => typeof(ICodeGenerationRegister).GetTypeInfo().IsAssignableFrom(x.GetTypeInfo()) && + x.GetTypeInfo().IsClass && !x.GetTypeInfo().IsAbstract) + .Select(type => (ICodeGenerationRegister) Activator.CreateInstance(type)!); + + foreach (var register in registers) + { + register.Register(config); + } + } } } diff --git a/src/Mapster.Tool/Mapster.Tool.csproj b/src/Mapster.Tool/Mapster.Tool.csproj index 48cbc119..1030cdac 100644 --- a/src/Mapster.Tool/Mapster.Tool.csproj +++ b/src/Mapster.Tool/Mapster.Tool.csproj @@ -15,7 +15,7 @@ true Mapster.Tool.snk true - 7.0.4 + 7.1.3 MIT Copyright (c) 2020 Chaowlert Chaisrichalermpol icon.png diff --git a/src/Mapster.Tool/Program.cs b/src/Mapster.Tool/Program.cs index 94c4f875..7fe159d6 100644 --- a/src/Mapster.Tool/Program.cs +++ b/src/Mapster.Tool/Program.cs @@ -117,26 +117,28 @@ private static void GenerateModels(ModelOptions opt) { using var dynamicContext = new AssemblyResolver(Path.GetFullPath(opt.Assembly)); var assembly = dynamicContext.Assembly; + var codeGenConfig = new CodeGenerationConfig(); + codeGenConfig.Scan(assembly); foreach (var type in assembly.GetTypes()) { - var attrs = type.SafeGetCustomAttributes() - .OfType() - .Where(it => !string.IsNullOrEmpty(it.Name) && it.Name != "[name]") + var builders = type.GetAdaptAttributeBuilders(codeGenConfig) + .Where(it => !string.IsNullOrEmpty(it.Attribute.Name) && it.Attribute.Name != "[name]") .ToList(); - if (attrs.Count == 0) + if (builders.Count == 0) continue; Console.WriteLine($"Processing: {type.FullName}"); - foreach (var attr in attrs) + foreach (var builder in builders) { - CreateModel(opt, type, attr); + CreateModel(opt, type, builder); } } } - private static void CreateModel(ModelOptions opt, Type type, BaseAdaptAttribute attr) + private static void CreateModel(ModelOptions opt, Type type, AdaptAttributeBuilder builder) { + var attr = builder.Attribute; var definitions = new TypeDefinitions { Namespace = opt.Namespace ?? type.Namespace, @@ -176,17 +178,25 @@ private static void CreateModel(ModelOptions opt, Type type, BaseAdaptAttribute properties = properties.Where(it => getPropType(it).Namespace?.StartsWith(ns) != true); } } + + var propSettings = builder.TypeSettings.GetValueOrDefault(type); var isReadOnly = isAdaptTo && attr.MapToConstructor; var isNullable = !isAdaptTo && attr.IgnoreNullValues; foreach (var member in properties) { + var setting = propSettings?.GetValueOrDefault(member.Name); + if (setting?.Ignore == true) + continue; + var adaptMember = member.GetCustomAttribute(); if (!isTwoWays && adaptMember?.Side != null && adaptMember.Side != side) adaptMember = null; - var propType = GetPropertyType(member, getPropType(member), attr.GetType(), opt.Namespace); + var propType = setting?.MapFunc?.ReturnType ?? + setting?.TargetPropertyType ?? + GetPropertyType(member, getPropType(member), attr.GetType(), opt.Namespace, builder); translator.Properties.Add(new PropertyDefinitions { - Name = adaptMember?.Name ?? member.Name, + Name = setting?.TargetPropertyName ?? adaptMember?.Name ?? member.Name, Type = isNullable ? propType.MakeNullable() : propType, IsReadOnly = isReadOnly }); @@ -203,7 +213,7 @@ static Type getPropType(MemberInfo mem) } private static readonly Dictionary _mockTypes = new Dictionary(); - private static Type GetPropertyType(MemberInfo member, Type propType, Type attrType, string? ns) + private static Type GetPropertyType(MemberInfo member, Type propType, Type attrType, string? ns, AdaptAttributeBuilder builder) { var navAttr = member.SafeGetCustomAttributes() .OfType() @@ -214,20 +224,29 @@ private static Type GetPropertyType(MemberInfo member, Type propType, Type attrT if (propType.IsCollection() && propType.IsCollectionCompatible() && propType.IsGenericType && propType.GetGenericArguments().Length == 1) { var elementType = propType.GetGenericArguments()[0]; - var newType = GetPropertyType(member, elementType, attrType, ns); + var newType = GetPropertyType(member, elementType, attrType, ns, builder); if (elementType == newType) return propType; var generic = propType.GetGenericTypeDefinition(); return generic.MakeGenericType(newType); } + var alterType = builder.AlterTypes + .Select(fn => fn(propType)) + .FirstOrDefault(it => it != null); + if (alterType != null) + return alterType; + var propTypeAttrs = propType.SafeGetCustomAttributes(); navAttr = propTypeAttrs.OfType() .FirstOrDefault(it => it.ForAttributes?.Contains(attrType) != false); if (navAttr != null) return navAttr.Type; - var adaptAttr = propTypeAttrs.OfType() - .FirstOrDefault(it => it.GetType() == attrType); + + var adaptAttr = builder.TypeSettings.ContainsKey(propType) + ? (BaseAdaptAttribute?) builder.Attribute + : propTypeAttrs.OfType() + .FirstOrDefault(it => it.GetType() == attrType); if (adaptAttr == null) return propType; if (adaptAttr.Type != null) @@ -242,6 +261,57 @@ private static Type GetPropertyType(MemberInfo member, Type propType, Type attrT return mockType; } + private static Type? GetFromType(Type type, BaseAdaptAttribute attr, Type[] types) + { + if (!(attr is AdaptFromAttribute) && !(attr is AdaptTwoWaysAttribute)) + return null; + + var fromType = attr.Type; + if (fromType == null && attr.Name != null) + { + var name = attr.Name.Replace("[name]", type.Name); + fromType = Array.Find(types, it => it.Name == name); + } + + return fromType; + } + + private static Type? GetToType(Type type, BaseAdaptAttribute attr, Type[] types) + { + if (!(attr is AdaptToAttribute)) + return null; + + var toType = attr.Type; + if (toType == null && attr.Name != null) + { + var name = attr.Name.Replace("[name]", type.Name); + toType = Array.Find(types, it => it.Name == name); + } + + return toType; + } + + private static void ApplySettings(TypeAdapterSetter setter, BaseAdaptAttribute attr, Dictionary settings) + { + setter.ApplyAdaptAttribute(attr); + foreach (var (name, setting) in settings) + { + if (setting.MapFunc != null) + { + setter.Settings.Resolvers.Add(new InvokerModel + { + DestinationMemberName = setting.TargetPropertyName ?? name, + SourceMemberName = name, + Invoker = setting.MapFunc, + }); + } + else if (setting.TargetPropertyName != null) + { + setter.Map(setting.TargetPropertyName, name); + } + } + } + private static void GenerateExtensions(ExtensionOptions opt) { using var dynamicContext = new AssemblyResolver(Path.GetFullPath(opt.Assembly)); @@ -249,13 +319,32 @@ private static void GenerateExtensions(ExtensionOptions opt) var config = TypeAdapterConfig.GlobalSettings; config.SelfContainedCodeGeneration = true; config.Scan(assembly); + var codeGenConfig = new CodeGenerationConfig(); + codeGenConfig.Scan(assembly); var types = assembly.GetTypes(); + var configDict = new Dictionary(); + foreach (var builder in codeGenConfig.AdaptAttributeBuilders) + { + var attr = builder.Attribute; + var cloned = config.Clone(); + foreach (var (type, settings) in builder.TypeSettings) + { + var fromType = GetFromType(type, attr, types); + if (fromType != null) + ApplySettings(cloned.ForType(fromType, type), attr, settings); + + var toType = GetToType(type, attr, types); + if (toType != null) + ApplySettings(cloned.ForType(type, toType), attr, settings); + } + + configDict[attr] = cloned; + } + foreach (var type in types) { - var attrs = type.SafeGetCustomAttributes(); - var mapperAttr = attrs.OfType() - .FirstOrDefault(); + var mapperAttr = type.GetGenerateMapperAttributes(codeGenConfig).FirstOrDefault(); var ruleMaps = config.RuleMap .Where(it => it.Key.Source == type && it.Value.Settings.GenerateMapper is MapType) @@ -265,11 +354,10 @@ private static void GenerateExtensions(ExtensionOptions opt) mapperAttr ??= new GenerateMapperAttribute(); var set = mapperAttr.ForAttributes?.ToHashSet(); - var adaptAttrs = attrs - .OfType() + var builders = type.GetAdaptAttributeBuilders(codeGenConfig) .Where(it => set?.Contains(it.GetType()) != false) .ToList(); - if (adaptAttrs.Count == 0 && ruleMaps.Count == 0) + if (builders.Count == 0 && ruleMaps.Count == 0) continue; Console.WriteLine($"Processing: {type.FullName}"); @@ -284,43 +372,26 @@ private static void GenerateExtensions(ExtensionOptions opt) }; var translator = new ExpressionTranslator(definitions); - foreach (var attr in adaptAttrs) + foreach (var builder in builders) { - if (attr is AdaptFromAttribute || attr is AdaptTwoWaysAttribute) + var attr = builder.Attribute; + var cloned = configDict.GetValueOrDefault(attr) ?? config; + var fromType = GetFromType(type, attr, types); + if (fromType != null) { - var fromType = attr.Type; - if (fromType == null && attr.Name != null) - { - var name = attr.Name.Replace("[name]", type.Name); - fromType = Array.Find(types, it => it.Name == name); - } - - if (fromType == null) - continue; - var tuple = new TypeTuple(fromType, type); var mapType = attr.MapType == 0 ? MapType.Map | MapType.MapToTarget : attr.MapType; - GenerateExtensionMethods(mapType, config, tuple, translator, type, mapperAttr.IsHelperClass); + GenerateExtensionMethods(mapType, cloned, tuple, translator, type, mapperAttr.IsHelperClass); } - if (attr is AdaptToAttribute) + var toType = GetToType(type, attr, types); + if (toType != null && (!(attr is AdaptTwoWaysAttribute) || type != toType)) { - var toType = attr.Type; - if (toType == null && attr.Name != null) - { - var name = attr.Name.Replace("[name]", type.Name); - toType = Array.Find(types, it => it.Name == name); - } - - if (toType == null) - continue; - - if (attr is AdaptTwoWaysAttribute && type == toType) - continue; - var tuple = new TypeTuple(type, toType); - var mapType = attr.MapType == 0 ? MapType.Map | MapType.MapToTarget | MapType.Projection : attr.MapType; - GenerateExtensionMethods(mapType, config, tuple, translator, type, mapperAttr.IsHelperClass); + var mapType = attr.MapType == 0 + ? MapType.Map | MapType.MapToTarget | MapType.Projection + : attr.MapType; + GenerateExtensionMethods(mapType, cloned, tuple, translator, type, mapperAttr.IsHelperClass); } } diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index 8d0dce69..b4684c7e 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -112,12 +112,23 @@ protected override Expression CreateBlockExpression(Expression source, Expressio var adapt = CreateAdaptExpression(member.Getter, member.DestinationMember.Type, arg, member, destMember); if (!member.UseDestinationValue) { - adapt = member.DestinationMember.SetExpression(destination, adapt); if (arg.Settings.IgnoreNullValues == true && member.Getter.CanBeNull()) { + if (adapt is ConditionalExpression condEx) + { + if (condEx.Test is BinaryExpression {NodeType: ExpressionType.Equal} binEx && + binEx.Left == member.Getter && + binEx.Right is ConstantExpression {Value: null}) + adapt = condEx.IfFalse; + } + adapt = member.DestinationMember.SetExpression(destination, adapt); var condition = Expression.NotEqual(member.Getter, Expression.Constant(null, member.Getter.Type)); adapt = Expression.IfThen(condition, adapt); } + else + { + adapt = member.DestinationMember.SetExpression(destination, adapt); + } } else if (!adapt.IsComplex()) continue; diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index 0f0c8991..0f4fcf2f 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -20,7 +20,7 @@ 1.6.1 True Mapster - 7.0.1 + 7.1.3 8.0 enable 1701;1702;8618 diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index 29bdb784..e9c06547 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -516,32 +516,7 @@ private TypeAdapterSettings CreateSettings(BaseAdaptAttribute attr) { var settings = new TypeAdapterSettings(); var setter = new TypeAdapterSetter(settings, this); - if (attr.IgnoreAttributes != null) - setter.IgnoreAttribute(attr.IgnoreAttributes); - if (attr.IgnoreNoAttributes != null) - { - setter.IgnoreMember((member, _) => !member.GetCustomAttributesData() - .Select(it => it.GetAttributeType()) - .Intersect(attr.IgnoreNoAttributes) - .Any()); - } - if (attr.IgnoreNamespaces != null) - { - foreach (var ns in attr.IgnoreNamespaces) - { - setter.IgnoreMember((member, _) => member.Type.Namespace?.StartsWith(ns) == true); - } - } - if (attr.IgnoreNullValues) - setter.IgnoreNullValues(attr.IgnoreNullValues); - if (attr.MapToConstructor) - setter.MapToConstructor(attr.MapToConstructor); - if (attr.MaxDepth > 0) - setter.MaxDepth(attr.MaxDepth); - if (attr.PreserveReference) - setter.PreserveReference(attr.PreserveReference); - if (attr.ShallowCopyForSameType) - setter.ShallowCopyForSameType(attr.ShallowCopyForSameType); + setter.ApplyAdaptAttribute(attr); return settings; } diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index bbb696ba..8eb0b4cd 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Linq.Expressions; using System.Reflection; using Mapster.Adapters; @@ -272,6 +273,36 @@ internal static TSetter Include(this TSetter setter, Type sourceType, T return setter; } + public static TSetter ApplyAdaptAttribute(this TSetter setter, BaseAdaptAttribute attr) where TSetter : TypeAdapterSetter + { + if (attr.IgnoreAttributes != null) + setter.IgnoreAttribute(attr.IgnoreAttributes); + if (attr.IgnoreNoAttributes != null) + { + setter.IgnoreMember((member, _) => !member.GetCustomAttributesData() + .Select(it => it.GetAttributeType()) + .Intersect(attr.IgnoreNoAttributes) + .Any()); + } + if (attr.IgnoreNamespaces != null) + { + foreach (var ns in attr.IgnoreNamespaces) + { + setter.IgnoreMember((member, _) => member.Type.Namespace?.StartsWith(ns) == true); + } + } + if (attr.IgnoreNullValues) + setter.IgnoreNullValues(attr.IgnoreNullValues); + if (attr.MapToConstructor) + setter.MapToConstructor(attr.MapToConstructor); + if (attr.MaxDepth > 0) + setter.MaxDepth(attr.MaxDepth); + if (attr.PreserveReference) + setter.PreserveReference(attr.PreserveReference); + if (attr.ShallowCopyForSameType) + setter.ShallowCopyForSameType(attr.ShallowCopyForSameType); + return setter; + } } public class TypeAdapterSetter : TypeAdapterSetter diff --git a/src/Sample.CodeGen/Attributes/DtoPropertyTypeAttribute.cs b/src/Sample.CodeGen/Attributes/DtoPropertyTypeAttribute.cs deleted file mode 100644 index 1c976cf7..00000000 --- a/src/Sample.CodeGen/Attributes/DtoPropertyTypeAttribute.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Mapster; - -namespace Sample.CodeGen.Attributes -{ - public sealed class DtoPropertyTypeAttribute : PropertyTypeAttribute - { - public DtoPropertyTypeAttribute(Type type) : base(type) - { - ForAttributes = new[] {typeof(GenerateDtoAttribute)}; - } - } -} diff --git a/src/Sample.CodeGen/Attributes/GenerateAddAttribute.cs b/src/Sample.CodeGen/Attributes/GenerateAddAttribute.cs deleted file mode 100644 index 69ea28a3..00000000 --- a/src/Sample.CodeGen/Attributes/GenerateAddAttribute.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Mapster; -using Newtonsoft.Json; - -namespace Sample.CodeGen.Attributes -{ - public sealed class GenerateAddAttribute : AdaptFromAttribute - { - public GenerateAddAttribute() : base("[name]Add") - { - Initialize(); - } - - public GenerateAddAttribute(Type type) : base(type) - { - Initialize(); - } - - private void Initialize() - { - IgnoreAttributes = new[] - { - typeof(NoModifyAttribute), - typeof(JsonIgnoreAttribute), - }; - MapType = MapType.Map; - ShallowCopyForSameType = true; - } - } -} diff --git a/src/Sample.CodeGen/Attributes/GenerateDtoAttribute.cs b/src/Sample.CodeGen/Attributes/GenerateDtoAttribute.cs deleted file mode 100644 index e4cc1828..00000000 --- a/src/Sample.CodeGen/Attributes/GenerateDtoAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using Mapster; -using Newtonsoft.Json; - -namespace Sample.CodeGen.Attributes -{ - public sealed class GenerateDtoAttribute : AdaptToAttribute - { - public GenerateDtoAttribute() : base("[name]Dto") - { - Initialize(); - } - - public GenerateDtoAttribute(Type type) : base(type) - { - Initialize(); - } - - private void Initialize() - { - IgnoreAttributes = new[] {typeof(JsonIgnoreAttribute)}; - ShallowCopyForSameType = true; - } - } -} diff --git a/src/Sample.CodeGen/Attributes/GenerateMergeAttribute.cs b/src/Sample.CodeGen/Attributes/GenerateMergeAttribute.cs deleted file mode 100644 index 3d614926..00000000 --- a/src/Sample.CodeGen/Attributes/GenerateMergeAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Mapster; -using Newtonsoft.Json; - -namespace Sample.CodeGen.Attributes -{ - public sealed class GenerateMergeAttribute : AdaptFromAttribute - { - public GenerateMergeAttribute() : base("[name]Merge") - { - Initialize(); - } - - public GenerateMergeAttribute(Type type) : base(type) - { - Initialize(); - } - - private void Initialize() - { - IgnoreAttributes = new[] - { - typeof(KeyAttribute), - typeof(NoModifyAttribute), - typeof(JsonIgnoreAttribute), - }; - MapType = MapType.MapToTarget; - ShallowCopyForSameType = true; - IgnoreNullValues = true; - } - } -} diff --git a/src/Sample.CodeGen/Attributes/GenerateUpdateAttribute.cs b/src/Sample.CodeGen/Attributes/GenerateUpdateAttribute.cs deleted file mode 100644 index e7bd77f9..00000000 --- a/src/Sample.CodeGen/Attributes/GenerateUpdateAttribute.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Mapster; -using Newtonsoft.Json; - -namespace Sample.CodeGen.Attributes -{ - public sealed class GenerateUpdateAttribute : AdaptFromAttribute - { - public GenerateUpdateAttribute() : base("[name]Update") - { - Initialize(); - } - - public GenerateUpdateAttribute(Type type) : base(type) - { - Initialize(); - } - - private void Initialize() - { - IgnoreAttributes = new[] - { - typeof(KeyAttribute), - typeof(NoModifyAttribute), - typeof(JsonIgnoreAttribute), - }; - MapType = MapType.MapToTarget; - ShallowCopyForSameType = true; - } - } -} diff --git a/src/Sample.CodeGen/Attributes/NoModifyAttribute.cs b/src/Sample.CodeGen/Attributes/NoModifyAttribute.cs deleted file mode 100644 index 083513de..00000000 --- a/src/Sample.CodeGen/Attributes/NoModifyAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Sample.CodeGen.Attributes -{ - [AttributeUsage(AttributeTargets.Property)] - public sealed class NoModifyAttribute : Attribute - { - } -} diff --git a/src/Sample.CodeGen/Domains/Course.cs b/src/Sample.CodeGen/Domains/Course.cs index 22c6f83c..997e5894 100644 --- a/src/Sample.CodeGen/Domains/Course.cs +++ b/src/Sample.CodeGen/Domains/Course.cs @@ -1,12 +1,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using Mapster; -using Sample.CodeGen.Attributes; namespace Sample.CodeGen.Domains { - [GenerateDto, GenerateAdd, GenerateUpdate, GenerateMerge, GenerateMapper] public class Course { [Key, DatabaseGenerated(DatabaseGeneratedOption.None)] diff --git a/src/Sample.CodeGen/Domains/Enrollment.cs b/src/Sample.CodeGen/Domains/Enrollment.cs index a0f4eb51..8a7998a6 100644 --- a/src/Sample.CodeGen/Domains/Enrollment.cs +++ b/src/Sample.CodeGen/Domains/Enrollment.cs @@ -1,28 +1,17 @@ -using Mapster; -using Newtonsoft.Json; -using Sample.CodeGen.Attributes; - -namespace Sample.CodeGen.Domains +namespace Sample.CodeGen.Domains { public enum Grade { A, B, C, D, F } - [GenerateDto, GenerateAdd, GenerateUpdate, GenerateMerge] public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } - - [PropertyType(typeof(string))] public Grade? Grade { get; set; } - - [JsonIgnore] public Course Course { get; set; } - - [NoModify] public Student Student { get; set; } } } diff --git a/src/Sample.CodeGen/Domains/Student.cs b/src/Sample.CodeGen/Domains/Student.cs index 331ce0c2..a40b65cf 100644 --- a/src/Sample.CodeGen/Domains/Student.cs +++ b/src/Sample.CodeGen/Domains/Student.cs @@ -1,14 +1,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Mapster; -using Newtonsoft.Json; -using Sample.CodeGen.Attributes; -using Sample.CodeGen.Models; namespace Sample.CodeGen.Domains { - [GenerateDto, GenerateAdd, GenerateUpdate, GenerateMerge, GenerateMapper, DtoPropertyType(typeof(Person))] public class Student { [Key] @@ -16,8 +11,6 @@ public class Student public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } - - [JsonIgnore] public ICollection Enrollments { get; set; } } } diff --git a/src/Sample.CodeGen/MappingRegister.cs b/src/Sample.CodeGen/MappingRegister.cs index 0841c4a5..e2c4e825 100644 --- a/src/Sample.CodeGen/MappingRegister.cs +++ b/src/Sample.CodeGen/MappingRegister.cs @@ -1,14 +1,60 @@ -using Mapster; +using System; +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using Mapster; +using Sample.CodeGen.Domains; using Sample.CodeGen.Models; namespace Sample.CodeGen { - public class MappingRegister : IRegister + public class MappingRegister : ICodeGenerationRegister { - public void Register(TypeAdapterConfig config) + public void Register(CodeGenerationConfig config) { - config.ForType() - .GenerateMapper(MapType.Map | MapType.MapToTarget); + config.AdaptTo("[name]Dto", MapType.Map | MapType.MapToTarget | MapType.Projection) + .ApplyDefaultRule() + .AlterType(); + + config.AdaptFrom("[name]Add", MapType.Map) + .ApplyDefaultRule() + .IgnoreNoModifyProperties(); + + config.AdaptFrom("[name]Update", MapType.MapToTarget) + .ApplyDefaultRule() + .IgnoreAttributes(typeof(KeyAttribute)) + .IgnoreNoModifyProperties(); + + config.AdaptFrom("[name]Merge", MapType.MapToTarget) + .ApplyDefaultRule() + .IgnoreAttributes(typeof(KeyAttribute)) + .IgnoreNoModifyProperties() + .IgnoreNullValues(true); + + config.GenerateMapper("[name]Mapper") + .ForType() + .ForType(); + } + + } + + static class RegisterExtensions + { + public static AdaptAttributeBuilder ApplyDefaultRule(this AdaptAttributeBuilder builder) + { + return builder + .ForAllTypesInNamespace(Assembly.GetExecutingAssembly(), "Sample.CodeGen.Domains") + .ExcludeTypes(typeof(SchoolContext)) + .ExcludeTypes(type => type.IsEnum) + .AlterType(type => type.IsEnum || Nullable.GetUnderlyingType(type)?.IsEnum == true, typeof(string)) + .ShallowCopyForSameType(true) + .ForType(cfg => cfg.Ignore(it => it.Course)) + .ForType(cfg => cfg.Ignore(it => it.Enrollments)); + } + + public static AdaptAttributeBuilder IgnoreNoModifyProperties(this AdaptAttributeBuilder builder) + { + return builder + .ForType(cfg => cfg.Ignore(it => it.Student)); } } } diff --git a/src/Sample.CodeGen/Models/CourseMapper.g.cs b/src/Sample.CodeGen/Models/CourseMapper.g.cs index dbeabc24..517f4f57 100644 --- a/src/Sample.CodeGen/Models/CourseMapper.g.cs +++ b/src/Sample.CodeGen/Models/CourseMapper.g.cs @@ -93,7 +93,7 @@ public static Course AdaptTo(this CourseMerge p15, Course p16) if (p15.Credits != null) { - result.Credits = p15.Credits == null ? 0 : (int)p15.Credits; + result.Credits = (int)p15.Credits; } if (p15.Enrollments != null) @@ -229,13 +229,38 @@ private static ICollection funcMain5(ICollection p1 while (enumerator.MoveNext()) { EnrollmentMerge item = enumerator.Current; - result.Add(item == null ? null : new Enrollment() - { - EnrollmentID = item.EnrollmentID == null ? 0 : (int)item.EnrollmentID, - CourseID = item.CourseID == null ? 0 : (int)item.CourseID, - StudentID = item.StudentID == null ? 0 : (int)item.StudentID, - Grade = item.Grade == null ? null : (Grade?)Enum.Parse(item.Grade) - }); + result.Add(funcMain6(item)); + } + return result; + + } + + private static Enrollment funcMain6(EnrollmentMerge p19) + { + if (p19 == null) + { + return null; + } + Enrollment result = new Enrollment(); + + if (p19.EnrollmentID != null) + { + result.EnrollmentID = (int)p19.EnrollmentID; + } + + if (p19.CourseID != null) + { + result.CourseID = (int)p19.CourseID; + } + + if (p19.StudentID != null) + { + result.StudentID = (int)p19.StudentID; + } + + if (p19.Grade != null) + { + result.Grade = (Grade?)Enum.Parse(p19.Grade); } return result; diff --git a/src/Sample.CodeGen/Models/PersonMapper.g.cs b/src/Sample.CodeGen/Models/PersonMapper.g.cs deleted file mode 100644 index 6b24d4a4..00000000 --- a/src/Sample.CodeGen/Models/PersonMapper.g.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Sample.CodeGen.Models; - -namespace Sample.CodeGen.Models -{ - public static partial class PersonMapper - { - public static Person AdaptToPerson(this Person p1) - { - return p1 == null ? null : new Person() - { - ID = p1.ID, - LastName = p1.LastName, - FirstMidName = p1.FirstMidName - }; - } - public static Person AdaptTo(this Person p2, Person p3) - { - if (p2 == null) - { - return null; - } - Person result = p3 ?? new Person(); - - result.ID = p2.ID; - result.LastName = p2.LastName; - result.FirstMidName = p2.FirstMidName; - return result; - - } - } -} \ No newline at end of file diff --git a/src/Sample.CodeGen/Models/StudentMapper.g.cs b/src/Sample.CodeGen/Models/StudentMapper.g.cs index e134e264..07375b1c 100644 --- a/src/Sample.CodeGen/Models/StudentMapper.g.cs +++ b/src/Sample.CodeGen/Models/StudentMapper.g.cs @@ -83,7 +83,7 @@ public static Student AdaptTo(this StudentMerge p8, Student p9) if (p8.EnrollmentDate != null) { - result.EnrollmentDate = p8.EnrollmentDate == null ? default(DateTime) : (DateTime)p8.EnrollmentDate; + result.EnrollmentDate = (DateTime)p8.EnrollmentDate; } return result; diff --git a/src/Sample.CodeGen/Program.cs b/src/Sample.CodeGen/Program.cs index d2ddb64a..9e3a49e7 100644 --- a/src/Sample.CodeGen/Program.cs +++ b/src/Sample.CodeGen/Program.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Sample.CodeGen.Domains; -using Sample.CodeGen.Models; namespace Sample.CodeGen { diff --git a/src/Sample.CodeGen/Sample.CodeGen.csproj b/src/Sample.CodeGen/Sample.CodeGen.csproj index faacd9eb..ba9848aa 100644 --- a/src/Sample.CodeGen/Sample.CodeGen.csproj +++ b/src/Sample.CodeGen/Sample.CodeGen.csproj @@ -6,7 +6,7 @@ - +