vendredi 7 juin 2019

F#: How to call Expression.Call for a method with discriminated union in a generic type using a case type?

Note: I purposefully simplified the design below

Let's say, I have a discriminated union type AccountEvent and a class Aggregate that carries two methods:

  • Apply1(event : AccountEvent)
  • Apply2(event : Event<AccountEvent>)

Event<'TEvent> being just a dummy class for sake of having a generic type.

type AccountCreation = {
    Owner: string
    AccountId: Guid
    CreatedAt: DateTimeOffset
    StartingBalance: decimal
}

type Transaction = {
    To: Guid
    From: Guid
    Description: string
    Time: DateTimeOffset
    Amount: decimal
}

type AccountEvent =
    | AccountCreated of AccountCreation
    | AccountCredited of Transaction
    | AccountDebited of Transaction

type Event<'TEvent>(event : 'TEvent)=
    member val Event = event with get

type Aggregate()=
    member this.Apply1(event : AccountEvent)=
        ()

    member this.Apply2(event : Event<AccountEvent>)=
        ()

let doingStuff (aggregateType: Type)(eventType: Type)(method: MethodInfo) =
    let aggregateParameter = Expression.Parameter(aggregateType, "a")
    let eventParameter = Expression.Parameter(eventType, "e")
    let body = Expression.Call(aggregateParameter, method, eventParameter)
    ()

[<EntryPoint>]
let main argv =

    let accountCreated = AccountEvent.AccountCreated({
        Owner = "Khalid Abuhakmeh"
        AccountId = Guid.NewGuid()
        StartingBalance = 1000m
        CreatedAt = DateTimeOffset.UtcNow
    })
    let accountCreatedType = accountCreated.GetType()

    let method1 = typeof<Aggregate>.GetMethods().Single(fun x -> x.Name = "Apply1")
    doingStuff typeof<Aggregate> typeof<AccountEvent> method1
    doingStuff typeof<Aggregate> accountCreatedType method1

    let method2 = typeof<Aggregate>.GetMethods().Single(fun x -> x.Name = "Apply2")
    let eventAccountCreatedType = typedefof<Event<_>>.MakeGenericType(accountCreatedType)
    doingStuff typeof<Aggregate> typeof<Event<AccountEvent>> method2
    doingStuff typeof<Aggregate> eventAccountCreatedType method2

    0

What I am trying to do it's to create an Expression corresponding to the call of Apply1 and Apply2 with the type of a discriminated union case as one of the parameter type.

In the case of Apply1 if I am building an Expression be it with AccountEvent or the runtime type of AccountEvent.AccountCreated. It works

However, if I want to achieve the same with Apply2 while it works with Event<AccountEvent>, but it fails working with Event<AccountEvent.AccountCreated>:

System.ArgumentException: Expression of type 'Program+Event`1[Program+AccountEvent+AccountCreated]' cannot be used for parameter of type 'Program+Event`1[Program+AccountEvent]' of method 'Void Apply2(Event`1)'
Parameter name: arg0
  at at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
  at at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0)
  at at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
  at at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments)
  at Program.doingStuff(Type aggregateType, Type eventType, MethodInfo method) in C:\Users\eperret\Desktop\ConsoleApp1\ConsoleApp1\Program.fs:40
  at Program.main(String[] argv) in C:\Users\eperret\Desktop\ConsoleApp1\ConsoleApp1\Program.fs:61

I am wondering how I can adjust the creation of my expression to accept the Event<AccountEvent.AccountCreated>?

I am thinking that maybe there is a need to have an intermediate layer to have a conversion layer from AccountEvent.AccountCreated to its base classAccountEvent (this is how discriminated unions are compiled), or more precisely considering the generic, from Event<AccountEvent.AccountCreated to Event<AccountEvent>.





Aucun commentaire:

Enregistrer un commentaire