Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[*.cs]
file_header_template = Copyright (c) MASA Stack All rights reserved.\nLicensed under the MIT License. See LICENSE.txt in the project root for license information.

# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
dotnet_diagnostic.CS8618.severity = none
Expand Down Expand Up @@ -29,4 +30,4 @@ indent_size = 2

# JSON files
[*.json]
indent_size = 2
indent_size = 2
36 changes: 36 additions & 0 deletions Masa.Contrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{1653
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Configuration.AutoMap.NoArgumentConstructor.Tests", "test\Masa.Contrib.Configuration.AutoMap.NoArgumentConstructor.Tests\Masa.Contrib.Configuration.AutoMap.NoArgumentConstructor.Tests.csproj", "{B8358ED1-C95A-4EC0-9756-FB32C931F204}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.BuildingBlocks.Data.Mapping", "src\BuildingBlocks\MASA.BuildingBlocks\src\Data\Masa.BuildingBlocks.Data.Mapping\Masa.BuildingBlocks.Data.Mapping.csproj", "{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Data.Mapping.Mapster.Tests", "test\Masa.Contrib.Data.Mapping.Mapster.Tests\Masa.Contrib.Data.Mapping.Mapster.Tests.csproj", "{834A12D0-FBED-45B3-86EA-5EA114C516B5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mapping", "Mapping", "{4AC23B67-52F9-44E5-9586-79A1DB73E6F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Contrib.Data.Mapping.Mapster", "src\Data\Mapping\Masa.Contrib.Data.Mapping.Mapster\Masa.Contrib.Data.Mapping.Mapster.csproj", "{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -676,6 +684,30 @@ Global
{B8358ED1-C95A-4EC0-9756-FB32C931F204}.Release|Any CPU.Build.0 = Release|Any CPU
{B8358ED1-C95A-4EC0-9756-FB32C931F204}.Release|x64.ActiveCfg = Release|Any CPU
{B8358ED1-C95A-4EC0-9756-FB32C931F204}.Release|x64.Build.0 = Release|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Debug|x64.ActiveCfg = Debug|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Debug|x64.Build.0 = Debug|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Release|Any CPU.Build.0 = Release|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Release|x64.ActiveCfg = Release|Any CPU
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0}.Release|x64.Build.0 = Release|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Debug|x64.ActiveCfg = Debug|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Debug|x64.Build.0 = Debug|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Release|Any CPU.Build.0 = Release|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Release|x64.ActiveCfg = Release|Any CPU
{834A12D0-FBED-45B3-86EA-5EA114C516B5}.Release|x64.Build.0 = Release|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Debug|x64.ActiveCfg = Debug|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Debug|x64.Build.0 = Debug|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Release|Any CPU.Build.0 = Release|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Release|x64.ActiveCfg = Release|Any CPU
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -761,6 +793,10 @@ Global
{97532A33-A591-4DF5-A2C0-72527B78ED82} = {38E6C400-90C0-493E-9266-C1602E229F1B}
{165391A5-034E-4894-8084-8DF7D4AA7518} = {42DF7AAC-362C-48F4-B76A-BDEEEFF17CC9}
{B8358ED1-C95A-4EC0-9756-FB32C931F204} = {9EEE31DA-3165-4CB3-AAE9-27CC3A4DE669}
{5A3338F1-9963-4CAC-85A3-7AB263CB15B0} = {DC578D74-98F0-4F19-A230-CFA8DAEE0AF1}
{834A12D0-FBED-45B3-86EA-5EA114C516B5} = {38E6C400-90C0-493E-9266-C1602E229F1B}
{4AC23B67-52F9-44E5-9586-79A1DB73E6F7} = {E33ADF54-4D35-49B7-BDA6-412587CA39FF}
{D5EA7A25-0FD2-4545-9C1C-FF96E5E35145} = {4AC23B67-52F9-44E5-9586-79A1DB73E6F7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {40383055-CC50-4600-AD9A-53C14F620D03}
Expand Down
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));
}
}
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(
Copy link
Contributor

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?

Copy link
Contributor Author

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

(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));
}
}
}
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);
}
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();
}
}
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;
}
}
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)!);
}
}
Loading