Zaid-Ajaj / LiteDB.FSharp

Advanced F# Support for LiteDB, an embedded NoSql database for .NET with type-safe query expression through F# quotations
MIT License
181 stars 20 forks source link

How to use an Id well in Classes, and save mutable values to LiteDB #30

Closed srlopez closed 5 years ago

srlopez commented 5 years ago

Hi @Zaid-Ajaj, I would like to use an autoincremental Id also in Classes. In the documentation it is very well explained that when it comes to records we have to classify it as [<CLIMutable>]and put Id = 0 so that the Identifier autoincrements itself. But that is not so established with the classes. I ask if there is a plan to do it also in classes?

In the meantime testing witdh this example class

type Article( Id: ObjectId, Name: string) as self =
    let mutable mutableValue = 0 
    do
        mutableValue <- 10
        printfn "Initialized object"
    // private mutable value
    member val Id = Id //Read Only 
    member val Name = Name with get, set
    // public wrapper for mutable value
    member this.SetMutableValue x = 
        mutableValue <- x 

TheId type set to ObjectId type and get one before inserting a new Article like this: let myArticle = new Article(ObjectId.NewObjectId(), "title")

I'm not sure that's the best way, since: let result = articles.FindById(myArticle.Id) shows the following error:

error FS0193: Type constraint mismatch. The type 'ObjectId' is not compatible with type 'BsonValue'.

I accept it's not the right type and I try that way: let result = articles.findOne <@ fun a -> a.Id = myArticle.Id @>, and everything works until I change the = for another operator <= to: let result = articles.findOne <@ fun a -> a.Id <= myArticle.Id @> and get:

error FS0001: The type 'ObjectId' does not support the 'comparison' constraint. For example, it does not support the 'System.IComparable' interface

The only issue here is that I don't know which is the correct way to use Id in a Class, if I want the DB to assign them automatically.

At the same time and with the class I've expounded before. Only the constructor parameters are stored in the DB, the let mutable mutableValue is not stored. Is there any way they persist in the database, which I've overlooked? If this is the behavior designed, we adapt without problems. But I think it will be interesting for the product that these values are saved in the DB by default, and if you don't want to apply them [<BsonIgnore>].

Thank you for everything. And it's really great work you're doing with LiteDB.FSharp.

humhei commented 5 years ago

AutoID in classes

You should set Val id with get and set; https://github.com/mbdavid/LiteDB/blob/0864d8f888af427571487b84652567c1fd78d97f/LiteDB.Tests/Database/AutoId_Tests.cs#L12

I don't know how does it get worked With a readonly property

type Article(Name: string) =
    // private mutable value
    member val Id = new ObjectId() with get,set
    member val Name = Name with get, set

At the same time and with the class I've expounded before. Only the constructor parameters are stored in the DB, the let mutable mutableValue is not stored. Is there any way they persist in the database, which I've overlooked?

By default, field is not expected to serialized by converter(While property is)

The purpose of Bsonfield in official Reposity is only to specfic the Name in database and make private internal protectd property serialiable

Both Bsonfield and Bsonignore are desired to apply to properties

https://github.com/mbdavid/LiteDB/search?q=BsonField&unscoped_q=BsonField

Correct me if i am wrong @Zaid-Ajaj

srlopez commented 5 years ago

Thanks @humhei, mutable member doesn't need to be in constructor if they are initialized. I'm learning.

    member val mutableMember = 0 with get, set  //<- Initialized
    member val Name = Name with get, set //<- set on constructor

They are both saved to DB. Thanks But: I still don't know which is the correct way to use Id in a Class, if I want the DB to assign them automatically. And if I continue using 'ObjectId' as the type of Id, let result = articles.findOne <@ fun a -> a.Id <= myArticle.Id @> the error is The type 'ObjectId' does not support the 'comparison' constraint. Take care about <=, the = is working.

humhei commented 5 years ago

This is because that ObjectID doesn't implement IComparable interface

I am using int instead of ObjectId

If you want use ObjectId anyWay

you should implement IComparable yourselt

For example

type MyObjectId() =
      inherit ObjectId()
      interface IComparable with 
             member x.CompareTo y = 
                    ----------do your compare here----------------
Zaid-Ajaj commented 5 years ago

As explained in #29 there is a reason that comparison operators don't work with identities: it doesn't make sense. The Id property to identify a single record of data, not a range of records. Which is also why ObjectID doesn't implement IComparable: it doesn't need to.

As for using classes in LiteDB.FSharp, I don't know why you would do that because again this introduces tight coupling between the persistence layer (modeling the record types) with your business layer (modeling behavior in your classes). Trying to hide the lambda expressions to make the model "readable" by domain expects isn't really a solution. Using classes is arguable less readable compared to just plain old functions:

type Computer = { Id: int; Manufacture: string }

module Computers = 

  let private computerDocuments = db.GetCollection<Computer>("computers")
  let getAll() = computerDocuments.FindAll()
  let findByManufacture (input: string) = computerDocuments.findMany (fun c -> c.Manufacture.Contains input)

I guess this answers your comment about domain modeling? (sorry I can't find it anymore)

If you can get this library to work with classes, then that is fine but it is really out of scope of this library to support classes: not idiomatic F#. The documentation clarifies this in the readme file:

LiteDB.FSharp is made mainly to work with records as representations of the persisted documents

srlopez commented 5 years ago

Thanks @Zaid-Ajaj, @humhei It was misunderstood on my part. In a class with Id as int without get and set, and that way you couldn't assign Id from LiteDB. Then I tried to use ObjectID. By fixing that, as @humhei reminded me, you can use Id as int, and LiteDB assigns the number correlatively.

You are right when you say that ObjetcID adds coupling, but if it's used as a GUI. But as it is currently calculated as sequential, which will be the same as an int. https://github.com/mbdavid/LiteDB/wiki/Data-Structure

Unlike the Guid data type, ObjectIds are sequential, so it's a better solution for indexing. ObjectIds use hexadecimal numbers represented as strings.

So the @humhei's fix

Only the = operator is supported (but now does with the other operators too thanks to @humhei)

has come in handy for all of us and is perfectly correct. Thanks guys.