diff --git a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs index 17c821f3ce..21a001266a 100644 --- a/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectMapping/Volo/Abp/ObjectMapping/DefaultObjectMapper.cs @@ -124,16 +124,12 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency protected virtual TDestination? TryToMapCollection(IServiceScope serviceScope, TSource source, TDestination? destination) { - if (!typeof(TSource).IsGenericType || typeof(TSource).GetGenericTypeDefinition() != typeof(ICollection<>) || - !typeof(TDestination).IsGenericType || typeof(TDestination).GetGenericTypeDefinition() != typeof(ICollection<>)) + if (!IsCollectionGenericType(out var sourceArgumentType, out var destinationArgumentType, out var definitionGenericType)) { - //skip, not a collection return default; } - var sourceGenericTypeDefinition = typeof(TSource).GenericTypeArguments[0]; - var destinationGenericTypeDefinition = typeof(TDestination).GenericTypeArguments[0]; - var mapperType = typeof(IObjectMapper<,>).MakeGenericType(sourceGenericTypeDefinition, destinationGenericTypeDefinition); + var mapperType = typeof(IObjectMapper<,>).MakeGenericType(sourceArgumentType, destinationArgumentType); var specificMapper = serviceScope.ServiceProvider.GetService(mapperType); if (specificMapper == null) { @@ -148,17 +144,87 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency x.GetParameters().Length == (destination == null ? 1 : 2)); }); - var result = Activator.CreateInstance(typeof(Collection<>).MakeGenericType(destinationGenericTypeDefinition))!.As(); - foreach (var sourceItem in (IEnumerable)source!) + var sourceList = source!.As(); + var result = definitionGenericType.IsGenericType + ? Activator.CreateInstance(definitionGenericType.MakeGenericType(destinationArgumentType))!.As() + : Array.CreateInstance(destinationArgumentType, sourceList.Count); + + for (var i = 0; i < sourceList.Count; i++) { - result.Add(destination == null - ? method.Invoke(specificMapper, new [] { sourceItem })! - : method.Invoke(specificMapper, new [] { sourceItem, Activator.CreateInstance(destinationGenericTypeDefinition)! })!); + var invokeResult = destination == null + ? method.Invoke(specificMapper, new [] { sourceList[i] })! + : method.Invoke(specificMapper, new [] { sourceList[i], Activator.CreateInstance(destinationArgumentType)! })!; + + if (definitionGenericType.IsGenericType) + { + result.Add(invokeResult); + } + else + { + result[i] = invokeResult; + } } return (TDestination)result!; } + protected virtual bool IsCollectionGenericType(out Type sourceArgumentType, out Type destinationArgumentType, out Type definitionGenericType) + { + sourceArgumentType = default!; + destinationArgumentType = default!; + definitionGenericType = default!; + + if ((!typeof(TSource).IsGenericType && !typeof(TSource).IsArray) || + (!typeof(TDestination).IsGenericType && !typeof(TDestination).IsArray)) + { + return false; + } + + var supportedCollectionTypes = new[] + { + typeof(IEnumerable<>), + typeof(ICollection<>), + typeof(Collection<>), + typeof(IList<>), + typeof(List<>) + }; + + if (typeof(TSource).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TSource).GetGenericTypeDefinition())) + { + sourceArgumentType = typeof(TSource).GenericTypeArguments[0]; + } + + if (typeof(TSource).IsArray) + { + sourceArgumentType = typeof(TSource).GetElementType()!; + } + + if (sourceArgumentType == default!) + { + return false; + } + + definitionGenericType = typeof(List<>); + if (typeof(TDestination).IsGenericType && supportedCollectionTypes.Any(x => x == typeof(TDestination).GetGenericTypeDefinition())) + { + destinationArgumentType = typeof(TDestination).GenericTypeArguments[0]; + + if (typeof(TDestination).GetGenericTypeDefinition() == typeof(ICollection<>) || + typeof(TDestination).GetGenericTypeDefinition() == typeof(Collection<>)) + { + definitionGenericType = typeof(Collection<>); + } + } + + if (typeof(TDestination).IsArray) + { + destinationArgumentType = typeof(TDestination).GetElementType()!; + definitionGenericType = typeof(Array); + } + + return destinationArgumentType != default!; + } + protected virtual TDestination AutoMap(object source) { return AutoObjectMappingProvider.Map(source); diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs index af94bac5b8..9eb537cec5 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Shouldly; @@ -29,20 +30,47 @@ public class AbpAutoMapperModule_Specific_ObjectMapper_Tests : AbpIntegratedTest [Fact] public void Specific_Object_Mapper_Should_Be_Used_For_Collections_If_Registered() { - var dtos = _objectMapper.Map, ICollection>(new List() + _objectMapper.Map, IEnumerable>(new List() { new MyEntity { Number = 42 } - }); - dtos.First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. - dtos = _objectMapper.Map, ICollection>(new List() + _objectMapper.Map, ICollection>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + _objectMapper.Map, Collection>(new Collection() + { + new MyEntity { Number = 42 } + }, new Collection() //When mapping to an existing collection, the destination collection is cleared first + { + new MyEntityDto2 { Number = 44 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + _objectMapper.Map, IList>(new List() + { + new MyEntity { Number = 42 } + }, new Collection() //When mapping to an existing collection, the destination collection is cleared first + { + new MyEntityDto2 { Number = 44 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + _objectMapper.Map, List>(new List() { new MyEntity { Number = 42 } }, new List() //When mapping to an existing collection, the destination collection is cleared first { new MyEntityDto2 { Number = 44 } - }); - dtos.First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + _objectMapper.Map(new MyEntity[] + { + new MyEntity { Number = 42 } + }, new MyEntityDto2[] //When mapping to an existing collection, the destination collection is cleared first + { + new MyEntityDto2 { Number = 44 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. } [Fact]