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
、ラムダ式が単なるプロパティ アクセスではなくなったことを意味します。元の見積もりでプロパティ値を囲み、内部でスプライシングをまったく行わないと、同じエラーが発生しますforMember
(以下を参照)。ただし、プロパティ値をボックス化しないと、期待Expression<Func<'a, 'b>>
されるパラメーターの型と一致しないものになってしまいます。ForMember
Expression<Func<'a, obj>>
AutoMapper が完全にジェネリックである場合、これは機能すると思いますForMember
が、メンバー アクセス式の戻り値の型を強制することは、F# で既に直接型であり、サブクラスではないobj
プロパティに対してのみ使用できることを意味します。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 のサンプル コードを使用してこの関数を記述できました。この関数は、AutoMapper が の最初の引数として満足する式を生成し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
現在はどちらも機能しています。
興味のある方は、ここで完全なコードを見つけることができます。