F # — «Недопустимое выражение свойства»

#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> при вызове функции, что приведет к той же ошибке.