#f#
#f#
Вопрос:
У меня есть этот метод, который принимает выражение в качестве параметра:
member x.HasSeq (expr:Expr<'a -> 'b seq>) =
let casted = <@ fun z -> (%expr) z :?> ICollection<'b> @>
ManyNavPropertyInfo(cfg.HasMany <| toLinq casted)
Что я хочу, так это привести 'b seq
к ICollection<'b>
, который, кажется, работает так, как должен, однако, когда он достигает строки, где он собирается преобразовать выражение в LINQ (необходимо сделать это, поскольку cfg.HasMany
исключает a System.Expression<Func<'a,ICollection<'b>>>
), он просто выдает исключение, говорящее:
Исключение InvalidOperationException:
Выражение ‘z => UnboxGeneric(ToFSharpFunc(z => z.Books).Invoke(z))’ не является допустимым выражением свойства. Выражение должно представлять свойство: C #: ‘t => t.MyProperty’ VB.Net: ‘Function(t) t.MyProperty’.
Функция, которую я использую для преобразования выражения в LINQ:
let toLinq (exp : Expr<'a -> 'b>) =
let linq = exp.ToLinqExpression()
let call = linq :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lambda.Body, lambda.Parameters)
Я использовал toLinq
функцию раньше без проблем — я думаю, это потому, что я приводил b seq
к ICollection<'b>
, который оставляет UnboxGeneric
в Expr
и при передаче Expr
в toLinq
он просто не знает, что делать с UnboxGeneric
— но, конечно, это всего лишь теория, и я вообще не знаю, что делать, чтобы решить это.
Ответ №1:
Ваши рассуждения верны — проблема в том, что HasMany
метод распознает только определенные деревья выражений C #, а дерево выражений, генерируемое вашим кодом F #, отличается.
Я предполагаю, что EF обрабатывает только случай, когда дерево выражений представляет собой простой доступ к свойству правильного типа — в синтаксисе C # что-то вроде: x => x.Foo
(без какого-либо приведения и т.д.). Я думаю, что лучшим вариантом было бы изменить ваш код, чтобы он также ожидал функцию 'a -> ICollection<'b>
.
Если у вас есть какой-либо способ построения правильного дерева выражений — например, если пользователь указывает x => x.Foo
, что вы хотите вернуть x => x.FooInternal
, то вы можете использовать шаблоны и функции для работы с кавычками F #, чтобы перестроить дерево выражений:
let hasSeq (e:Expr<'a -> seq<'b>>) =
match e with
| Patterns.Lambda(v, Patterns.PropertyGet(Some instance, propInfo, [])) ->
printfn "Get property %s of %A" propInfo.Name instance
// TODO: Use 'Expr.Lambda' amp; 'Expr.PropGet' to construct
// an expression tree in the expected format
| _ -> failwith "Not a lambda!"
… но имейте в виду, что результат должен соответствовать структуре, ожидаемой HasMany
. Я предполагаю, что замена фактического свойства, указанного пользователем, каким-либо другим свойством (например, некоторой внутренней версией, имеющей правильный тип) — это практически единственное, что вы можете сделать.
Комментарии:
1. Ваш последний подход выглядит интересным, однако я не уверен, как смешать
Expr.Lambda
иExpr.PropGet
, чтобы построить дерево выражений. Вы не возражаете против демонстрации ;=)?2. Если я заставлю функцию ожидать a
'a -> ICollection<'b>
, то вам просто нужно будет вместо этого привестиseq<'b>
при вызове функции, что приведет к той же ошибке.