Question

I want to eagerly load some records from and their relations from the database something like this:

let getEmails() =
    let emails =
        (query { for q in entities.QueueItems do
                    select q.Email
                    take batchSize }
        ).Include(fun (e:Email) -> e.QueueItem)
        |> Seq.toArray

    emails 
    |> Array.iter (fun e -> entities.QueueItems.Remove(e.QueueItem) |> ignore)

    entities.SaveChanges(logger) |> ignore 
    emails

This works great, although I have to wrap the query expression in brackets to be able to call include on it which looks a bit weird. I wondered if I could write a helper function to call Include in a more idiomatic F# style, and I came up with this.

module Ef =
    let Include (f:'a -> 'b) (source:IQueryable<'a>) = 
        source.Include(f)

now my query can look like this (type inference works on the queryable type :D)

let emails =
    query { for q in entities.QueueItems do
                select q.Email
                take batchSize }
    |> Ef.Include(fun e -> e.QueueItem)
    |> Seq.toArray

It compiles! But when I run it, I get an error from the DbExtensions library telling me The Include path expression must refer to a navigation property defined on the type.

Inspecting the lambda function before it's passed to Queryable.Include, it looks like this {<StartupCode$Service>.$Worker.emails@30} Microsoft.FSharp.Core.FSharpFunc<Entities.Email,Entities.QueueItem> {<StartupCode$Service>.$Worker.emails@30}.

I guess problem is to do with how my lambda is being interpreted and conversions between FSharpFuncs and Expression<Func<>>s. I tried to rewrite my helper function so it had an Expression<Func<'a, 'b>> as its first parameter, and even downloaded the FSharp.Core source to look for inspiration in the implementations of the Seq module and QueryBuilder, but I couldn't get anything working. I tried redefining my helper function as so:

module Ef =
    let Include (y:Expression<Func<'a,'b>>) (source:IQueryable<'a>) = 
        source.Include(y)

But then I get the compiler error This function takes too many arguments, or is used in a context where a function is not expected.

I'm a bit stumped. Can anyone suggest how I can get this working?

Was it helpful?

Solution

AFAIR type-directed conversions are applied only to uncurried type members, not to let bindings. As a fix you can try to change Ef.Include to be a static member

type Ef = 
    static member Include (f : Expression<System.Func<'a, 'b>>) = 
        fun (q : IQueryable<'a>)  -> q.Include f
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top