问题
I'm having this method which takes a Expr as parameter:
member x.HasSeq (expr:Expr<'a -> 'b seq>) =
let casted = <@ fun z -> (%expr) z :?> ICollection<'b> @>
ManyNavPropertyInfo(cfg.HasMany <| toLinq casted)
What I want is to cast the 'b seq
to an ICollection<'b>
, which seems to work as it should, however when it reaches the line where it's going to convert the Expr to LINQ (need to do this since cfg.HasMany
excepts a System.Expression<Func<'a,ICollection<'b>>>
) it simply throws an exception saying:
InvalidOperationException:
The expression 'z => UnboxGeneric(ToFSharpFunc(z => z.Books).Invoke(z))' is not a valid property expression. The expression should represent a property: C#: 't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'.
The function I use for converting Expr to 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)
I've used the toLinq
function before without problems - I think it's because I cast b seq
to ICollection<'b>
which leaves UnboxGeneric
in the Expr
and when passing the Expr
to toLinq
it simply dosent know what to do with the UnboxGeneric
- but of course thats just a theory, and I dont know what to do at all, in order to resolve it.
回答1:
Your reasoning is correct - the problem is that the HasMany
method recognizes only specific C# expression trees and the expression tree that your F# code generates is different.
My guess is that EF only handles a case when the expression tree is a plain access to a property of the right type - in the C# syntax something like: x => x.Foo
(without any casting etc.). I think that the best option would be to modify your code to also expect a function 'a -> ICollection<'b>
.
If you have some way for building a correct expression tree - e.g. if user specifies x => x.Foo
, you want to return x => x.FooInternal
, then you can use patterns & functions for working with F# quotations to rebuild the expression tree:
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' & 'Expr.PropGet' to construct
// an expression tree in the expected format
| _ -> failwith "Not a lambda!"
... but keep in mind that the result needs to match the structure expected by HasMany
. I guess that replacing the actual property specified by the user with some other property (e.g. some internal version that has the right type) is pretty much the only thing you can do.
来源:https://stackoverflow.com/questions/5776307/f-not-a-valid-property-expression