Using Neo4j with F# – Cypher 2.0

There’s an excellent post by Sergey Tihon about using Neo4j with F#, written back in March, before Neo4j began the progression into Cypher 2.0. (Gist for this here: https://gist.github.com/cskardon/7673426)

Some of the benefits of the newer Neo4jClient versions are the general move away from using Node / NodeReferences and  going for a more consistent Cypher only approach, so I’ve updated the code by Sergey to use the 2.0 stuff (as part of the process of answering this StackOverflow question).

BTW: It should be noted, I am not an F# developer, so any corrections are warmly received!

I’m using VS2013 and I’ve created a new FSharp Console Application for this, which I believe is a different approach to Sergey, but I think you can do the same ‘script’ approach he has, but with this code (below) and we’ll be cooking.

Sergey is creating a basic twitter pattern, so Person A follows Person B etc, we need a POCO for our person:

[<CLIMutable>]
type Person = { Name:string; Twitter:string }

Now we can define our new ‘createPerson’ function:

    let createPerson person =
        client.Cypher
            .Create("(p:Person {param})")
            .WithParam("param", person)
            .Return<Person>("p")
            .Results
            .Single()

Here we’ve attached a ‘Person’ label to our person being created.

So, let’s create some people:

    let pA = createPerson { Name = "PersonA"; Twitter = "tA" }
    let pB = createPerson { Name = "PersonB"; Twitter = "tB" }
    let pC = createPerson { Name = "PersonC"; Twitter = "tC" }
    let pD = createPerson { Name = "PersonD"; Twitter = "tD" }

Now, we need to add the ‘follows’ function:

    let follows target source =
        client.Cypher
            .Match("(s:Person)", "(t:Person)")
            .Where(fun s -> s.Twitter = source.Twitter)
            .AndWhere(fun t -> t.Twitter = target.Twitter)
            .CreateUnique("s-[:follows]->t")
            .ExecuteWithoutResults()

Again, we’re using the Labels in the Match clause, CreateUnique will only create the link if it doesn’t already exist, which is what we’re after. So once again, following Sergey’s example, let’s do some following:

    pB |> follows pA
    pC |> follows pA
    pD |> follows pB
    pD |> follows pC

We now have a graph like so:

In the next step Sergey adds a ‘knows’ relationship, with some data defining ‘how’ the people know each other. So step one, let’s create our Knows data:

[<CLIMutable>]
type Knows = { How:string }

Note, there is no use of the ‘Relationship’ classes here, we’re shifting away from them. Now we need to define our knows function, I’ve modified the function signature here to allow us to use Parameters which is better for Neo4j on the whole, plus we don’t really want a load of ‘Knows’ relationships with different data structures.

    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()

And let’s make B know C…

    pB |> knows pC {How = "colleagues"}

Now we can do the same query as Sergey and get all the people who follow ‘A’:

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

As I said before, I’m not au fait with F# really, there are a couple of things I’d have liked to have done, but I’m not sure how at the moment, the biggest (and most annoying) is the Return statements. They should be:

.Return(fun p -> p.As<Person>())

But the F# compiler complains about that, so I’ve had to drop back to the

.Return<Person>("p")

route. Maybe someone can figure out how to get that to work, which would be awesome!

Print | posted @ Wednesday, November 27, 2013 10:11 AM

Comments on this entry:

Gravatar # re: Using Neo4j with F# – Cypher 2.0
by Grant C at 12/12/2013 3:38 PM

That was useful, was having problems getting it running with the new version, thanks very much
Gravatar # re: Using Neo4j with F# – Cypher 2.0
by Chris at 12/12/2013 3:49 PM

You're welcome!
Gravatar # re: Using Neo4j with F# – Cypher 2.0
by Andrew at 1/5/2014 10:20 PM

You do he last part with the return statement with this:

open Microsoft.FSharp.Linq.RuntimeHelpers

then:

let createExpression quotationExpression = LeafExpressionConverter.QuotationToLambdaExpression quotationExpression

let resultExpression = createExpression <@ Func<ICypherResultItem, Person>(fun (e : Cypher.ICypherResultItem) -> e.As<Person>()) @>

let pAfollowers =
client.Cypher
.Match("n<-[:follows]-e")
.Where(fun n -> n.Twitter = "tA")
.Return(resultExpression)
.Results
.Select(fun x -> x.Name)
Gravatar # re: Using Neo4j with F# – Cypher 2.0
by Chris at 1/7/2014 2:54 PM

Thanks Andrew, you also (if using this) need to add:

open Neo4jClient.Cypher

as the ICypherResultItem is defined there.
I'm not sure it's an improvement on the legibility of the code (at least from a non-F# dev's point of view) but it does work. I've added another Gist (https://gist.github.com/cskardon/8300420) for anyone interested in just getting all the code.
Gravatar # re: Using Neo4j with F# – Cypher 2.0
by Maciej J. Bańkowski at 5/24/2014 12:04 AM

F# is fine, it is a classic method overload inference issue.

There is no need to use the RuntimeHelpers, just add a 'Cypher.ICypherResultItem' type annotation to the 'e' object like so and the code will compile and run as expected:

let pAfollowers =
client.Cypher
.Match("n<-[:follows]-e")
.Where(fun n -> n.Twitter = "tA")
.Return(fun (e : Cypher.ICypherResultItem) -> e.As<Person>().Name)
.Results
Gravatar # re: Using Neo4j with F# – Cypher 2.0
by Chris at 5/29/2014 8:04 AM

@Macie - that is a much better way
Post A Comment
Title:
Name:
Email:
Comment:
Verification: