Advanced F# Support for LiteDB, an embedded NoSql database for .NET with type-safe query expression through F# quotations
F# Support for LiteDB in .NET Core and full .NET Framework as well.
LiteDB.FSharp provides serialization utilities making it possible for LiteDB to understand F# types such as records, unions, maps etc. with support for type-safe query expression through F# quotations
LiteDB.FSharp comes with a custom
BsonMappercalled
FSharpBsonMapperthat you would pass to a
LiteDatabaseinstance during initialization:
open LiteDB open LiteDB.FSharp open LiteDB.FSharp.Extensionslet mapper = FSharpBsonMapper() use db = new LiteDatabase("simple.db", mapper)
LiteDB.FSharp is made mainly to work with records as representations of the persisted documents. The library requires that records have a primary key called
Idor
id. This field is then mapped to
_idwhen converted to a bson document for indexing.
type Genre = Rock | Pop | Metaltype Album = { Id: int Name: string DateReleased: DateTime Genre: Genre }
Get a typed collection from the database:
fsharp let albums = db.GetCollection("albums")
let metallica = { Id = 1; Name = "Metallica"; Genre = Metal; DateReleased = DateTime(1991, 8, 12) }albums.Insert(metallica)
// result : Album let result = albums.findOne album.Id = 1 @>// OR let id = BsonValue(1) // result : Album let result = albums.FindById(id)
// metallicaAlbums : Seq let metallicaAlbums = albums.findMany album.Name = "Metallica" @> // OR let name = BsonValue("Metallica") let query = Query.EQ("Name", name) // metallicaAlbums : Seq let metallicaAlbums = albums.Find(query)
// find all albums where Genre = Rock // rockAlbums : Seq let rockAlbums = albums.findMany album.Genre = Rock @>// OR
let genre = BsonValue("Rock") let query = Query.EQ("Genre", genre) // rockAlbums : Seq let rockAlbums = albums.Find(query)
// find all albums released last year let now = DateTime.Now let dateFrom = DateTime(now.Year - 1, 01, 01) |> BsonValue let dateTo = DateTime(now.Year, 01, 01) |> BsonValue let query = Query.Between("DateReleased", dateFrom, dateTo) // albumsLastYear : Seq let albumsLastYear = albums.Find(query)
// Filtering albums released a year divisble by 5 // filtered : Seq let filtered = albums.fullSearch album.DateReleased @> (fun dateReleased -> dateReleased.Year % 5 = 0)
The function
Query.Whereexpects a field name and a filter function of type
BsonValue -> bool. You can deserialize the
BsonValueusing
Bson.deserializeFieldwhere
'tis the type of the serialized value.
// Filtering albums released a year divisble by 5 let searchQuery = Query.Where("DateReleased", fun bsonValue -> // dateReleased : DateTime let dateReleased = Bson.deserializeField bsonValue let year = dateReleased.Year year % 5 = 0 )let searchResult = albums.Find(searchQuery)
type Shape = | Circle of float | Rect of float * float | Composite of Shape listtype RecordWithShape = { Id: int; Shape: Shape }
let records = db.GetCollection("shapes")
let shape = Composite [ Circle 2.0; Composite [ Circle 4.0; Rect(2.0, 5.0) ] ]
let record = { Id = 1; Shape = shape } records.Insert(record) |> ignore
let searchQuery = Query.Where("Shape", fun bsonValue -> let shapeValue = Bson.deserializeField bsonValue match shapeValue with | Composite [ Circle 2.0; other ] -> true | otherwise -> false ) records.Find(searchQuery) |> Seq.length |> function | 1 -> pass() // passed! | n -> fail()
Add CLIMutableAttribute to record type and set Id 0
fsharp [] type Album = { Id: int Name: string DateReleased: DateTime Genre: Genre } let metallica = { Id = 0; Name = "Metallica"; Genre = Metal; DateReleased = DateTime(1991, 8, 12) }
just as https://github.com/mbdavid/LiteDB/wiki/DbRef
open LiteDB.FSharp.Linq[] type Company= { Id : int Name : string}
[]
type Order= { Id :int Company :Company }let mapper = FSharpBsonMapper() mapper.DbRef(fun c -> c.Company)
Item1and
Item2are inherited from
IItem
we must register the type relations first globally
fsharp FSharpBsonMapper.RegisterInheritedConverterType() FSharpBsonMapper.RegisterInheritedConverterType()By conversion, The inherited type must has mutable field for serializable and deserializable
fsharp val mutable Id : intNote: Because json converter find inherited type by comparing the fields names from inherited type and database
fsharp let findType (jsonFields: seq) = inheritedTypes |> Seq.maxBy (fun tp -> let fields = tp.GetFields() |> Seq.map (fun fd -> fd.Name) let fieldsLength = Seq.length fields (jsonFields |> Seq.filter(fun jsonField -> Seq.contains jsonField fields ) |> Seq.length),-fieldsLength )
This means that we should not implement the some interface with different fields For example,we should not do below implementations ```fsharp type Item1 =
val mutable Id : int val mutable Art : string val mutable Name : string val mutable Number : intinterface IItem with member this.Art = this.Art member this.Id = this.Id member this.Name = this.Name member this.Number = this.Number
/// unexpected codes type Item2 =
val mutable Id2 : int val mutable Art2 : string val mutable Name2 : string val mutable Number2 : intinterface IItem with member this.Art = this.Art2 member this.Id = this.Id2 member this.Name = this.Name2 member this.Number = this.Number2
/// expected codes type Item2 =
val mutable Id : int val mutable Art : string val mutable Name : string val mutable Number : intinterface IItem with member this.Art = this.Art member this.Id = this.Id member this.Name = this.Name member this.Number = this.Number
Full sample codes:```fsharp /// classlibray.fs []
type EOrder= { Id: int Items : IItem list OrderNumRange: string }/// consumer.fs type Item1 = /// val mutable will make field serializable and deserializable val mutable Id : int val mutable Art : string val mutable Name : string val mutable Number : int
interface IItem with member this.Art = this.Art member this.Id = this.Id member this.Name = this.Name member this.Number = this.Number val mutable Barcode : string interface IBarcode with member this.Barcode = this.Barcode /// type constructor new (id, art, name, number, barcode) = { Id = id; Art = art; Name = name; Number = number; Barcode = barcode }
type Item2 = val mutable Id : int val mutable Art : string val mutable Name : string val mutable Number : int
interface IItem with member this.Art = this.Art member this.Id = this.Id member this.Name = this.Name member this.Number = this.Number val mutable Size : int interface ISize with member this.Size = this.Size val mutable Color : string interface IColor with member this.Color = this.Color new (id, art, name, number, size, color) = { Id = id; Art = art; Name = name; Number = number; Size = size; Color = color }
FSharpBsonMapper.RegisterInheritedConverterType() FSharpBsonMapper.RegisterInheritedConverterType()
let item1 = Item1 ( id = 0, art = "art", name = "name", number = 1000, barcode = "7254301" )
let item2 = Item2 ( id = 0, art = "art", name = "name", number = 1000, color = "red" , size = 39 )
let eorder = { Id = 1; Items = [item1;item2]; OrderNumRange = "" }
let queryedEOrder = db |> LiteRepository.insertItem eorder |> LiteRepository.query |> LiteQueryable.first
match queryedEOrder.Items with | [item1;item2] -> match item1,item2 with | :? IBarcode,:? IColor -> pass() | _ -> fail()
| _ -> fail()