Usando AutoMapper con F #
Estoy tratando de usarAutoMapper de F #, pero tengo problemas para configurarlo debido al uso intensivo de LINQ Expressions por parte de AutoMapper.
Específicamente, el tipo AutoMapperIMappingExpression<'source, 'dest>
Tiene un método con esta firma:
ForMember(destMember: Expression<Func<'dest, obj>>, memberOpts: Action<IMemberConfigurationExpression<'source>>)
Esto se usa típicamente en C # como esto:
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));
Hice una envoltura F # que organiza las cosas para que la inferencia de tipos pueda funcionar. Esta envoltura me permite traducir el ejemplo de C # anterior en algo como esto:
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 se compila, y parece bastante limpio en cuanto a sintaxis y uso. Sin embargo, en tiempo de ejecución, AutoMapper me dice esto:
AutoMapper.AutoMapperConfigurationException: la configuración personalizada para miembros solo se admite para miembros individuales de nivel superior en un tipo.
Supongo que esto se debe al hecho de que tengo que convertirExpr<'a -> 'b>
dentroExpression<Func<'a, obj>>
. Yo convierto el'b
aobj
con un cast, lo que significa que mi expresión lambda ya no es simplemente un acceso de propiedad. Recibo el mismo error si escribo el valor de la propiedad en la oferta original y no hago ningún empalme dentroforMember
(vea abajo). Sin embargo, si no encierro el valor de la propiedad, termino conExpression<Func<'a, 'b>>
que no coincide con el tipo de parámetro queForMember
espera,Expression<Func<'a, obj>>
.
Creo que esto funcionaría si AutoMapper'sForMember
era completamente genérico, pero forzando el tipo de retorno de la expresión de acceso de miembro a serobj
significa que solo puedo usarlo en F # para propiedades que ya son directamente de tipoobj
y no una subclase. Siempre puedo recurrir al uso de la sobrecarga deForMember
eso toma el nombre del miembro como una cadena, pero pensé que me gustaría verificar si alguien tiene una solución alternativa brillante antes de renunciar a la corrección de errores de tiempo de compilación.
Estoy usando este código (más la parte LINQ de F # PowerPack) para convertir una cita de F # en una expresión 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)
Esta es la envoltura real de F # para 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())
Actualizar:Pude usarCódigo de muestra de tomas para escribir esta función, que produce una expresión con la que se satisface AutoMapper para el primer 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"
Todavía necesito el soporte de PowerPack LINQ para implementar mimapMember
Funcionan, pero ambos funcionan ahora.
Si alguien está interesado, puede encontrar elcódigo completo aquí.