MongoDB: how to use it in F#, any users here?

I am trying to figure out how to use MongoDB in F#. As far as I can see, the .Net driver is really C# friendly, but it is giving me hard time in F#.

The issue I have, just for start, is to use F# types to define documents, example:

type NoteDocument =
  { _id: string
    content: string
    operator_id: string
    customer_id: string option
  }

Trouble is, the BSON serializer built in does not understand the F#'s optional and this is just the very beginning of my journey…

There is almost no search results of “mongodb f#” – like queries, most of them are very old or reference the https://github.com/tkellogg/MongoDB.FSharp which is not maintained any more.

So, maybe you have something to help me start with F# and MongoDB? Any ideas, tips’ n tricks?

I’m pretty new to F#, but isn’t this a case of creating a domain transfer function that understands your records and other types on one side, and the things that the DB understands on the other, and figuring out the best way to translate between them?
For example, if your customer ID is optional, then when you serialize it you’d handle the two Option cases at that point. You’d do the same thing on deserialization.
I don’t know much about interacting with databases at this point, so maybe it’s normal for a module to handle this sort of thing in the background so that you don’t have to do it manually, but it seems likely it’s being done one way or the other.

Welcome to the club :slight_smile:

Well, I was hoping to define types describing the shape of documents in database, so one could figure them out without reading any further. I can write code converting the document type (type Doc…) into domain type (type Domain…) though. Your solutions would be to write function converting from domain type straight into some DB-level structure like BsonDocument, but when looking at the very top of the project, I could see domain types, but the functions are usually hidden somewhere deeper, so it takes time to find it and then you read line-by-line to figure out the shape of documents.

On the other side, I could define, e.g. type Note and type NoteDocument, and then define serializer/deserializer for the latter and functions converting from Note into NoteDocument and vice versa, but it all adds a lot of boilerplate code which could be (hopefully) avoided?

Your concerns seem reasonable to me, and I’m not a skilled developer so I can’t really say much against it. For me, if I was looking at a project’s code and looking at the domain types, I wouldn’t have the expectation that those types would have functional properties like ‘shape’ down into the database layer. If I understood the data types and understood they were serialized at some point, my only expectation would be that they de-serialized into something that was equal.

If you want to retain the shape of the data even down into the DB layer out of scope of F# itself, I can only assume you’d be forced to use an object store instead of a traditional relational database. But again, I’m not a developer by trade so I could be completely wrong.

That’s right, MongoDB is not relational, it stores documents in form of BSON – kind of JSON with few more types like dates and binary representation at the storage level.

I think I have found solution to use MongoDB’s build in serializer: each document type needs a [<CLIMutable>], e.g.

[<CLIMutable>]
type NoteDocument =
  { _id: string
    content: string
    operator_id: string
    customer_id: string
  }

This basically means the type is now mutable with everything possibly nullable (well, int is an int in C# anyway, but you can use Nullable<int> if applicable) and you should convert it into something FSharpy ASAP :wink:

Now there are no more exceptions like:

  • Unhandled exception. MongoDB.Bson.BsonSerializationException: No matching creator found. or

  • Unhandled exception. System.FormatException: An error occurred while deserializing the customer_id property of class Program+NoteDocument: Expected a nested document representing the serialized form of a Microsoft.FSharp.Core.FSharpOption`1[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] value, but found a value of type String instead.

Of course, when writing SomeDocument -> SomeDomainObject functions I have to assume everything in document can be literally null, but that’s not a problem: sometimes I can assume no null and fail if found, sometimes I can just convert into Option. I must check for available Computation Expressions to see if I could use one to aid me, especially I am waiting for applicative-like expressions, so I could get list of parsing errors with nice and clean code.

So, tips 'n trick of null in F# are described i.a. here: Null Values - F# | Microsoft Docs

Goole, Bing, DuckDuckGo and others: please index this solution for some other me in other time trying first steps in F# with MongoDb :slight_smile:

2 Likes

I basically did the same thing in one of my projects: https://github.com/Korbin73/realworld-fsharp/blob/master/suaveio_dotnetcore2/lib/BsonDocConverter.fs

I have my models that my program works with and then I send it to this converter for my CRUD operations.

Just as a side note. I’m also an fsharp novice, but I had a similar problem deserializing an id property from the following type:

[<CLIMutable>]
type User = {
    _id: ObjectId
    id: int
    name: string
    age: int
}

let collection = db.GetCollection<User>("users")

let filter = FilterDefinition<User>.Empty

let doc = collection.Find(filter).FirstOrDefault()

printfn "%O" doc

If used like this it will give an error:

MongoDB.Bson.BsonSerializationException: The property 'Id' of type 'FSI_0074+User' cannot use element name '_id' because it is already being used by property '_id'.

So the solution I found would be to add the [<MongoDB.Bson.Serialization.Attributes.BsonIdAttribute>] attribute to the _id property, and conflict solved.

I wish there was an extensive up to date guide on how to do interop :frowning_face: