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