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

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

В частности, тип AutoMapper IMappingExpressionимеет метод с такой сигнатурой:

ForMember(destMember: Expression>, memberOpts: Action>)

Это обычно используется в C# следующим образом:

Mapper.CreateMap()
    .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()
|> 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 'b>в Expression>. Я конвертирую 'bв objс помощью приведения, что означает, что мое лямбда-выражение больше не является просто доступом к свойству. Я получаю ту же ошибку, если я помещаю значение свойства в исходную цитату и вообще не делаю никакого соединения внутри forMember(см. ниже).Однако, если я не помещаю значение свойства в рамку, я получаю Expression>, который не соответствует типу параметра, который ожидает ForMember, Выражение>.

Я думаю, что это сработало бы, если бы AutoMapper ForMemberбыл полностью универсальным, но принудительный тип возвращаемого значения выражения доступа к члену 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>(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())

Обновление:

Я был можно использовать образец кода Томасадля написания этой функции, которая создает выражение, удовлетворяющее 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)
        let paramExpr = Expression.Parameter(v.Type, v.Name)
        Expression.Lambda>(bodyExpr, paramExpr)
    | _ -> failwith "not supported"

Мне по-прежнему нужна поддержка PowerPack LINQ для реализации моей функции mapMember, но теперь они оба работают.

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

9
задан Joel Mueller 19 May 2012 в 03:14
поделиться