Search code examples
f#neo4jneo4jclient

F# - cypher query with multiple return values


Given this query (from here)

  let pAfollowers =
        client.Cypher
            .Match("n<-[:follows]-e")
            .Where(fun n -> n.Twitter = "tA")
            .Return<Person>("e")
            .Results
            .Select(fun x -> x.Name)

I would like to tweak it and have it return multiple values packaged together. Not sure about how the type should look:

let pAfollowers =
        client.Cypher
            .Match("n<-[r:follows]-e")
            .Where(fun n -> n.Twitter = "tA")
            .Return<???>("n, r, e")

Secondly I was wondering if it is possible to have a return statement after a CreateUnique. I am trying to tweak this query:

let knows target (details : Knows) source =
        client.Cypher
            .Match("(s:Person)", "(t:Person)")
            .Where(fun s -> s.Twitter = source.Twitter)
            .AndWhere(fun t -> t.Twitter = target.Twitter)
            .CreateUnique("s-[:knows {knowsData}]->t")
            .WithParam("knowsData", details)
            .ExecuteWithoutResults()

to have it return s, t and the details.


Solution

  • OK, good news / bad news - though in practice the good is tempered with bad :(

    Good first:

    You can return after a CreateUnique, something like:

    .CreateUnique("s-[:Knows {knowsData}]-t")
    .WithParam("knowsData", details)
    .Returns<???>( "s,t" )
    .Results;
    

    Bad news:

    The bad news is that you probably can't do it in F#. Neo4jClient requires you to use either object initializers, or anonymous types to cast the data, so you could try something like:

    type FollowingResults = { Follower : Person; Followed : Person;}
    
    let createExpression quotationExpression = LeafExpressionConverter.QuotationToLambdaExpression quotationExpression
    
    let pAfollowers =
        client.Cypher
            .Match("n<-[:follows]-e")
            .Where(fun n -> n.Twitter = "tA")
            .Return(createExpression <@ Func<ICypherResultItem, ICypherResultItem, FollowingResults>(fun (e : Cypher.ICypherResultItem) (n : Cypher.ICypherResultItem) -> {Follower = e.As<Person>(); Followed = n.As<Person>()}) @>)
            .Results
            .Select(fun x -> x)
    
    for follower in pAfollowers do
        printfn "%s followed %s" follower.Follower.Name follower.Followed.Name
    

    For which the F# compiler will have no problems at all. However, Neo4jClient will throw an Argument exception with the following message:

    The expression must be constructed as either an object initializer (for example: n => new MyResultType { Foo = n.Bar }), an anonymous type initializer (for example: n => new { Foo = n.Bar }), a method call (for example: n => n.Count()), or a member accessor (for example: n => n.As().Bar). You cannot supply blocks of code (for example: n => { var a = n + 1; return a; }) or use constructors with arguments (for example: n => new Foo(n)).

    The problem being, F# doesn't have object initializers, nor anonymous types, you can wrangle with the F# stuff for ages and not get anywhere, as the C# doesn't recognize the F# initialization.