LiteDB.FSharp

by Zaid-Ajaj

Zaid-Ajaj / LiteDB.FSharp

Advanced F# Support for LiteDB, an embedded NoSql database for .NET with type-safe query expression ...

138 Stars 15 Forks Last release: Not found MIT License 200 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

LiteDB.FSharp Build Status Nuget

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

Usage

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:

fsharp
let albums = db.GetCollection("albums")

Insert documents

let metallica = 
    { Id = 1; 
      Name = "Metallica";
      Genre = Metal;
      DateReleased = DateTime(1991, 8, 12) }

albums.Insert(metallica)

Query one document by Id

// result : Album
let result = albums.findOne  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
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)

Query documents by value of discriminated union

// 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)

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
let albumsLastYear = albums.Find(query)

Customized Full Search using quoted expressions

// Filtering albums released a year divisble by 5
// filtered : Seq
let filtered = 
    albums.fullSearch 
         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
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 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("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()

Id auto-incremented

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) }

DbRef

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)

Inheritence

Item1
and
Item2
are 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 : int
Note: 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 : 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:

```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()

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.