F# Support for LiteDB
This package relies on LiteDB 4.14 >= version > 5.0 Support for v5 is work in progress and might require a full rewrite.
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 BsonMapper
called FSharpBsonMapper
that you would pass to a LiteDatabase
instance during initialization:
open LiteDB open LiteDB.FSharp open LiteDB.FSharp.Extensions let 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 Id
or id
. This field is then mapped to _id
when converted to a bson document for indexing.
type Genre = Rock | Pop | Metal type Album = { Id: int Name: string DateReleased: DateTime Genre: Genre }
Get a typed collection from the database:
let albums = db.GetCollection<Album>("albums")
let metallica = { Id = 1; Name = "Metallica"; Genre = Metal; DateReleased = DateTime(1991, 8, 12) } albums.Insert(metallica)
// result : Album let result = albums.findOne <@ fun album -> album.Id = 1 @> // OR let id = BsonValue(1) // result : Album let result = albums.FindById(id)Query many documents depending on the value of a field
// metallicaAlbums : Seq<Album> let metallicaAlbums = albums.findMany <@ fun album -> album.Name = "Metallica" @> // OR let name = BsonValue("Metallica") let query = Query.EQ("Name", name) // metallicaAlbums : Seq<Album> let metallicaAlbums = albums.Find(query)Query documents by value of discriminated union
// find all albums where Genre = Rock // rockAlbums : Seq<Album> let rockAlbums = albums.findMany <@ fun album -> album.Genre = Rock @> // OR let genre = BsonValue("Rock") let query = Query.EQ("Genre", genre) // rockAlbums : Seq<Album> let rockAlbums = albums.Find(query)Query documents between or time intervals
// 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<Album> let albumsLastYear = albums.Find(query)Customized Full Search using quoted expressions
// Filtering albums released a year divisble by 5 // filtered : Seq<Album> let filtered = albums.fullSearch <@ fun album -> album.DateReleased @> (fun dateReleased -> dateReleased.Year % 5 = 0)Customized Full Search using Query.Where
The function Query.Where
expects a field name and a filter function of type BsonValue -> bool
. You can deserialize the BsonValue
using Bson.deserializeField<'t>
where 't
is 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<DateTime> bsonValue let year = dateReleased.Year year % 5 = 0 ) let searchResult = albums.Find(searchQuery)Query.Where: Filtering documents by matching with values of a nested DU
type Shape = | Circle of float | Rect of float * float | Composite of Shape list type RecordWithShape = { Id: int; Shape: Shape } let records = db.GetCollection<RecordWithShape>("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<Shape> 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
[<CLIMutable>] 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 [<CLIMutable>] type Company= { Id : int Name : string} [<CLIMutable>] type Order= { Id :int Company :Company } let mapper = FSharpBsonMapper() mapper.DbRef<Order,_>(fun c -> c.Company)
Item1
and Item2
are inherited from IItem
we must register the type relations first globally
FSharpBsonMapper.RegisterInheritedConverterType<IItem,Item1>() FSharpBsonMapper.RegisterInheritedConverterType<IItem,Item2>()
By conversion, The inherited type must has mutable field for serializable and deserializable
Note: Because json converter find inherited type by comparing the fields names from inherited type and database
let findType (jsonFields: seq<string>) = 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
type Item1 = 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 /// unexpected codes type Item2 = val mutable Id2 : int val mutable Art2 : string val mutable Name2 : string val mutable Number2 : int interface 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 : int interface 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:
/// classlibray.fs [<CLIMutable>] 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<IItem,Item1>() FSharpBsonMapper.RegisterInheritedConverterType<IItem,Item2>() 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<EOrder> |> LiteQueryable.first match queryedEOrder.Items with | [item1;item2] -> match item1,item2 with | :? IBarcode,:? IColor -> pass() | _ -> fail() | _ -> fail()
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4