Monday, August 04, 2014

More on composing queries in F#

I ran across some StackOverflow questions from a couple of years ago asking about composing query expressions in F#, and gave answers showing how FSharpComposableQuery can help with this:

http://stackoverflow.com/questions/13826749/how-do-you-compose-query-expressions-in-f

http://stackoverflow.com/questions/10158512/dynamic-sql-queries-with-f-3-0

Tomas Petricek's previous posts on dynamic LINQ queries in C# and F# 2.0 were, of course part of the inspiration for our work.  FSharpComposableQuery updates these ideas to F# 3.0 and, hopefully, makes them easily usable without the need for tricks.

I also ran across a more recent blog post by Loïc Denuziere that discusses the related issue of how to splice partial F# query expressions together to build more complex ones.  His approach cleverly defines an alternative query operator, pquery, that can be used to define query snippets that don't get evaluated immediately.  He suggests the following code (copied from the end of the post):


// Utility code, write it once
type PartialQueryBuilder() =
  inherit Linq.QueryBuilder()
  member this.Run(e: Expr<Linq.QuerySource<'T, IQueryable>>) = e
let pquery = PartialQueryBuilder()
type Linq.QueryBuilder with
  [<ReflectedDefinition>]
  member this.Source(qs: Linq.QuerySource<'T, _>) = qs
This code means that any queries written using pquery effectively just get wrapped in quotations, and QueryBuilder.Source is overloaded to recognize these quotations and embed them into ordinary queries seamlessly.  This can be used as follows (code also copied from the post):
// Example usage
let GetLatestMessages num usernameOpt =
  use db = (* retrieve LINQ-to-SQL context *)
  let baseQuery =
    pquery{ for m in ctx.Messages do
            sortBy m.PostedDate
            select m }
  let filteredQuery =
    match usernameOpt with
    | None -> baseQuery
    | Some username ->
      pquery{ for m in %baseQuery do
              join u in db.Users on (m.UserId = u.Id)
              where (u.Username = username)
              select m }
  query { for m in %filteredQuery do
          take num }
  |> Array.ofSeq

 In FSharpComposableQuery, this is not necessary, because wherever one would write pquery {...} one can instead just write <@ query {...} @> and normalization ensures that the right thing will happen.  For example, I believe Loïc's main example program can be written as follows using FSharpComposableQuery:

let GetLatestMessages num usernameOpt =
  use db = (* retrieve LINQ-to-SQL context *)
  let baseQuery =
    <@ query{ for m in ctx.Messages do
              sortBy m.PostedDate
              select m } @>
  let filteredQuery =
    match usernameOpt with
    | None -> baseQuery
    | Some username ->
      <@ query{ for m in %baseQuery do
                join u in db.Users on (m.UserId = u.Id)
                where (u.Username = username)
                select m } @>
  query { for m in %filteredQuery do
          take num }
  |> Array.ofSeq

This idea seems beneficial (though not strictly necessary) in FSharpComposableQuery also, by decreasing the amount of visual clutter around partial query expressions, so it may make sense to incorporate pquery into the library at some stage, pending testing to make sure that everything still works.

Labels: , ,

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home