Usando o AutoMapper com F #
Estou tentando usarAutoMapper de F #, mas estou tendo problemas para configurá-lo devido ao uso pesado de Expressões LINQ pelo AutoMapper.
Especificamente, o tipo AutoMapperIMappingExpression<'source, 'dest>
tem um método com esta assinatura:
ForMember(destMember: Expression<Func<'dest, obj>>, memberOpts: Action<IMemberConfigurationExpression<'source>>)
Isso é normalmente usado em C # assim:
Mapper.CreateMap<Post, PostsViewModel.PostSummary>()
.ForMember(x => x.Slug, o => o.MapFrom(m => SlugConverter.TitleToSlug(m.Title)))
.ForMember(x => x.Author, o => o.Ignore())
.ForMember(x => x.PublishedAt, o => o.MapFrom(m => m.PublishAt));
Eu fiz um wrapper F # que organiza as coisas para que a inferência de tipos possa funcionar. Este wrapper permite-me traduzir o exemplo C # acima em algo como isto:
Mapper.CreateMap<Post, Posts.PostSummary>()
|> mapMember <@ fun x -> x.Slug @> <@ fun m -> SlugConverter.TitleToSlug(m.Title) @>
|> ignoreMember <@ fun x -> x.Author @>
|> mapMember <@ fun x -> x.PublishedAt @> <@ fun m -> m.PublishAt @>
|> ignore
Este código compila e parece bastante limpo quanto a sintaxe e uso. No entanto, em tempo de execução, o AutoMapper me diz isso:
AutoMapper.AutoMapperConfigurationException: A configuração personalizada para membros é suportada apenas para membros individuais de nível superior em um tipo.
Eu presumo que isso é causado pelo fato de que eu tenho que converterExpr<'a -> 'b>
para dentroExpression<Func<'a, obj>>
. Eu converto o'b
paraobj
com um elenco, o que significa que minha expressão lambda não é mais simplesmente um acesso à propriedade. Eu recebo o mesmo erro se eu colocar o valor da propriedade na cotação original, e não faço nenhuma emenda dentroforMember
(ver abaixo). No entanto, se eu não marcar o valor da propriedade, acabo comExpression<Func<'a, 'b>>
que não corresponde ao tipo de parâmetro queForMember
espera,Expression<Func<'a, obj>>
.
Eu acho que isso funcionaria se o AutoMapperForMember
era completamente genérico, mas forçando o tipo de retorno da expressão de acesso de membro a serobj
significa que eu só posso usá-lo em F # para propriedades que já são diretamente do tipoobj
e não uma subclasse. Eu sempre posso recorrer ao uso da sobrecarga deForMember
que leva o nome do membro como uma string, mas eu pensei em verificar se alguém tem uma solução brilhante antes de desistir da verificação de erros em tempo de compilação.
Estou usando este código (mais a parte LINQ do F # PowerPack) para converter uma cotação F # em uma expressão LINQ:
namespace Microsoft.FSharp.Quotations
module Expr =
open System
open System.Linq.Expressions
open Microsoft.FSharp.Linq.QuotationEvaluation
// http://stackoverflow.com/questions/10647198/how-to-convert-expra-b-to-expressionfunca-obj
let ToFuncExpression (expr:Expr<'a -> 'b>) =
let call = expr.ToLinqExpression() :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters)
Este é o wrapper F # real do AutoMapper:
namespace AutoMapper
/// Functions for working with AutoMapper using F# quotations,
/// in a manner that is compatible with F# type-inference.
module AutoMap =
open System
open Microsoft.FSharp.Quotations
let forMember (destMember: Expr<'dest -> 'mbr>) (memberOpts: IMemberConfigurationExpression<'source> -> unit) (map: IMappingExpression<'source, 'dest>) =
map.ForMember(Expr.ToFuncExpression <@ fun dest -> ((%destMember) dest) :> obj @>, memberOpts)
let mapMember destMember (sourceMap:Expr<'source -> 'mapped>) =
forMember destMember (fun o -> o.MapFrom(Expr.ToFuncExpression sourceMap))
let ignoreMember destMember =
forMember destMember (fun o -> o.Ignore())
Atualizar:Eu pude usarCódigo de amostra de Tomas para escrever essa função, que produz uma expressão com a qual o AutoMapper está satisfeito para o primeiro argumentoIMappingExpression.ForMember
.
let toAutoMapperGet (expr:Expr<'a -> 'b>) =
match expr with
| Patterns.Lambda(v, body) ->
// Build LINQ style lambda expression
let bodyExpr = Expression.Convert(translateSimpleExpr body, typeof<obj>)
let paramExpr = Expression.Parameter(v.Type, v.Name)
Expression.Lambda<Func<'a, obj>>(bodyExpr, paramExpr)
| _ -> failwith "not supported"
Eu ainda preciso do suporte do PowerPack LINQ para implementarmapMember
função, mas ambos funcionam agora.
Se alguém estiver interessado, eles podem encontrar ocódigo completo aqui.