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
180 stars 22 forks source link

Serializing and deserializing f# functions is not supported #7

Closed humhei closed 6 years ago

humhei commented 6 years ago

Today i tried to do make a model something like this

[<CLIMutable>]
type Product =
    {
        Id: int
        Name: string
        //PrinterKey: Expression<Func<Item,obj>>
        ImposerKey: obj -> obj
    }

When i deserialized it from database, below exception was thrown

Newtonsoft.Json.JsonSerializationException: 'Could not create an instance of type Microsoft.FSharp.Core.FSharpFunc`2[System.Object,System.Object]. Type is an interface or abstract class and cannot be instantiated. Path 'Products[0].ImposerKey', line 1, position 133.'
Zaid-Ajaj commented 6 years ago

Serializing arbitrary functions into Bson (or any persistence mechanism) doesn’t really make sense, what do you expect the library to do?

humhei commented 6 years ago

Currently i use it as property expression to support dynamic ORM in fsx I need to transform fsharp type bson datas to csv so it can be invoked by other UI program

Seq.csvWithProp  (Expr.prop (fun (it:Item) -> 
    it.Brand,it.Art,it.Size,it.Barcode,it.Image,it.Material)) items

items is retrived from litedb so i think

Expr.prop (fun (it:Item) -> 
it.Brand,it.Art,it.Size,it.Barcode,it.Image,it.Material

should also be retrived from litedb

image

humhei commented 6 years ago

I tried fsharplu it taked the some exception while FsPickler can serialize and deserialize function but is not compact to LiteDB

humhei commented 6 years ago

For me there are two solutions

humhei commented 6 years ago

I think i will take some time to see if the first solution is feasible or not

Zaid-Ajaj commented 6 years ago

Another simpler solution is to persist the fields as a list of strings such as [“Brand”; “Art”] and then:

dynamicallyRead item [“Brand”; “Size”] => [“Play ...”; “35” ]

Where dynamicallyRead uses reflection on the fields of items to retrieve their values

Zaid-Ajaj commented 6 years ago

Nested property values can have a path i.e. [“NestedField.PropName”]

A general solution to persisting arbitrary logic in functions is not a good fit for this particular problem, there is always a simpler solution

humhei commented 6 years ago

Thanks for answer 😀

humhei commented 6 years ago

I still encounter this problem 😓 follow is my AST

[<CLIMutable>]
type Item = 
    {
        Id: int
        Template: string option
        Art: string
        Brand: string option
        Color: ColorEnum option
        Material: Material option
        Image: string option
        Barcode: string option
        Sex: Sex option
        Fraction: int option
        Code: int option
        SizeRange: string option
        EUR: float
        US: float option
        UK: float option
        CM: float option
        Price: string option
        Number: int 
    }
[<CLIMutable>]
type Order =
    {
        Id: int
        Name: string
        Items: Item list
        Date: DateTime
    }
[<CLIMutable>]
type Product =
    {
        Id: int
        Name: string
        Kind: ProductKind
        Orders: Order list
        UnitPrice: float
        Paper: Paper option
    }

Now i want to add a Filter to Product to conditionally determine whether this product take effect The logic of product taking effect is arbitrary ,Because customs 's logic is always not the some :) For example

What i want to do ? Take a chance to introduce FsPickler to this repository as a option to resolve complex serializing and deserializing problems

Please tell me what you think @Zaid-Ajaj

Zaid-Ajaj commented 6 years ago

Your business logic should be in your code, not in your storage system. The only reason you would store arbitrary filters inside the database, is to be able to update them by hand (without the application) which I don't think you actually want.

You would put this kind of logic in place where you are reading the values from the database in a where query:

// order that contains any item with "CROSS DOCK" is invalid 
let orderHasInvalidItems (order : Order) = 
  Seq.forall (fun item -> item.Art.Contains("CROSS DOCK")) order.Items) 

// get all products of which the orders are valid (their items do not contain "CROSS DOCK") 
let getActiveProducts (db: ILiteDatabase) = 
  let products = db.GetCollection<Product> "products"
  products.where 
      <@ fun prod -> prod.Orders @> 
      (fun orders -> Seq.forall (fun order -> not (orderHasInvalidItems  order) orders) 

otherwise if you really needed to store F# filters, I suggest you use a simple embedded DSL that you can serialize to the database (then you will be doing what LiteDB is doing for you now):

type FilterExpr = 
  | NoFilter 
  | Equals of propName:string * value:obj 
  | LessThan of propName:string * value:float 
  | GreaterThan of propName:string * value:float 
  | Contains of propName:string * value:string 
  | And of left:FilterExpr * right:FilterExpr 
  | Or of left:FilterExpr * right:FilterExpr  
  | Not of expr:FilterExpr 
  | NestedFilter of propName:string * filter:FilterExpr 

You will also need to evaulate the expr yourself against dynamic record values from the database

Zaid-Ajaj commented 6 years ago

I am assuming you solved your issue, if it happened to be the case that you still need to serialize or deserialize functions, then just use a byte[] as the data format to store the function with FsPickler for converting between the bytes and functions. This way you don't need to integrate FsPickler with this library internally

humhei commented 6 years ago

then just use a byte[] as the data format to store the function with FsPickler

I like this way 👍 ,It's flexible. I may finally directly write function in .fsx file (do everything in it,assum it a middleware to database and my ui program)

humhei commented 6 years ago

Thanks for your answer again! 😄

Zaid-Ajaj commented 6 years ago

@humhei No problem, happy to help ;)

humhei commented 5 years ago

Also find a alternative way want to share you @Zaid-Ajaj Using excel UDF by Excel DNA https://github.com/Excel-DNA to store functions Moreever, With Dynamic array formulas is really cool