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.... :)
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 |
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() |
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.
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.