Używanie AutoMappera z F #

Próbuję użyćAutoMapper z F #, ale mam problem z ustawieniem go z powodu intensywnego używania wyrażeń LINQ przez AutoMapper.

W szczególności typu AutoMapperIMappingExpression<'source, 'dest> ma metodę z tym podpisem:

ForMember(destMember: Expression<Func<'dest, obj>>, memberOpts: Action<IMemberConfigurationExpression<'source>>)

Zwykle jest to używane w C # w ten sposób:

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));

Zrobiłem opakowanie F #, które porządkuje rzeczy, aby wnioskowanie typu mogło działać. Ten wrapper pozwala mi przetłumaczyć powyższy przykład C na coś takiego:

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

Ten kod się kompiluje i wydaje się całkiem czysty, jeśli chodzi o składnię i użycie. Jednak w czasie wykonywania AutoMapper mówi mi o tym:

AutoMapper.AutoMapperConfigurationException: konfiguracja niestandardowa dla członków jest obsługiwana tylko dla pojedynczych członków najwyższego poziomu typu.

Przypuszczam, że jest to spowodowane faktem, że muszę się nawrócićExpr<'a -> 'b> wExpression<Func<'a, obj>>. Konwertuję'b doobj z obsadą, co oznacza, że ​​moje wyrażenie lambda nie jest już po prostu dostępem do własności. Otrzymuję ten sam błąd, jeśli zaznaczę wartość właściwości w oryginalnym cytacie i nie wykonam żadnego splicingu w środkuforMember (patrz poniżej). Jeśli jednak nie ustawię wartości nieruchomości, otrzymamExpression<Func<'a, 'b>> który nie pasuje do typu parametruForMember oczekuje,Expression<Func<'a, obj>>.

Myślę, że to zadziałałoby, gdyby AutoMapperaForMember była całkowicie ogólna, ale wymuszanie typu powrotu wyrażenia dostępu członka byłoobj oznacza, że ​​mogę używać go tylko w F # dla właściwości, które są już bezpośrednio typuobj a nie podklasa. Zawsze mogę skorzystać z przeciążeniaForMember który przyjmuje nazwę członka jako ciąg, ale pomyślałem, że sprawdzę, czy ktoś ma genialne obejście, zanim zrezygnuję z sprawdzania literówki podczas kompilacji.

Używam tego kodu (plus części LINQ F # PowerPack), aby przekonwertować cytat F # na wyrażenie 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) 

To jest rzeczywiste opakowanie F # dla AutoMappera:

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())
Aktualizacja:

Byłem w stanie użyćPrzykładowy kod Tomasa napisać tę funkcję, która tworzy wyrażenie, z którego AutoMapper jest zadowolony do pierwszego argumentuIMappingExpression.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"

Nadal potrzebuję wsparcia PowerPack LINQ w celu wdrożenia mojegomapMember funkcja, ale obie działają teraz.

Jeśli ktoś jest zainteresowany, może znaleźćpełny kod tutaj.

questionAnswers(2)

yourAnswerToTheQuestion