Question

I am trying to define a key for a model type that has two key properties and is defined like this:

type Model () =
    member val IdOne = 0 with get, set
    member val IdTwo = 0 with get, set
    member val OtherProperty = "" with get, set

When I try to use this model in Entity Framework 5, I get the error that "Model has no key defined. Define the key for this EntityType". The model types are given, I cannot change them and add the [<Key>] attribute. So I tried the Fluent API.

In C#, you would do something like this:

modelBuilder.Entity<Model>().HasKey(m => new { m.IdOne, m.IdTwo });

It uses an anonymous type. But for the life of me I cannot figure out how to pull this off in F#. I tried Tuples, Records, even a regular type that has the properties IdOne and 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

None of these approaches work, I get an ArgumentNullException every time. I'm out of ideas...


EDIT With the code Tomas provided below (that causes the same ArgumentNullException btw.), I did some snooping around. This is what I found:

I used the function below to analyze the Expression Tree C# is building:

static void Analyze<T>(Expression<Func<Model, T>> function)
{
}

// Call it like this:
Analyze(m => new { m.IdOne, m.IdTwo });

Then I looked at the generated lambda in the debugger. This is what C# generates:

{m => new <>f__AnonymousType0'2(IdOne = m.IdOne, IdTwo = m.IdTwo)}

Doing the same thing on the F# side using the getFuncTree function from Tomas using <@ fun (m : Model) -> ModelKey(m.IdOne, m.IdTwo) @> yields:

{m => new ModelKey(m.IdOne, m.IdTwo)}

As you can see, the explicit naming of the - what is this anyway, looks like properties - arguments is missing in the F# code. I recreated the whole expression tree by hand in F# so that it would look like the C# version:

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"
                )
            ]
        )

The part that was missing in the F# representation is the members sequence. When I move the mouse over this expression, this representation appears:

{m => new ModelKey(IdOne = m.IdOne, IdTwo = m.IdTwo)}

As you can see, apart from the class, it looks the same. But when I try to use this expression in the HasKey method, I get the following 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 }'.

So it seems to me that this anonymous class syntax does something special...

Was it helpful?

Solution 3

It looks like the issue is twofold:

  1. F#'s compiler never sets the Members collection of a LINQ NewExpression during quotation translation, but this is used to mark the construction of an anonymous type, which EF expects.
  2. EF is really picky: even in C#, doing m => new { A = m.IdOne, B = m.IdTwo } won't work - the anonymous type's property names must match the model's property names.

One way to work around the issue (which is probably overkill, but works) is to create a new type dynamically at runtime which has fields with the right names and then just use a tuple in the F# code:

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 @>)

OTHER TIPS

In F# 4.6, this has worked for me after much struggle.

Apperantly all you need is a tuple. Makes sense since an anon object without explicit member names is basically, a tuple.

I can't believe nobody has put this on the MS documentation tho.

    modelBuilder.Entity<Entity>()
      .HasKey(fun e -> (e.Key1, e.Key2) :> obj)
      |> ignore

EDIT: The technique below would be needed in F# 2.0, but it should not be needed in the newer versions. There must be some other issue with the F#-generated expression tree...

I think the problem is that Entity Framework wants you to specify a lambda expression as an expression tree:

modelBuilder.Entity<Model>().HasKey(fun (m : Model) -> ModelKey (m.IdOne, m.IdTwo))

This should just work in F# 3.1 in Visual Studio 2013, but it is not supported in F# 3.0. You can still do this, but you'll have to use F# quotations and write a bit of code that converts a quotation to LINQ expression tree - there is a helper that does most of the work:

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 @>

Using this, you should be able to call:

modelBuilder.Entity<Model>().HasKey(getFuncTree <@ fun (m : Model) -> 
  ModelKey (m.IdOne, m.IdTwo) @>)

You could define an extension method like HasKeyQuot that does this behind the cover, to make the code a bit nicer.

// F# 3.0    
open Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter

// Regular type with properties IdOne & IdTwo.
type ModelKey (idOne, idTwo) =
    member this.IdOne = idOne
    member this.IdTwo = idTwo

modelBuilder.Entity<Model>()
    .HasKey(QuotationToLambdaExpression(
             <@ Func<_,_>(fun m -> NewAnonymousObjectHelper<_>(ModelKey(m.IdOne, m.IdTwo))) @>
                                       )
            )
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top