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.
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.
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
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.
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