F# and Raven DB

Ok so I have blogged about these to before. Previously, F# didn't have brilliant support for Linq. F# Expressions couldn't easily be converted to the Linq expression trees that the RavenDB API required. This caused somewhat of a mis-match between the two which made Raven difficult but not impossible to use from F#. My previous blog introduced a library which is available for Raven clients prior to version 2.0, to bridge this gap, and tried to make Raven more natural to use from F#. However as of Raven 2.0 this library has been removed. The reasons are explained here. I don't disagree with the reasons ayende cut the support, I wouldn't want to support something I had little knowledge of either. However things have changed.... :)

The advent F# 3 and query expressions

So we are now in the era of F# 3.0 and things have changed somewhat. F# is now truly in the data era... Information Rich programming is an awesome feature of F# manifested in the form of Type Providers and Query Expressions. If you haven't read about or don't know what type providers are then I encourage you to check them out here. Type providers are not really applicable for use with RavenDB see it is schemaless so for the rest of thispost we will focus on Query Expressions. It is this feature that means the gap between Linq and F# no longer exists. If you are familiar with

var result = 
    (from x in xs do
     where x.Published >= someDate
     select x.Name).ToArray()

then the query expression syntax shouldn't feel that different,

1: 
2: 
3: 
4: 
5: 
6: 
let publishedOn date xs =
    query {
           for x in xs do
           where (x.Published >= date)
           select x.Title
      } |> Seq.toArray

So What about RavenDB?

Using RavenDB from C# is well documented and things are not that different when using it from F#. The in's and out's are well known and lets face it the API is your safety net. It doesn't let you kill yourself, in fact you have to try very hard to actually do anything really bad in RavenDB. This is I think the best feature of RavenDB.

So, what are the things that we need to be aware of when using RavenDB from F#? First things first, initializing the document store. This can be done pretty much the same as in C#

1: 
let store = new DocumentStore(Url = "http://localhost:8080")

and this is all well and good if we just use straight forward POCO objects. But what if we want to use F# record or Union types? We need to make a few simple changes. Lets first consider F# record types, all we need to do here is declare the Id property as mutable.

1: 
2: 
3: 
4: 
5: 
6: 
type BlogPost = {
    mutable Id : string
    Title : string
    Body : string
    Published : DateTime
}

Simple eh?, but what about Union types, Maps, F# lists? These are a little more complex as Json.NET doesn't do the correct thing to serialize these out of the box. However Json.NET and the internalised Raven counterpart does have an extension point and a UnionTypeConverter or MapTypeConverter as Json.NET implementations can be found here. To use this we need to modify our document store setup a little to include the converter.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let customisedStore =
    let customiseSerialiser (s : Raven.Imports.Newtonsoft.Json.JsonSerializer) =
        s.Converters.Add(new Json.MapTypeConverter())
        s.Converters.Add(new Json.UnionTypeConverter())

    let store = new DocumentStore(Url="http://localhost:8080")
    store.Conventions.CustomizeJsonSerializer <- (fun s -> (customiseSerialiser s))
    store.Initialize()

Querying raven

With the document store setup we can now start querying Raven. As with any Raven query we need to create a session from our document store, then we need to create the query.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let getPostsAsOf asOfDate = 
    use session = customisedStore.OpenSession()
    query {
        for post in session.Query<BlogPost>() do
        where (post.Published >= asOfDate)
        select post
    }

The above creates a query that is pending execution. To execute this we can run,

1: 
2: 
3: 
let posts = 
    getPostsAsOf (DateTime.Now.AddDays(-2.).Date) 
    |> Seq.toArray

this will give us an array of BlogPosts published from 2 days ago. Notice we had to enumerate to an array for the query to actually be executed. This is the same execution semantics as C#; and it is important to realise that there isn't really any magic going on in the world of F#, it is still just a .NET lanuguage it still compiles to CIL and is fully interoperable with any other .NET library.

Indexes

OK so things haven't got much better here in terms of static indexes. Basically you still need to define them in C# and then you can extend the document initialization process by including the assembly that contains the index definitions. However in Raven 2.0, dynamic indexes and promotion to permanent indexes have been massively improved, which reduces the need to create static indexes.

namespace System
namespace System.Collections
namespace System.Collections.Generic
namespace System.Reflection
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Reflection
namespace System.Text
namespace System.IO
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val typeof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typeof
type IEnumerable =
  member GetEnumerator : unit -> IEnumerator

Full name: System.Collections.IEnumerable
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
module Array

from Microsoft.FSharp.Collections
val find : predicate:('T -> bool) -> array:'T [] -> 'T

Full name: Microsoft.FSharp.Collections.Array.find
val empty<'T> : 'T []

Full name: Microsoft.FSharp.Collections.Array.empty
val typedefof<'T> : System.Type

Full name: Microsoft.FSharp.Core.Operators.typedefof
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : params indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
System.Array.CreateInstance(elementType: System.Type, params lengths: int64 []) : System.Array
System.Array.CreateInstance(elementType: System.Type, params lengths: int []) : System.Array
System.Array.CreateInstance(elementType: System.Type, length: int) : System.Array
System.Array.CreateInstance(elementType: System.Type, lengths: int [], lowerBounds: int []) : System.Array
System.Array.CreateInstance(elementType: System.Type, length1: int, length2: int) : System.Array
System.Array.CreateInstance(elementType: System.Type, length1: int, length2: int, length3: int) : System.Array
System.Array.Copy(sourceArray: System.Array, destinationArray: System.Array, length: int64) : unit
System.Array.Copy(sourceArray: System.Array, destinationArray: System.Array, length: int) : unit
System.Array.Copy(sourceArray: System.Array, sourceIndex: int64, destinationArray: System.Array, destinationIndex: int64, length: int64) : unit
System.Array.Copy(sourceArray: System.Array, sourceIndex: int, destinationArray: System.Array, destinationIndex: int, length: int) : unit
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
val empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.empty
val query : Linq.QueryBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.query
module Seq

from Microsoft.FSharp.Collections
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
tweet-share