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 FSharpFunc
s 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?
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