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.