【发布时间】:2013-08-22 13:21:30
【问题描述】:
我正在尝试为具有两个键属性的模型类型定义一个键,其定义如下:
type Model () =
member val IdOne = 0 with get, set
member val IdTwo = 0 with get, set
member val OtherProperty = "" with get, set
当我尝试在 Entity Framework 5 中使用此模型时,我收到“模型没有定义键。定义此 EntityType 的键”的错误。给出了模型类型,我无法更改它们并添加 [<Key>] 属性。所以我尝试了 Fluent API。
在 C# 中,你会做这样的事情:
modelBuilder.Entity<Model>().HasKey(m => new { m.IdOne, m.IdTwo });
它使用匿名类型。但是对于我的生活,我无法弄清楚如何在 F# 中实现这一点。我尝试了元组、记录,甚至是具有属性 IdOne 和 IdTwo 的常规类型:
// Regular type with properties IdOne & IdTwo.
type ModelKey (idOne, idTwo) =
member this.IdOne = idOne
member this.IdTwo = idTwo
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey (m.IdOne, m.IdTwo))
// ArgumentNullException: Value cannot be null. Parameter name: source
// Regular type with default constructor and properties IdOne & IdTwo.
type ModelKey2 () =
member val IdOne = 0 with get, set
member val IdTwo = 0 with get, set
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey2 ())
// ArgumentNullException: Value cannot be null. Parameter name: source
// Record type.
type ModelKeyRecord = { IdOne : Int32; IdTwo : Int32 }
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> { IdOne = m.IdOne; IdTwo = m.IdTwo })
// ArgumentNullException: Value cannot be null. Parameter name: source
// Tuple.
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> (m.IdOne, m.IdTwo))
// ArgumentNullException: Value cannot be null. Parameter name: source
这些方法都不起作用,我每次都会收到 ArgumentNullException。我没有想法......
编辑 使用下面提供的代码 Tomas(导致相同的 ArgumentNullException 顺便说一句。),我做了一些窥探。这是我发现的:
我使用下面的函数来分析 C# 正在构建的表达式树:
static void Analyze<T>(Expression<Func<Model, T>> function)
{
}
// Call it like this:
Analyze(m => new { m.IdOne, m.IdTwo });
然后我在调试器中查看了生成的 lambda。这是 C# 生成的:
{m => new <>f__AnonymousType0'2(IdOne = m.IdOne, IdTwo = m.IdTwo)}
在 F# 端使用来自 Tomas 的 getFuncTree 函数使用 <@ fun (m : Model) -> ModelKey(m.IdOne, m.IdTwo) @> 做同样的事情会产生:
{m => new ModelKey(m.IdOne, m.IdTwo)}
如您所见,F# 代码中缺少参数的显式命名 - 无论如何,它看起来像属性 - 参数。我在 F# 中手动重新创建了整个表达式树,使其看起来像 C# 版本:
let modelKeyExpression =
Expression.Lambda<Func<Model, ModelKey>> (
body = Expression.New (
``constructor`` = typeof<ModelKey>.GetConstructor [| typeof<Int32>; typeof<Int32> |],
arguments = seq {
yield Expression.MakeMemberAccess (
expression = Expression.Parameter (
``type`` = typeof<Model>,
name = "m"
),
``member`` = typeof<Model>.GetProperty "IdOne"
) :> Expression;
yield Expression.MakeMemberAccess (
expression = Expression.Parameter (
``type`` = typeof<Model>,
name = "m"
),
``member`` = typeof<Model>.GetProperty "IdTwo"
) :> Expression
},
members = seq {
yield (typeof<ModelKey>.GetProperty "IdOne") :> MemberInfo
yield (typeof<ModelKey>.GetProperty "IdTwo") :> MemberInfo
}
),
parameters = [
Expression.Parameter (
``type`` = typeof<Model>,
name = "m"
)
]
)
F# 表示中缺少的部分是成员序列。 当我将鼠标移到这个表达式上时,会出现这个表示:
{m => new ModelKey(IdOne = m.IdOne, IdTwo = m.IdTwo)}
如您所见,除了类之外,它看起来是一样的。但是当我尝试在HasKey 方法中使用这个表达式时,我得到以下InvalidOperationException:
The properties expression 'm => new ModelKey(IdOne = m.IdOne, IdTwo= m.IdTwo)'
is not valid. The expression should represent a property: C#: 't =>
t.MyProperty' VB.Net: 'Function(t) t.MyProperty'. When specifying multiple
properties use an anonymous type: C#: 't => new { t.MyProperty1,
t.MyProperty2 }' VB.Net: 'Function(t) New With { t.MyProperty1,
t.MyProperty2 }'.
所以在我看来,这种匿名类语法做了一些特别的事情......
【问题讨论】:
-
您使用的是 F# 3.1(在 Visual Studio 2013 预览版中)吗?我想这使用表达式树,仅在 VS 2013 中完全支持...
-
嗨 Tomas,我正在使用 VS2012 Update 3 和 F# 3.0... 我无法切换。那么最简单的解决方案可能是一个额外的 C# 库来配置实体?
-
@Tomas - 是什么让你这么说? F# 3 中已经有很好的表达式树支持;我怀疑真正的问题是树没有 EF 期望的形式。
-
@kvb 我只是指新功能,当您将
fun x -> ...传递给需要Expression<Func<..>>的方法时,F# 会自动将其转换为表达式树。我相信这是 F# 3.1 中的新功能。如果我是正确的,那么在 F# 3.0 中,您需要明确地使用引号并进行一些预处理(就像我在回答中所做的那样......)。不过你是对的,这可能仍然会产生 EF 不期望的树。 -
@Tomas - 该功能绝对适用于 F# 3.0。在 F# 3.1 中,有一些更改可以改善某些类似 LINQ 的场景中的重载分辨率,并且可能修复了特定结构的翻译的一些错误,但据我所知没有重大工作。
标签: entity-framework f# ef-code-first entity-framework-5 f#-3.0