Repodb: Question: Question on how to properly configure type mapping for FSharp Option types

Created on 24 Jul 2020  路  15Comments  路  Source: mikependon/RepoDB

I am trying to configure mapping for F# option types but have run into an exception that I could not diagnose. Attached is my attempt.

ConsoleApp1.zip

question

Most helpful comment

The fix for this has been checked in to the main branch. #499. Will be a part of the next beta release.

All 15 comments

Would you be able to check this project? I am not the best resource for F#, maybe I can ask the community. @AngelMunoz would you be a help here?

Or if it is just a technical thing, you can visit the Class Mapping documentation for attributes-based setup, or Implicit Mapping if you wish to setup a mapping without any attributes.

Hey there, I just took a brief look at the sample

I'm not much experienced in that area either but since RepoDB uses Reflection there might be an issue with F#'s explicit interface usage

I stumbled across that recently (https://github.com/worldbeater/Live.Avalonia/blob/main/Live.Avalonia/LiveControlLoader.cs#L40) if you use a class from F# and want to use it's interface via reflection you must be sure to get the method from the interface type not the concrete class, I'm not sure how RepoDB does that but that would be my best guess and I'm not sure that could be the real problem

that being said...
F# types are sometimes complex to interop with C# libraries (and json serialization) I tend to avoid them when writing to the database/serializing to json 馃槥 and use either DTO's + anonymous records with the shape of the database table with raw queries, you can check these lines to give you an idea

But I'd just use the Execute'Query family

Just to add to this

Exception has occurred: CLR/System.NullReferenceException An unhandled exception of type 'System.NullReferenceException' occurred in RepoDb.dll: 'Object reference not set to an instance of an object.' 


stacktrace
Exception has occurred: CLR/System.NullReferenceException
An unhandled exception of type 'System.NullReferenceException' occurred in RepoDb.dll: 'Object reference not set to an instance of an object.'
at RepoDb.Reflection.FunctionFactory.<>c__DisplayClass7_01.<GetDataEntitiesDbCommandParameterSetterFunction>b__0(Int32 entityIndex, Expression instance, ParameterExpression property, DbField dbField, ClassProperty classProperty, Boolean skipValueAssignment, ParameterDirection direction) at RepoDb.Reflection.FunctionFactory.GetDataEntitiesDbCommandParameterSetterFunction[TEntity](IEnumerable1 inputFields, IEnumerable1 outputFields, Int32 batchSize, IDbSetting dbSetting) at RepoDb.FunctionCache.DataEntitiesDbCommandParameterSetterFunctionCache1.Get(String cacheKey, IEnumerable1 inputFields, IEnumerable1 outputFields, Int32 batchSize, IDbSetting dbSetting)
at RepoDb.FunctionCache.GetDataEntitiesDbCommandParameterSetterFunctionTEntity
at RepoDb.DbConnectionExtension.<>c__DisplayClass252_01.<InsertAllInternalBase>b__0(Int32 batchSizeValue) at RepoDb.Contexts.Execution.InsertAllExecutionContextCache1.Get(String tableName, IEnumerable1 fields, Int32 batchSize, Func2 callback)
at RepoDb.DbConnectionExtension.InsertAllInternalBaseTEntity
at Program.main(String[] argv) in C:\Users\scyth\Downloads\ConsoleApp1\ConsoleApp1\Program.fs:line 61

which leads me think this might be the method with the issue it might as well happen with the Getter
https://github.com/mikependon/RepoDb/blob/b1e0b5a9a0d9ec938a33688cefe20268d517d7e1/RepoDb/RepoDb/Reflection/FunctionFactory.cs#L984-L989

Thanks @AngelMunoz for the reply.

F# and want to use it's interface via reflection you must be sure to get the method from the interface type not the concrete class, I'm not sure how RepoDB does that but that would be my best guess and I'm not sure that could be the real problem

This is handled internally, the type references of the underlying interface used by the class is always passed before the compilation. If that reference is null, then I used the DeclaringType property in the compilation. So that would fix the problem there.

The only problem that I know in relation to F# is that, F# classes are immutable and that RepoDb would not work there until you specific the CLIMutable attribute.

Maybe you can guide the guy how to call the FluentMapper.Entity().Table("TableName") method?

@lambdakris, is the issue above mentioned by @AngelMunoz the same as what you are encountering? If that is the case, then I need to look at it. It seems the RepoDb is failing in reflecting the type in F#. Have to research it.

Hey @mikependon and @AngelMunoz, thanks for looking at this. I have a feeling that @AngelMunoz might be on to something because the problem seems to occur before the PropertyHandler methods have a chance to execute. I tried to recreate the example in the docs which simply serializes and deserializes a json string to an object, and I still get the same exception. I haven't tried it in C#, but assuming that it works as expected there, then it really might be an issue with how interfaces are implemented in F#. Below is the example:

the table...

create table dbo.Customer (
    Id int identity(1,1) not null,
    Name nvarchar(100) not null,
    Address nvarchar(500) not null
);

the code...

open System
open System.Data.SqlClient
open RepoDb
open RepoDb.Attributes
open RepoDb.Interfaces
open Newtonsoft.Json

[<CLIMutable>]
type Address = {
    City: string
    State: string }

type AddressPropertyHandler() =
    interface IPropertyHandler<string, Address> with
        member this.Get (input, property) =
            JsonConvert.DeserializeObject<Address>(input)
        member this.Set (input, property) =
            JsonConvert.SerializeObject(input)

[<CLIMutable>]
type Customer = {
    Id: int
    Name: string
    [<PropertyHandler(typeof<AddressPropertyHandler>)>] 
    Address: Address 
}

[<EntryPoint>]
let main argv =
    RepoDb.SqlServerBootstrap.Initialize()

    let customer1 = { Id = 0; Name = "customer 1"; Address = { City = "San Juan"; State = "Puerto Rico" } }

    use connection = new SqlConnection("server=localhost;database=repodb;trusted_connection=true")

    connection.Insert<Customer>(customer1) |> ignore

    0 // exit code

@lambdakris - now that I have your model and table schema, can you also paste your exception here? Is it identical to what @AngelMunoz has encountered (i.e.: NullException)?

Sure, here is the serialized exception...

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=RepoDb
  StackTrace:
   at RepoDb.Reflection.FunctionFactory.<>c__DisplayClass6_0`1.<GetDataEntityDbCommandParameterSetterFunction>b__0(Expression instance, ParameterExpression property, DbField dbField, ClassProperty classProperty, Boolean skipValueAssignment, ParameterDirection direction)
   at RepoDb.Reflection.FunctionFactory.GetDataEntityDbCommandParameterSetterFunction[TEntity](IEnumerable`1 inputFields, IEnumerable`1 outputFields, IDbSetting dbSetting)
   at RepoDb.FunctionCache.GetDataEntityDbCommandParameterSetterFunctionCache`1.Get(String cacheKey, IEnumerable`1 inputFields, IEnumerable`1 outputFields, IDbSetting dbSetting)
   at RepoDb.FunctionCache.GetDataEntityDbCommandParameterSetterFunction[TEntity](String cacheKey, IEnumerable`1 inputFields, IEnumerable`1 outputFields, IDbSetting dbSetting)
   at RepoDb.DbConnectionExtension.<>c__DisplayClass242_0`2.<InsertInternalBase>b__0()
   at RepoDb.Contexts.Execution.InsertExecutionContextCache`1.Get(String tableName, IEnumerable`1 fields, Func`1 callback)
   at RepoDb.DbConnectionExtension.InsertInternalBase[TEntity,TResult](IDbConnection connection, String tableName, TEntity entity, IEnumerable`1 fields, String hints, Nullable`1 commandTimeout, IDbTransaction transaction, ITrace trace, IStatementBuilder statementBuilder, Boolean skipIdentityCheck)
   at RepoDb.DbConnectionExtension.InsertInternal[TEntity,TResult](IDbConnection connection, TEntity entity, String hints, Nullable`1 commandTimeout, IDbTransaction transaction, ITrace trace, IStatementBuilder statementBuilder)
   at RepoDb.DbConnectionExtension.Insert[TEntity](IDbConnection connection, TEntity entity, String hints, Nullable`1 commandTimeout, IDbTransaction transaction, ITrace trace, IStatementBuilder statementBuilder)
   at Program.main(String[] argv) in C:\Users\cax9124\source\repos\ConsoleApp1\ConsoleApp1\Program.fs:line 36

And the content of the {exception}.TargetSite property just in case...

{System.Linq.Expressions.Expression <GetDataEntityDbCommandParameterSetterFunction>b__0(System.Linq.Expressions.Expression, System.Linq.Expressions.ParameterExpression, RepoDb.DbField, RepoDb.ClassProperty, Boolean, System.Data.ParameterDirection)} | System.Reflection.MethodBase {System.Reflection.RuntimeMethodInfo}

The fix for this has been checked in to the main branch. #499. Will be a part of the next beta release.

Does this mean that Option or any types are mappable using FluentMapper?

@Swoorup - Apology for overlook. The options are not a part of our latest beta released. Please keep this ticket open.

@mikependon Any timeline for the release?

Starting the evaluate this library to replace current TypeProvider based library on a F# codebase. It hits a sweet spot before EF and using TP libraries directly writing queries. 馃憤

@mikependon @lambdakris @AngelMunoz
Can confirm that I can directly use Option types in POCO model with the latest release.

EDIT: Spoke too soon. Didn't ran the code I thought I did. Doesnt appear to work for
attributes like [

Option.ofObj
Nullable

Can you provide the error stack trace if you are using that attribute?

Was this page helpful?
0 / 5 - 0 ratings