Nik*_*ird 3 f# entity-framework ef-code-first f#-3.0 entity-framework-5
我试图为具有两个关键属性的模型类型定义一个键,并定义如下:
type Model () =
member val IdOne = 0 with get, set
member val IdTwo = 0 with get, set
member val OtherProperty = "" with get, set
Run Code Online (Sandbox Code Playgroud)
当我尝试在Entity Framework 5中使用此模型时,我收到错误"模型没有定义键.定义此EntityType的键".给出了模型类型,我无法更改它们并添加[<Key>]
属性.所以我尝试了Fluent API.
在C#中,你会做这样的事情:
modelBuilder.Entity<Model>().HasKey(m => new { m.IdOne, m.IdTwo });
Run Code Online (Sandbox Code Playgroud)
它使用匿名类型.但对于我的生活,我无法弄清楚如何在F#中解决这个问题.我尝试了Tuples,Records,甚至是具有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
Run Code Online (Sandbox Code Playgroud)
这些方法都不起作用,我每次都得到一个ArgumentNullException.我没有想法......
编辑使用下面提供的代码Tomas(导致相同的ArgumentNullException btw.),我做了一些窥探.这是我发现的:
我使用下面的函数来分析C#正在构建的表达式树:
static void Analyze<T>(Expression<Func<Model, T>> function)
{
}
// Call it like this:
Analyze(m => new { m.IdOne, m.IdTwo });
Run Code Online (Sandbox Code Playgroud)
然后我查看了调试器中生成的lambda.这就是C#生成的内容:
{m => new <>f__AnonymousType0'2(IdOne = m.IdOne, IdTwo = m.IdTwo)}
Run Code Online (Sandbox Code Playgroud)
使用来自Tomas的getFuncTree函数在F#端执行相同的操作<@ fun (m : Model) -> ModelKey(m.IdOne, m.IdTwo) @>
:
{m => new ModelKey(m.IdOne, m.IdTwo)}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,无论如何,显式命名 - 看起来像属性 - 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"
)
]
)
Run Code Online (Sandbox Code Playgroud)
F#表示中缺少的部分是成员序列.当我将鼠标移到此表达式上时,会出现以下表示:
{m => new ModelKey(IdOne = m.IdOne, IdTwo = m.IdTwo)}
Run Code Online (Sandbox Code Playgroud)
如您所见,除了课程外,它看起来都一样.但是,当我尝试在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 }'.
Run Code Online (Sandbox Code Playgroud)
所以在我看来,这个匿名类语法做了一些特别的事情......
小智 6
在 F# 4.6 中,经过多次努力,这对我有用。
显然,您只需要一个元组。这是有道理的,因为没有明确成员名称的匿名对象基本上是一个元组。
我不敢相信没有人把它放在 MS 文档上。
modelBuilder.Entity<Entity>()
.HasKey(fun e -> (e.Key1, e.Key2) :> obj)
|> ignore
Run Code Online (Sandbox Code Playgroud)
编辑: F#2.0中需要以下技术,但在较新版本中不需要它.F#生成的表达式树必定存在其他问题......
我认为问题是Entity Framework希望您将lambda表达式指定为表达式树:
modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey (m.IdOne, m.IdTwo))
Run Code Online (Sandbox Code Playgroud)
这应该只适用于Visual Studio 2013中的F#3.1,但在F#3.0中不受支持.你仍然可以这样做,但是你必须使用F#引用并编写一些将引用转换为LINQ表达式树的代码 - 有一个帮助程序可以完成大部分工作:
open System
open System.Linq.Expressions
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Linq.RuntimeHelpers
let getFuncTree (quotation:Expr<'a -> 'b>) =
let e = LeafExpressionConverter.QuotationToExpression quotation
let call = e :?> MethodCallExpression
let lam = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'a, 'b>>(lam.Body, lam.Parameters)
getFuncTree <@ fun x -> x + 1 @>
Run Code Online (Sandbox Code Playgroud)
使用此功能,您应该可以调用:
modelBuilder.Entity<Model>().HasKey(getFuncTree <@ fun (m : Model) ->
ModelKey (m.IdOne, m.IdTwo) @>)
Run Code Online (Sandbox Code Playgroud)
您可以定义一个扩展方法,就像HasKeyQuot
在封面后面这样做,以使代码更好一些.
看起来问题是双重的:
Members
LINQ 的集合NewExpression
,但这用于标记 EF 期望的匿名类型的构造。m => new { A = m.IdOne, B = m.IdTwo }
行不通 - 匿名类型的属性名称必须与模型的属性名称相匹配。解决该问题的一种方法(可能有点过分,但有效)是在运行时动态创建一个新类型,其中包含具有正确名称的字段,然后在 F# 代码中使用元组:
open Quotations.Patterns
open Quotations.ExprShape
open System.Reflection
open System.Linq.Expressions
module AnonymousTypeFixer =
let private mb =
let ab = System.AppDomain.CurrentDomain.DefineDynamicAssembly(AssemblyName("codeGen"), Emit.AssemblyBuilderAccess.ReflectionOnly)
ab.DefineDynamicModule("codeGen")
let transform (Lambda(v, (NewTuple exprs)) : Quotations.Expr<'a -> 'b>) =
let newV = Expression.Variable(v.Type, v.Name)
let cvtExpr (PropertyGet(Some(Var v'), p, [])) =
assert (v = v')
Expression.Property(newV, p) :> Expression, p
let ty = mb.DefineType(v.Type.Name)
let ctor = ty.DefineConstructor(MethodAttributes.Public (*||| MethodAttributes.RTSpecialName ||| MethodAttributes.SpecialName*), CallingConventions.HasThis, exprs |> List.map (fun e -> e.Type) |> List.toArray)
ctor.GetILGenerator().Emit(Emit.OpCodes.Ret)
let fields =
[for (_, p) in exprs |> List.map cvtExpr ->
ty.DefineField(p.Name, p.PropertyType, FieldAttributes.Public) :> MemberInfo]
ty.CreateType()
let newE = Expression.New(ctor, exprs |> Seq.map (cvtExpr >> fst), fields)
Expression.Lambda<System.Func<'a, obj>>(newE, newV)
let mb = System.Data.Entity.DbModelBuilder()
mb.Entity<Model>().HasKey(AnonymousTypeFixer.transform <@ fun (m:Model) -> m.IdOne, m.IdTwo @>)
Run Code Online (Sandbox Code Playgroud)