Cypher support has now been added to the Neo4jClient
The Neo4jClient also has a built in custom Cypher REST result deserializer, so you do not need to worry about all the serialization/deserialization logic, courtesy to Tatham Oddie.
Below are some sample queries to get you going.
You can look at the Source Code for other code samples in the Test project.
Simple Query from API
return graphClient.RootNode
.StartCypher("root")
.Match("root-[:BELONGS]->(user)")
.Return<SimpleResultDto>("user")
.Results
.OrderBy(u => u.Username);
Simple Query
[Test]
public void WhereBooleanOperations()
{
// http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations
// START n=node(3, 1)
// WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias")
// RETURN n
var client = Substitute.For<IGraphClient>();
var query = new CypherFluentQuery(client)
.Start("n", (NodeReference)3, (NodeReference)1)
.Where<FooNode>(n => (n.Age < 30 && n.Name == "Tobias") || n.Name != "Tobias")
.Return<object>("n")
.Query;
Assert.AreEqual("START n=node({p0}, {p1})\r\nWHERE (((n.Age < {p2}) AND (n.Name = {p3})) OR (n.Name != {p4}))\r\nRETURN n".Replace("'","\""), query.QueryText);
Assert.AreEqual(3, query.QueryParameters["p0"]);
Assert.AreEqual(1, query.QueryParameters["p1"]);
Assert.AreEqual(30, query.QueryParameters["p2"]);
Assert.AreEqual("Tobias", query.QueryParameters["p3"]);
Assert.AreEqual("Tobias", query.QueryParameters["p4"]);
}
Simple Query Column Aliases
[Test]
public void ReturnColumnAlias()
{
// http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias
// START a=node(1)
// RETURN a.Age AS SomethingTotallyDifferent
var client = Substitute.For<IGraphClient>();
var query = new CypherFluentQuery(client)
.Start("a", (NodeReference)1)
.Return(a => new ReturnPropertyQueryResult
{
SomethingTotallyDifferent = a.As<FooNode>().Age
})
.Query;
Assert.AreEqual("START a=node({p0})\r\nRETURN a.Age AS SomethingTotallyDifferent", query.QueryText);
Assert.AreEqual(1, query.QueryParameters["p0"]);
}
return graphClient.RootNode .StartCypher("root") .Match("root-[:BELONGS]->(user)") .Return<SimpleResultDto>("user") .Results .OrderBy(u => u.Username);
Simple Query
[Test]
public void WhereBooleanOperations()
{
// http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations
// START n=node(3, 1)
// WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias")
// RETURN n
var client = Substitute.For<IGraphClient>();
var query = new CypherFluentQuery(client)
.Start("n", (NodeReference)3, (NodeReference)1)
.Where<FooNode>(n => (n.Age < 30 && n.Name == "Tobias") || n.Name != "Tobias")
.Return<object>("n")
.Query;
Assert.AreEqual("START n=node({p0}, {p1})\r\nWHERE (((n.Age < {p2}) AND (n.Name = {p3})) OR (n.Name != {p4}))\r\nRETURN n".Replace("'","\""), query.QueryText);
Assert.AreEqual(3, query.QueryParameters["p0"]);
Assert.AreEqual(1, query.QueryParameters["p1"]);
Assert.AreEqual(30, query.QueryParameters["p2"]);
Assert.AreEqual("Tobias", query.QueryParameters["p3"]);
Assert.AreEqual("Tobias", query.QueryParameters["p4"]);
}
Simple Query Column Aliases
[Test]
public void ReturnColumnAlias()
{
// http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias
// START a=node(1)
// RETURN a.Age AS SomethingTotallyDifferent
var client = Substitute.For<IGraphClient>();
var query = new CypherFluentQuery(client)
.Start("a", (NodeReference)1)
.Return(a => new ReturnPropertyQueryResult
{
SomethingTotallyDifferent = a.As<FooNode>().Age
})
.Query;
Assert.AreEqual("START a=node({p0})\r\nRETURN a.Age AS SomethingTotallyDifferent", query.QueryText);
Assert.AreEqual(1, query.QueryParameters["p0"]);
}
[Test] public void WhereBooleanOperations() { // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations // START n=node(3, 1) // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") // RETURN n var client = Substitute.For<IGraphClient>(); var query = new CypherFluentQuery(client) .Start("n", (NodeReference)3, (NodeReference)1) .Where<FooNode>(n => (n.Age < 30 && n.Name == "Tobias") || n.Name != "Tobias") .Return<object>("n") .Query; Assert.AreEqual("START n=node({p0}, {p1})\r\nWHERE (((n.Age < {p2}) AND (n.Name = {p3})) OR (n.Name != {p4}))\r\nRETURN n".Replace("'","\""), query.QueryText); Assert.AreEqual(3, query.QueryParameters["p0"]); Assert.AreEqual(1, query.QueryParameters["p1"]); Assert.AreEqual(30, query.QueryParameters["p2"]); Assert.AreEqual("Tobias", query.QueryParameters["p3"]); Assert.AreEqual("Tobias", query.QueryParameters["p4"]); }
Simple Query Column Aliases
[Test] public void ReturnColumnAlias() { // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias // START a=node(1) // RETURN a.Age AS SomethingTotallyDifferent var client = Substitute.For<IGraphClient>(); var query = new CypherFluentQuery(client) .Start("a", (NodeReference)1) .Return(a => new ReturnPropertyQueryResult { SomethingTotallyDifferent = a.As<FooNode>().Age }) .Query; Assert.AreEqual("START a=node({p0})\r\nRETURN a.Age AS SomethingTotallyDifferent", query.QueryText); Assert.AreEqual(1, query.QueryParameters["p0"]); }
Below is deserialization code, note this is Internal to the client and you would never explicitly call it directly, you would always use one of the Return overloads instead. I have put it here for those coders interested in deserialzing Cypher Rest results from Neo4j using expressions.
Deserialization Test Sample
[Test] public void ShouldDeserializeTableStructureWithRelationships() { // Arrange const string queryText = @" START x = node({p0}) MATCH x-[r]->n RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId LIMIT 3"; var query = new CypherQuery( queryText, new Dictionary<string, object> { {"p0", 123} }); var httpFactory = MockHttpFactory.Generate("http://foo/db/data", new Dictionary<RestRequest, HttpResponse> { { new RestRequest { Resource = "/", Method = Method.GET }, new HttpResponse { StatusCode = HttpStatusCode.OK, ContentType = "application/json", Content = @"{ 'cypher' : 'http://foo/db/data/cypher', 'batch' : 'http://foo/db/data/batch', 'node' : 'http://foo/db/data/node', 'node_index' : 'http://foo/db/data/index/node', 'relationship_index' : 'http://foo/db/data/index/relationship', 'reference_node' : 'http://foo/db/data/node/0', 'extensions_info' : 'http://foo/db/data/ext', 'extensions' : { 'GremlinPlugin' : { 'execute_script' : 'http://foo/db/data/ext/GremlinPlugin/graphdb/execute_script' } } }".Replace('\'', '"') } }, { new RestRequest { Resource = "/cypher", Method = Method.POST, RequestFormat = DataFormat.Json }.AddBody(new CypherApiQuery(query)), new HttpResponse { StatusCode = HttpStatusCode.OK, ContentType = "application/json", Content = @"{ 'data' : [ [ { 'start' : 'http://foo/db/data/node/0', 'data' : { 'Bar' : 'bar', 'Baz' : 'baz' }, 'property' : 'http://foo/db/data/relationship/0/properties/{key}', 'self' : 'http://foo/db/data/relationship/0', 'properties' : 'http://foo/db/data/relationship/0/properties', 'type' : 'HAS_REFERENCE_DATA', 'extensions' : { }, 'end' : 'http://foo/db/data/node/1' }, 'HOSTS', 'foo', 44321 ], [ { 'start' : 'http://foo/db/data/node/1', 'data' : { 'Bar' : 'bar', 'Baz' : 'baz' }, 'property' : 'http://foo/db/data/relationship/1/properties/{key}', 'self' : 'http://foo/db/data/relationship/1', 'properties' : 'http://foo/db/data/relationship/1/properties', 'type' : 'HAS_REFERENCE_DATA', 'extensions' : { }, 'end' : 'http://foo/db/data/node/1' }, 'LIKES', 'bar', 44311 ], [ { 'start' : 'http://foo/db/data/node/2', 'data' : { 'Bar' : 'bar', 'Baz' : 'baz' }, 'property' : 'http://foo/db/data/relationship/2/properties/{key}', 'self' : 'http://foo/db/data/relationship/2', 'properties' : 'http://foo/db/data/relationship/2/properties', 'type' : 'HAS_REFERENCE_DATA', 'extensions' : { }, 'end' : 'http://foo/db/data/node/1' }, 'HOSTS', 'baz', 42586 ] ], 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] }".Replace('\'', '"') } } }); var graphClient = new GraphClient(new Uri("http://foo/db/data"), httpFactory); graphClient.Connect(); //Act var results = graphClient.ExecuteGetCypherResults<ResultWithRelationshipDto>(query); //Assert Assert.IsInstanceOf<IEnumerable<ResultWithRelationshipDto>>(results); var resultsArray = results.ToArray(); Assert.AreEqual(3, resultsArray.Count()); var firstResult = resultsArray[0]; Assert.AreEqual(0, firstResult.Fooness.Reference.Id); Assert.AreEqual("bar", firstResult.Fooness.Data.Bar); Assert.AreEqual("baz", firstResult.Fooness.Data.Baz); Assert.AreEqual("HOSTS", firstResult.RelationshipType); Assert.AreEqual("foo", firstResult.Name); Assert.AreEqual(44321, firstResult.UniqueId); var secondResult = resultsArray[1]; Assert.AreEqual(1, secondResult.Fooness.Reference.Id); Assert.AreEqual("bar", secondResult.Fooness.Data.Bar); Assert.AreEqual("baz", secondResult.Fooness.Data.Baz); Assert.AreEqual("LIKES", secondResult.RelationshipType); Assert.AreEqual("bar", secondResult.Name); Assert.AreEqual(44311, secondResult.UniqueId); var thirdResult = resultsArray[2]; Assert.AreEqual(2, thirdResult.Fooness.Reference.Id); Assert.AreEqual("bar", thirdResult.Fooness.Data.Bar); Assert.AreEqual("baz", thirdResult.Fooness.Data.Baz); Assert.AreEqual("HOSTS", thirdResult.RelationshipType); Assert.AreEqual("baz", thirdResult.Name); Assert.AreEqual(42586, thirdResult.UniqueId); } }
Conclusion
The Neo4jClient now supports both Gremlin and Cypher query language in one logical graphClient, this should prove to be sufficient for all graph client query needs and CRUD operations, we now get the best of both worlds. You have intrinsic Neo4j CRUD + Gremlin + Cypher.
We find a balance where Gremlin is used for simple Graph Traversals and Cypher is used as our reporting tool.