ra0o0f / arangoclient.net

ArangoDB .NET Client with LINQ support
Apache License 2.0
99 stars 37 forks source link

F# Support: Linq Update is impossible due to lack of implicit upcasting #83

Open TOTBWF opened 7 years ago

TOTBWF commented 7 years ago

F# has no implicit upcasting, requiring either a direct cast using :> or the upcast keyword. This causes issues when trying to use the Linq update, as it requires a Func<TSource, Object>, and calls to :> and upcast count as method invocation, meaning there is actually no way to call Update from F#.

TOTBWF commented 6 years ago

As a workaround, this code works, though it is pretty dirty

    let rec private (|RecordReduction|_|) expr =
        let rec traverse env = function
            | Let(v,e,r) -> traverse (env |> Map.add v e) r
            | NewRecord(ty, exprs) -> 
                let result = 
                    exprs 
                    |> List.map(fun e -> 
                        match e with
                        | Var v -> Map.find v env
                        | _ -> e)
                Some(Expr.NewRecord(ty, result))   
            | _ -> None
        traverse Map.empty expr

    type IQueryable<'TSource> with

            // Here be dragons
            member source.Update([<ReflectedDefinition>]selector: Expr<'TSource -> 'TSource> ) =
                let expr =
                    match selector with
                    | Lambda(v, RecordReduction(record)) -> Expr.Lambda(v, record) |> Expr.Cast<'TSource -> 'TSource>
                    | e -> e
                let linq = LeafExpressionConverter.QuotationToExpression expr
                let call = linq :?> MethodCallExpression
                let lambda = call.Arguments.[0] :?> LambdaExpression
                let generateNewExpression () =
                    let body = lambda.Body :?> NewExpression
                    let ctor = typeof<NewExpression>.GetConstructors(BindingFlags.NonPublic ||| BindingFlags.Instance).[0]
                    let members =  FSharpType.GetRecordFields(typeof<'TSource>) |> Seq.map(fun field -> field :> MemberInfo) |> ResizeArray |> ReadOnlyCollection
                    ctor.Invoke([| body.Constructor; body.Arguments; members |]) :?> NewExpression
                let boxedSelector = Expression.Lambda<Func<'TSource, obj>>(generateNewExpression(), lambda.Parameters)
                QueryableExtensions.Update(source, boxedSelector)