-
Notifications
You must be signed in to change notification settings - Fork 119
feat(Mapping): Support Mapping.Mapster #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3f7979f
feat(Mapping): Support Mapping
zhenlei520 828ab50
Update .editorconfig
zhenlei520 9eb0e4b
chore: Modify global using sorting
zhenlei520 57bd370
Merge branch 'feature/mapping' of https://github.com/masastack/MASA.C…
zhenlei520 67d104a
chore: Optimization parameter naming
zhenlei520 545b9c7
chore: IMapping changed its name to IMapper
zhenlei520 6e96601
docs(Mapping): Modify the Mapping document
zhenlei520 7c5576c
docs(Mapping): Modify the Mapping document
zhenlei520 3c380c7
chore: Modify mappings to mappers
zhenlei520 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule MASA.BuildingBlocks
updated
7 files
34 changes: 34 additions & 0 deletions
34
src/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/DefaultMapper.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright (c) MASA Stack All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE.txt in the project root for license information. | ||
|
|
||
| namespace Masa.Contrib.Data.Mapping.Mapster; | ||
|
|
||
| public class DefaultMapper : IMapper | ||
| { | ||
| private readonly IMappingConfigProvider _provider; | ||
|
|
||
| public DefaultMapper(IMappingConfigProvider provider) | ||
| => _provider = provider; | ||
|
|
||
| public TDestination Map<TSource, TDestination>(TSource source, MapOptions? options = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(source, nameof(source)); | ||
|
|
||
| return source.Adapt<TSource, TDestination>(_provider.GetConfig(source.GetType(), typeof(TDestination), options)); | ||
| } | ||
|
|
||
| public TDestination Map<TDestination>(object source, MapOptions? options = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(source, nameof(source)); | ||
|
|
||
| return source.Adapt<TDestination>(_provider.GetConfig(source.GetType(), typeof(TDestination), options)); | ||
| } | ||
|
|
||
| public TDestination Map<TSource, TDestination>(TSource source, TDestination destination, MapOptions? options = null) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(source, nameof(source)); | ||
|
|
||
| Type destinationType = destination?.GetType() ?? typeof(TDestination); | ||
| return source.Adapt<TSource, TDestination>(destination, _provider.GetConfig(source.GetType(), destinationType, options)); | ||
| } | ||
| } |
187 changes: 187 additions & 0 deletions
187
src/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/DefaultMappingConfigProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| // Copyright (c) MASA Stack All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE.txt in the project root for license information. | ||
|
|
||
| namespace Masa.Contrib.Data.Mapping.Mapster; | ||
|
|
||
| public class DefaultMappingConfigProvider : IMappingConfigProvider | ||
| { | ||
| private readonly ConcurrentDictionary<(Type SourceType, Type DestinationType, MapOptions? MapOptions), TypeAdapterConfig?> _store = new(); | ||
|
|
||
| private readonly MapOptions _options; | ||
|
|
||
| public DefaultMappingConfigProvider(MapOptions options) => _options = options; | ||
|
|
||
| public TypeAdapterConfig GetConfig(Type sourceType, Type destinationType, MapOptions? options = null) | ||
| => GetConfigByCache(sourceType, destinationType, options); | ||
|
|
||
| protected virtual TypeAdapterConfig GetConfigByCache(Type sourceType, Type destinationType, MapOptions? options) | ||
| { | ||
| TypeAdapterConfig? config = _store.GetOrAdd( | ||
| (sourceType, destinationType, options), | ||
| type => GetAdapterConfig(type.SourceType, type.DestinationType, options)); | ||
|
|
||
| return config ?? GetDefaultConfig(options); | ||
| } | ||
|
|
||
| protected virtual TypeAdapterConfig? GetAdapterConfig(Type sourceType, Type destinationType, MapOptions? options) | ||
| { | ||
| TypeAdapterConfig adapterConfig = GetDefaultConfig(options); | ||
|
|
||
| var mapTypes = GetMapAndSelectorTypes(adapterConfig, sourceType, destinationType, options, true); | ||
|
|
||
| foreach (var item in mapTypes) | ||
| { | ||
| var methodExecutor = InvokeBuilder.Build(item.SourceType, item.DestinationType); | ||
| methodExecutor.Invoke(adapterConfig, item.Constructor); | ||
| } | ||
|
|
||
| return IsShare(options) ? null : adapterConfig; //When in shared mode, Config returns empty to save memory space | ||
| } | ||
|
|
||
| //todo: In the follow-up, according to the situation, consider whether the configuration requires Fork, which is not processed for the time being | ||
| private List<MapTypeOptions> GetMapTypes( | ||
| TypeAdapterConfig adapterConfig, | ||
| Type sourceType, | ||
| Type destinationType, | ||
| MapOptions? options) | ||
| { | ||
| if (!NeedAutomaticMap(sourceType, destinationType)) | ||
| return new List<MapTypeOptions>(); | ||
|
|
||
| List<MapTypeOptions> mapTypes = new(); | ||
| var sourceProperties = sourceType.GetProperties().ToList(); | ||
| var destinationProperties = destinationType.GetProperties().ToList(); | ||
|
|
||
| List<ConstructorInfo> destinationConstructors = destinationType | ||
| .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) | ||
| .Where(c => c.GetParameters().Length <= sourceProperties.Count) | ||
| .OrderByDescending(c => c.GetParameters().Length) | ||
| .ToList(); | ||
|
|
||
| MapTypeOptions mapTypeOption = new(sourceType, destinationType) | ||
| { | ||
| Constructor = GetBestConstructor(destinationConstructors, sourceProperties) | ||
| }; | ||
| if (!RuleMapIsExist(adapterConfig, sourceType, destinationType)) | ||
| { | ||
| mapTypes.Add(mapTypeOption); | ||
| } | ||
|
|
||
| List<(string Name, Type DdestinationPropertyType)> destinationPropertyList = destinationProperties | ||
| .Select(p => (p.Name.ToLower(), p.PropertyType)) | ||
| .Concat(mapTypeOption.Constructor.GetParameters().Select(p => (p.Name!.ToLower(), p.ParameterType))!) | ||
| .Distinct() | ||
| .ToList(); | ||
|
|
||
| foreach (var sourceProperty in sourceProperties) | ||
| { | ||
| if (!sourceProperty.CanRead) | ||
| continue; | ||
|
|
||
| var destinationProperty = destinationPropertyList.FirstOrDefault(p | ||
| => p.Name.Equals(sourceProperty.Name, StringComparison.OrdinalIgnoreCase)); | ||
| if (destinationProperty != default) | ||
| { | ||
| var subMapTypes = GetMapAndSelectorTypes(adapterConfig, sourceProperty.PropertyType, | ||
| destinationProperty.DdestinationPropertyType, options, false); | ||
|
|
||
| if (!subMapTypes.Any() || mapTypes.Any(option => subMapTypes.Any(subOption | ||
| => subOption.SourceType == option.SourceType && subOption.DestinationType == option.DestinationType))) | ||
| continue; | ||
|
|
||
| mapTypes.AddRange(subMapTypes); | ||
| } | ||
| } | ||
|
|
||
| return mapTypes; | ||
| } | ||
|
|
||
| private List<MapTypeOptions> GetMapAndSelectorTypes(TypeAdapterConfig adapterConfig, Type sourceType, Type destinationType, | ||
| MapOptions? options, bool isFirst) | ||
| { | ||
| bool sourcePropertyIsEnumerable = IsCollection(sourceType); | ||
| bool destinationPropertyIsEnumerable = IsCollection(destinationType); | ||
| if (!sourcePropertyIsEnumerable && !destinationPropertyIsEnumerable) | ||
| { | ||
| var subMapTypes = GetMapTypes( | ||
| adapterConfig, | ||
| sourceType, | ||
| destinationType, | ||
| options); | ||
| if (subMapTypes.Any()) return subMapTypes; | ||
| } | ||
| else if (sourcePropertyIsEnumerable && destinationPropertyIsEnumerable) | ||
| { | ||
| var subMapTypes = GetMapTypes(adapterConfig, | ||
| sourceType.GetGenericArguments()[0], | ||
| destinationType.GetGenericArguments()[0], | ||
| options); | ||
|
|
||
| if (subMapTypes.Any()) return subMapTypes; | ||
| } | ||
| return new(); | ||
| } | ||
|
|
||
| protected virtual bool IsCollection(Type type) | ||
| => type.IsGenericType && type.GetInterfaces().Any(x => x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); | ||
|
|
||
| protected virtual ConstructorInfo GetBestConstructor(List<ConstructorInfo> destinationConstructors, List<PropertyInfo> sourceProperties) | ||
| { | ||
| if (destinationConstructors.Count <= 1) | ||
| return destinationConstructors.First(); | ||
|
|
||
| foreach (var constructor in destinationConstructors) | ||
| { | ||
| if (IsPreciseMatch(constructor, sourceProperties)) | ||
| return constructor; | ||
| } | ||
|
|
||
| throw new Exception("Failed to get the best constructor"); | ||
| } | ||
|
|
||
| protected virtual bool IsPreciseMatch(ConstructorInfo destinationConstructor, List<PropertyInfo> sourceProperties) | ||
| { | ||
| foreach (var parameter in destinationConstructor.GetParameters()) | ||
| { | ||
| if (!sourceProperties.Any(p | ||
| => p.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase) && p.PropertyType == parameter.ParameterType)) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| protected virtual List<Type> NotNeedAutomaticMapTypes => new() | ||
| { | ||
| typeof(string) | ||
| }; | ||
|
|
||
| protected virtual bool NeedAutomaticMap(Type sourceType, Type destinationType) | ||
| => sourceType.IsClass && | ||
| !IsCollection(sourceType) && | ||
| (sourceType != destinationType || (sourceType != typeof(object) || destinationType != typeof(object))) && | ||
| !NotNeedAutomaticMapTypes.Contains(sourceType); | ||
|
|
||
| protected virtual bool RuleMapIsExist(TypeAdapterConfig adapterConfig, Type sourceType, Type destinationType) | ||
| => adapterConfig.RuleMap.Any(r => r.Key == new TypeTuple(sourceType, destinationType)); | ||
|
|
||
| protected virtual bool IsShare(MapOptions? options) => (options?.Mode ?? _options.Mode) == MapMode.Shared; | ||
|
|
||
| /// <summary> | ||
| /// Get initial configuration | ||
| /// When currently in shared mode, return the default global settings | ||
| /// </summary> | ||
| /// <returns></returns> | ||
| protected virtual TypeAdapterConfig GetDefaultConfig(MapOptions? options) | ||
| { | ||
| //todo: Other modes are currently not supported, and will be added in the future according to the situation | ||
| switch (options?.Mode ?? _options.Mode) | ||
| { | ||
| case MapMode.Shared: | ||
| return TypeAdapterConfig.GlobalSettings; | ||
| default: | ||
| throw new ArgumentException("Only shared configuration is supported", nameof(MapOptions.Mode)); | ||
| } | ||
| } | ||
| } | ||
9 changes: 9 additions & 0 deletions
9
src/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/IMappingConfigProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // Copyright (c) MASA Stack All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE.txt in the project root for license information. | ||
|
|
||
| namespace Masa.Contrib.Data.Mapping.Mapster; | ||
|
|
||
| public interface IMappingConfigProvider | ||
| { | ||
| TypeAdapterConfig GetConfig(Type sourceType, Type destinationType, MapOptions? options = null); | ||
| } |
40 changes: 40 additions & 0 deletions
40
src/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Internal/InvokeBuilder.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // Copyright (c) MASA Stack All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE.txt in the project root for license information. | ||
|
|
||
| namespace Masa.Contrib.Data.Mapping.Mapster.Internal; | ||
|
|
||
| internal class InvokeBuilder | ||
| { | ||
| private static readonly MethodInfo _newConfigMethodInfo; | ||
| private static readonly Type _typeAdapterConfigType; | ||
|
|
||
| static InvokeBuilder() | ||
| { | ||
| var typeAdapterSetterExpandType = typeof(TypeAdapterSetterExpand); | ||
| _newConfigMethodInfo = typeAdapterSetterExpandType.GetMethod(nameof(TypeAdapterSetterExpand.NewConfigByConstructor))!; | ||
| _typeAdapterConfigType = typeof(TypeAdapterConfig); | ||
| } | ||
|
|
||
| internal delegate TypeAdapterSetter MethodExecutor(TypeAdapterConfig target, object parameter); | ||
|
|
||
| public static MethodExecutor Build( | ||
| Type sourceType, | ||
| Type destinationType) | ||
| { | ||
| var methodInfo = _newConfigMethodInfo.MakeGenericMethod(sourceType, destinationType); | ||
|
|
||
| ParameterExpression[] parameters = | ||
| { | ||
| Expression.Parameter(_typeAdapterConfigType, "adapterConfigParameter"), | ||
| Expression.Parameter(typeof(object), "constructorInfoParameter") | ||
| }; | ||
| var newConfigMethodCall = Expression.Call( | ||
| null, | ||
| methodInfo, | ||
| parameters | ||
| ); | ||
|
|
||
| var lambda = Expression.Lambda<MethodExecutor>(newConfigMethodCall, parameters); | ||
| return lambda.Compile(); | ||
| } | ||
| } |
19 changes: 19 additions & 0 deletions
19
src/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Internal/Options/MapTypeOptions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Copyright (c) MASA Stack All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE.txt in the project root for license information. | ||
|
|
||
| namespace Masa.Contrib.Data.Mapping.Mapster.Internal.Options; | ||
|
|
||
| internal class MapTypeOptions | ||
| { | ||
| public Type SourceType { get; } = default!; | ||
|
|
||
| public Type DestinationType { get; } = default!; | ||
|
|
||
| public ConstructorInfo Constructor { get; set; } = default!; | ||
|
|
||
| public MapTypeOptions(Type sourceType, Type destinationType) | ||
| { | ||
| SourceType = sourceType; | ||
| DestinationType = destinationType; | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/Data/Mapping/Masa.Contrib.Data.Mapping.Mapster/Internal/TypeAdapterSetterExpand.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // Copyright (c) MASA Stack All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE.txt in the project root for license information. | ||
|
|
||
| namespace Masa.Contrib.Data.Mapping.Mapster.Internal; | ||
|
|
||
| internal class TypeAdapterSetterExpand | ||
| { | ||
| public static TypeAdapterSetter<TSource, TDestination> NewConfigByConstructor<TSource, TDestination>(TypeAdapterConfig adapterConfig, | ||
| object constructorInfo) | ||
| { | ||
| return adapterConfig | ||
| .NewConfig<TSource, TDestination>() | ||
| .MapToConstructor((constructorInfo as ConstructorInfo)!); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use lazy or ManualMemoryCache?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand what you mean, here is the configuration that tries to get the mapping relationship when the user is mapped