我正在尝试使用F# 中的AutoMapper,但由于 AutoMapper 大量使用 LINQ 表达式,我无法设置它。
具体来说,AutoMapper 类型IMappingExpression<'source, 'dest>
有一个带有这个签名的方法:
ForMember(destMember: Expression<Func<'dest, obj>>, memberOpts: Action<IMemberConfigurationExpression<'source>>)
这通常在 C# 中使用,如下所示:
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));
我制作了一个 F# 包装器来安排事物,以便类型推断可以工作。这个包装器允许我将上面的 C# 示例翻译成如下内容:
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
这段代码可以编译,就语法和用法而言,它看起来很干净。但是,在运行时 AutoMapper 告诉我:
AutoMapper.AutoMapperConfigurationException:成员的自定义配置仅支持类型上的顶级单个成员。
我认为这是由于我必须转换Expr<'a -> 'b>
为Expression<Func<'a, obj>>
. 我使用强制转换将其转换为'b
,obj
这意味着我的 lambda 表达式不再是简单的属性访问。如果我在原始报价中将属性值装箱,并且根本不做任何拼接forMember
(见下文),我会得到同样的错误。但是,如果我不将属性值装箱,我最终会得到与预期Expression<Func<'a, 'b>>
的参数类型不匹配的.ForMember
Expression<Func<'a, obj>>
我认为如果 AutoMapperForMember
是完全通用的,这将起作用,但是强制成员访问表达式的返回类型是obj
意味着我只能在 F# 中将它用于已经是直接类型obj
而不是子类的属性。我总是可以使用将ForMember
成员名称作为字符串的重载,但我想在我放弃编译时错字检查之前,我会检查是否有人有出色的解决方法。
我正在使用此代码(加上 F# PowerPack 的 LINQ 部分)将 F# 引用转换为 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)
这是 AutoMapper 的实际 F# 包装器:
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())
更新:
我能够使用Tomas 的示例代码来编写这个函数,它会为IMappingExpression.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"
我仍然需要 PowerPack LINQ 支持来实现我的mapMember
功能,但它们现在都可以工作。
如果有人感兴趣,他们可以在这里找到完整的代码。