我有这个方法,它将Expr作为参数:
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
除了System.Expression<Func<'a,ICollection<'b>>>
之外,它只会抛出一个异常说:
InvalidOperationException异常:
表达式'z =&gt; UnboxGeneric(ToFSharpFunc(z =&gt; z.Books).Invoke(z))'不是有效的 财产表达。表达方式 应该代表一个属性:C#:'t =&gt; t.MyProperty'VB.Net:'功能(t) t.MyProperty”。
我用于将Expr转换为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
- 但当然这只是一个理论,而我根本不知道该怎么做,以便解决它。
答案 0 :(得分:2)
你的推理是正确的 - 问题是HasMany
方法只识别特定的C#表达式树,而你的F#代码生成的表达式树是不同的。
我的猜测是EF只处理表达式树是对正确类型的属性的普通访问的情况 - 在C#语法中类似于:x => x.Foo
(没有任何强制转换等)。我认为最好的选择是修改代码以期望函数'a -> ICollection<'b>
。
如果你有办法建立一个正确的表达式树 - 例如如果用户指定x => x.Foo
,您想要返回x => x.FooInternal
,那么您可以使用模式&amp;使用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' & 'Expr.PropGet' to construct
// an expression tree in the expected format
| _ -> failwith "Not a lambda!"
...但请记住,结果需要与HasMany
期望的结构相匹配。我想用一些其他属性(例如一些具有正确类型的内部版本)替换用户指定的实际属性几乎是你唯一能做的事情。