我正在尝试为具有两个关键属性的模型类型定义一个键,并且定义如下:
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)}
使用 Tomas 的 getFuncTree 函数在 F# 端使用 yield 执行相同的操作<@ 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 }'.
所以在我看来,这种匿名类语法做了一些特别的事情......