Вопрос по – Использование AutoMapper с F #

9

Я пытаюсь использоватьAutoMapper из F #, но у меня возникают проблемы с его настройкой из-за интенсивного использования в AutoMapper выражений LINQ.

В частности, тип AutoMapperIMappingExpression<'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: Custom configuration for members is only supported for top-level individual members on a type.

Я предполагаю, что это связано с тем, что я должен преобразоватьExpr<'a -> 'b> вExpression<Func<'a, obj>>, Я конвертирую'b вobj с приведением, что означает, что мое лямбда-выражение больше не просто доступ к свойству. Я получаю ту же ошибку, если я помещаю значение свойства в исходную цитату и не выполняю никакого сращивания внутриforMember (увидеть ниже). Однако, если я не укажу значение свойства, я получуExpression<Func<'a, 'b>> который не соответствует типу параметра, которыйForMember надеется,Expression<Func<'a, obj>>.

Я думаю, что это будет работать, если AutoMapperForMember был полностью универсальным, но заставлял возвращаемый тип выражения доступа членаobj означает, что я могу использовать его только в F # для свойств, которые уже имеют типobj а не подкласс. Я всегда могу прибегнуть к использованию перегрузкиForMember имя участника воспринимается как строка, но я подумал, что я проверю, есть ли у кого-нибудь блестящий обходной путь, прежде чем я перестану проверять опечатки во время компиляции.

Я использую этот код (плюс часть LINQ в F # PowerPack) для преобразования цитаты 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) 

Это фактическая оболочка F # для 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())
Update:

Я был в состоянии использоватьПример кода Томаса написать эту функцию, которая создает выражение, которым 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 функции, но они оба работают сейчас.

Если кому-то интересно, они могут найтиполный код здесь.

вам больше не нуженF# PowerPack для.ToLinqExpression() это сейчас в F # какMicrosoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToExpress‌​ion Maslow

Ваш Ответ

2   ответа
4

Теперь, когда F # рад генерироватьExpression<Func<...>> прямо изfun Выражение, это относительно легко решить. Самая большая проблема сейчас заключается в том, что компилятор F #, похоже, запутался из-за перегрузкиForMember метод, и не может правильно определить, что вы хотите. Это можно обойти, определив метод расширения с другим именем:

type AutoMapper.IMappingExpression<'TSource, 'TDestination> with
    // The overloads in AutoMapper's ForMember method seem to confuse
    // F#'s type inference, forcing you to supply explicit type annotations
    // for pretty much everything to get it to compile. By simply supplying
    // a different name, 
    member this.ForMemberFs<'TMember>
            (destGetter:Expression<Func<'TDestination, 'TMember>>,
             sourceGetter:Action<IMemberConfigurationExpression<'TSource, 'TDestination, 'TMember>>) =
        this.ForMember(destGetter, sourceGetter)

Затем вы можете использоватьForMemberFs метод более или менее как оригиналForMember предназначен для работы, например:

this.CreateMap<Post, Posts.PostSummary>()
    .ForMemberFs
        ((fun d -> d.Slug),
         (fun opts -> opts.MapFrom(fun m -> SlugConverter.TitleToSlug(m.Title)))
4

Я не совсем уверен, как исправить сгенерированное дерево выражений (что выполнимо путем его последующей обработки, но очень трудно узнать, что ожидает AutoMapper). Однако есть две альтернативы:

As a first option - выражения, которые вам нужно перевести, довольно просты. В основном это просто вызовы методов, получение свойств и использование переменной. Это означает, что должна быть возможность написать собственную цитату в переводчике деревьев выражений, который генерирует именно тот код, который вы хотите (тогда вы также можете добавить свою собственную обработкуobjвозможно, позвонивExpression.Convert построить дерево выражений). Я написалпростой переводчик цитат в качестве образца, который должен обрабатывать большую часть материала в вашем образце.

As a second option - если AutoMapper предоставляет возможность указать только имя свойства - вы можете просто использовать цитаты в форме<@ x.FooBar @>, Их должно быть довольно легко разобрать, используяPatterns.PropertyGet шаблон. API должен выглядеть примерно так:

Mapper.CreateMap<Post, Posts.PostSummary>(fun post summary mapper ->
  mapper |> mapMember <@ post.Slug @> // not sure what the second argument should be?
         |> ignoreMember <@ post.Author @> )

Или, на самом деле, вы можете использовать этот стиль API даже в первом случае, потому что вам не нужно многократно писать лямбда-выражения для каждого отдельного отображения, так что, возможно, это немного лучше :-)

Error: User Rate Limit Exceeded Joel Mueller
Error: User Rate Limit ExceededmapMemberError: User Rate Limit ExceededPostSummaryError: User Rate Limit ExceededPost.SlugError: User Rate Limit ExceededPostSummary.TitleError: User Rate Limit ExceededCreateMapError: User Rate Limit Exceeded Joel Mueller
Error: User Rate Limit Exceeded Joel Mueller

Похожие вопросы