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.

questionAnswers(2)

yourAnswerToTheQuestion