使用 AutoMapper 转换为只读集合
Convert to readonly collection with AutoMapper
我需要找到一种方法将任意值列表转换为另一个列表
如果目标类型是 ICollection<>
,AutoMapper 可以工作,因为它正在创建一个实例并用 Add
填充它,但我的类型是不可变列表 'a list
因此,如果我创建整数列表:
let ints = [1; 2; 3]
并尝试使用
将其映射到ResizeArray<int64>
(List<T>
的同义词)
mapper.Map<ResizeArray<int64>>(ints)
它会工作,但如果我尝试将它映射到 int64 list
和
mapper.Map<int64 list>
那就失败了。
我找到了一个可以成功转换的解决方案,但它只适用于明确定义的类型
let cfg = MapperConfiguration(
fun c ->
c.CreateMap<int, int64>() |> ignore
c.CreateMap<int list, int64 list>()
.ConvertUsing(
fun source _ (cfg: ResolutionContext) ->
source
|> Seq.map cfg.Mapper.Map<int, int64>
|> Seq.toList))
所以问题是:如何编写类型转换器将 'a list
转换为 'b list
而无需显式定义这些类型的所有可能组合?
我终于找到了解决办法。我只需要查看此 ReadOnlyCollection 映射器
的源代码
解决方案并不完美,因为集合项转换并插入 System.Collections.Generic.List
,然后转换为 Microsoft.FSharp.Collections.FSharpList
,这会产生一些开销。但至少它在工作
using System.Collections.Generic;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using AutoMapper.Mappers;
using AutoMapper.Internal;
using static AutoMapper.Internal.CollectionMapperExpressionFactory;
using Microsoft.FSharp.Collections;
public class SeqToFSharpListMapper : EnumerableMapperBase
{
public override bool IsMatch(TypePair context)
=> context.SourceType.IsEnumerableType()
&& context.DestinationType.FullName.StartsWith("Microsoft.FSharp.Collections.FSharpList`1");
public override Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, IMemberMap memberMap,
Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
var listType = typeof(List<>).MakeGenericType(ElementTypeHelper.GetElementType(destExpression.Type));
var list = MapCollectionExpression(configurationProvider, profileMap, memberMap, sourceExpression, Default(listType), contextExpression, typeof(List<>), MapItemExpr);
return Call(typeof(ListModule).GetMethod(nameof(ListModule.OfSeq)).MakeGenericMethod(destExpression.Type.GenericTypeArguments[0]), list);
}
}
和 F#
override _.MapExpression (configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression) =
let listType = typedefof<System.Collections.Generic.List<_>>.MakeGenericType(ElementTypeHelper.GetElementType destExpression.Type)
let list = MapCollectionExpression(configurationProvider, profileMap, memberMap,
sourceExpression, Default(listType), contextExpression,
typedefof<System.Collections.Generic.List<_>>,
MapItem(fun c p s d ctx i -> MapItemExpr(c, p, s, d, ctx, &i))) // compiler require explicit lambda
upcast Call(typedefof<obj list>.Assembly // don't want to use AssemblyQualifiedName
.GetType("Microsoft.FSharp.Collections.ListModule") // have to use this trick because we can't access ListModule through typeof
.GetMethod("OfSeq")
.MakeGenericMethod(destExpression.Type.GenericTypeArguments.[0]),
list)
我需要找到一种方法将任意值列表转换为另一个列表
如果目标类型是 ICollection<>
,AutoMapper 可以工作,因为它正在创建一个实例并用 Add
填充它,但我的类型是不可变列表 'a list
因此,如果我创建整数列表:
let ints = [1; 2; 3]
并尝试使用
将其映射到ResizeArray<int64>
(List<T>
的同义词)
mapper.Map<ResizeArray<int64>>(ints)
它会工作,但如果我尝试将它映射到 int64 list
和
mapper.Map<int64 list>
那就失败了。
我找到了一个可以成功转换的解决方案,但它只适用于明确定义的类型
let cfg = MapperConfiguration(
fun c ->
c.CreateMap<int, int64>() |> ignore
c.CreateMap<int list, int64 list>()
.ConvertUsing(
fun source _ (cfg: ResolutionContext) ->
source
|> Seq.map cfg.Mapper.Map<int, int64>
|> Seq.toList))
所以问题是:如何编写类型转换器将 'a list
转换为 'b list
而无需显式定义这些类型的所有可能组合?
我终于找到了解决办法。我只需要查看此 ReadOnlyCollection 映射器
的源代码解决方案并不完美,因为集合项转换并插入 System.Collections.Generic.List
,然后转换为 Microsoft.FSharp.Collections.FSharpList
,这会产生一些开销。但至少它在工作
using System.Collections.Generic;
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
using AutoMapper.Mappers;
using AutoMapper.Internal;
using static AutoMapper.Internal.CollectionMapperExpressionFactory;
using Microsoft.FSharp.Collections;
public class SeqToFSharpListMapper : EnumerableMapperBase
{
public override bool IsMatch(TypePair context)
=> context.SourceType.IsEnumerableType()
&& context.DestinationType.FullName.StartsWith("Microsoft.FSharp.Collections.FSharpList`1");
public override Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, IMemberMap memberMap,
Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
var listType = typeof(List<>).MakeGenericType(ElementTypeHelper.GetElementType(destExpression.Type));
var list = MapCollectionExpression(configurationProvider, profileMap, memberMap, sourceExpression, Default(listType), contextExpression, typeof(List<>), MapItemExpr);
return Call(typeof(ListModule).GetMethod(nameof(ListModule.OfSeq)).MakeGenericMethod(destExpression.Type.GenericTypeArguments[0]), list);
}
}
和 F#
override _.MapExpression (configurationProvider, profileMap, memberMap, sourceExpression, destExpression, contextExpression) =
let listType = typedefof<System.Collections.Generic.List<_>>.MakeGenericType(ElementTypeHelper.GetElementType destExpression.Type)
let list = MapCollectionExpression(configurationProvider, profileMap, memberMap,
sourceExpression, Default(listType), contextExpression,
typedefof<System.Collections.Generic.List<_>>,
MapItem(fun c p s d ctx i -> MapItemExpr(c, p, s, d, ctx, &i))) // compiler require explicit lambda
upcast Call(typedefof<obj list>.Assembly // don't want to use AssemblyQualifiedName
.GetType("Microsoft.FSharp.Collections.ListModule") // have to use this trick because we can't access ListModule through typeof
.GetMethod("OfSeq")
.MakeGenericMethod(destExpression.Type.GenericTypeArguments.[0]),
list)