Geeks With Blogs

The Lanham Factor The (ir)rational thoughts of a (not-so)mad man

Recently I started working with Neo4j in C#.  I am using the Neo4jClient and decided to write a wrapper for it using Generics.  In this blog post I'll show how I did it.  As always, your feedback is appreciated.

The first is the CRUD operations provider.  CRUD stands for Create, Retrieve, Update, Delete in case you don't know.  The CRUD provider is quite simple. 

But before we jump into that, we need to make some concessions.  We must have a common base type with a common property in order to find the node on successive calls.  So I created a base class with a single property, “Id”, as an int.  You can choose any property or properties you like but for simplicity I chose a small footprint.  Here is my base class:

public abstract class Neo4jBase
{     
	public int Id { get; set; }
}

(I don’t know why the “I” is uppercase.  I guess I got a little keyboard crazy.)

With that in place, we can build our CRUD data provider.  Remember, you must be using the Neo4jClient library.  Here is my generic provider:

using Neo4jClient;
public class Neo4jDataProvider<T> where T : Neo4jBase
{     
	IGraphClient _client = null;     
	public Neo4jDataProvider (IGraphClient client)     
	{         
		_client = client;     
	}     
	public T Create(T record)     
	{         
		if (_client != null)         
		{             
			var inputString = string.Format("({0}:{1}", typeof(T).Name.ToLower(), typeof(T).Name);             
			inputString += " { newRecord })";             
			_client.Cypher                 
				.Create(inputString)                 
				.WithParam("newRecord", record)                 
				.ExecuteWithoutResults();         
		}         
		return record;     
	}     
	public T Retrieve(int id)     
	{         
		T result = null;         
		if (_client != null)         
		{             
			var inputString = string.Format("({0}:{1})", "record", typeof(T).Name);             
			var query = _client.Cypher                         
					.Match(inputString)                         
					.Where((T record) => record.Id == id)                         
					.Return(record => record.As<T>());             
			result = query.Results.FirstOrDefault();         
		}         
		return result;     
	}     
	public T Update(T entry)     
	{         
		if (_client != null)         
		{             
			var inputString = string.Format("({0}:{1})", "record", typeof(T).Name);             
			_client.Cypher                     
				.Match(inputString)                     
				.Where((T record) => record.Id == entry.Id)                     
				.Set("record = {updatedRecord}")                     
				.WithParam("updatedRecord", entry)                     
				.ExecuteWithoutResults();         
		}                     
		return entry;     
	}     
	public void Delete(int id)     
	{         
		if (_client != null)         
		{             
			var inputString = string.Format("({0}:{1})", "record", typeof(T).Name);             
			_client.Cypher                     
				.Match(inputString)                     
				.Where((T record) => record.Id == id)                     
				.Delete("record")                     
				.ExecuteWithoutResults();         
		}     
	}
}

So what’s going here?  First it is a generic class of type T where T is a Neo4jBase object.  The reason is the Id property.  That property allows us to find and deal with nodes individually in an easy and familiar fashion.  Notice that the constructor takes an IGraphClient object.  It is expected the IGraphClient is instantiated and connected to a database.

The next point to note is the use of the type of T as the label.  For those of you new to graph databases, the “label” is the “type” of the node in the database.  For example, labels may be “Person”, “Movie”, “Book”, etc.  So in each method, the name of the type of T is used as the label.  You will also see the word “record” scattered throughout the code.  This is used as a variable name. 

An interesting behavior is that the .WHERE methods seem to break if the variable name in the .MATCH methods is not the same name as the name of the variable in the lambda expression.

What about Relationships?

Remember that relationships are first-class citizens in graph databases, just like nodes.  However, since relationships involve nodes of various types I decided to create a separate class for handling them.  The class is still a generic class but instead uses two different types, TLEFT and TRIGHT. 

A caveat: 1) The relationships data provider only handles unidirectional relationships (always left-to-right);  Here is the relationship data provider:

using Neo4jClient;
public class Neo4jDataProviderRelationships<TLeft, TRight>                                              where TLeft : Neo4jBase                                              where TRight : Neo4jBase
{     
	IGraphClient _client = null;     
	public Neo4jDataProviderRelationships(IGraphClient client)     
	{         
		_client = client;     
	}     
	public void Associate(TLeft left, string relationshipName, TRight right)     
	{         
		if (_client != null)         
		{             
			var inputStringLeft = string.Format("({0}:{1})", "tleft", typeof(TLeft).Name);             
			var inputStringRight = string.Format("({0}:{1})", "tright", typeof(TRight).Name);             
			_client.Cypher                 
				.Match(inputStringLeft, inputStringRight)                 
				.Where((TLeft tleft) => tleft.Id == left.Id)                 
				.AndWhere((TRight tright) => tright.Id == right.Id)                 
				.Create("tleft-[:" + relationshipName + "]->tright")                 
				.ExecuteWithoutResults();         
		}     
	}     
	public void Dissociate(TLeft left, string relationshipName, TRight right)     
	{         
		if (_client != null)         
		{             
			var inputStringLeft = string.Format("({0}:{1})", "tleft", typeof(TLeft).Name);             
			var inputStringRight = string.Format("({0}:{1})", "tright", typeof(TRight).Name);             
			_client.Cypher                 
				.Match(inputStringLeft + "-[:" + relationshipName + "]->" + inputStringRight)                 
				.Where((TLeft tleft) => tleft.Id == left.Id)                 
				.AndWhere((TRight tright) => tright.Id == right.Id)                 
				.Delete(relationshipName)                 
				.ExecuteWithoutResults();         
		}     
	}
}

Notice that like the CRUD data provider, an IGraphClient is expected by the constructor.  There are two methods for associating and dissociating nodes.

Usage

So how are these classes used?  Well, suppose you have a class, Person, and a class, Movie as follows (borrowed from the demo database that ships with Neo4j):

public class Movie : Neo4jBase
{     
	public string title { get; set; }     
	public string released { get; set; }     
	public string tagline { get; set; }

}
 
public class Person : Neo4jBase
{     
	public string name { get; set; }     
	public string born { get; set; }

}

Now suppose you want to create a Person and a Movie and relate them.  Specifically, let’s create William Shatner, and Airplane II, and relate them.  First, create the nodes:

Neo4jDataProvider<Person> personDataProvider = new Neo4jDataProvider<Person>(client);
Person person = new Person { Id = 1, born = "1966", name = "William Shatner" };
personDataProvider.Create(person);
 
Neo4jDataProvider<Movie> movieDataProvider = new Neo4jDataProvider<Movie>(client);
var movie = new Movie { Id = 1, released = "1982", tagline = "The Sequel", title = "Airplane II" };
movieDataProvider.Create(movie);

That will create the two nodes.  Let’s relate them:

Neo4jDataProviderRelationships<Person, Movie> relDataProvider = new Neo4jDataProviderRelationships<Person, Movie>(client);
relDataProvider.Associate(person, "ACTED_IN", movie);

And that’s it!

What’s the catch?

There are a number of catches including:

  • You must manage the “Id” values yourself.
  • You can’t easily retrieve a node based on any other property.
  • You can’t retrieve relationships. Even though they are fist class citizens, I only created CRUD operations wrappers.
  • The “Create” method doesn’t check for duplicates.  The “Update” method allows changing the “Id” field.

I’m sure there are more catches.  As an apology, I am just getting started.  I hope to extend these classes with more robust operations.  Your feedback is appreciated.  If someone has already done this (or something similar) I’d love to know about it. 

Posted on Sunday, January 5, 2014 4:58 PM | Back to top


Comments on this post: Generic C# Data Providers for Neo4j

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Really cool,

would love to see if this made it into a small library after addressing the catches.

The "retrieve by property" should be not to hard with inspection and using indexes to speed it up.

Duplicate checking should be done by unique constraints.

Looking forward to your next steps, please keep us posted.
Left by Michael Hunger on Jan 05, 2014 5:46 PM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Thanks for the feedback Michael! I appreciate you taking the time to review and offering suggestions!
Left by Brian Lanham on Jan 05, 2014 8:04 PM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Brian,

This looks pretty cool. I'm just now learning about Node4j and graph DBs, but hope to continue the exploration in the future. Quick question: in Node4jDataProvider's Create method, you use typeof(T).Name.toLower() as the 'property' (?) of the inputString, but elsewhere you hardcode 'record'. Why the difference?

Thanks,
Mike
Left by Mike Haggerty on Jan 08, 2014 10:32 PM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Mike,

Thanks for the feedback. The reason for "record" hardcoded is that the Neo4jClient seems to cast the lambda expression to a string and looks for the property name to be the same as the lambda expression variable name. I ran into a bizarre error and when I did this the error disappeared. Hope this makes sense.

Brian
Left by Brian C. Lanham on Jan 09, 2014 10:30 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
For Deleting a node with relationship
============================================

public void DeleteWithRelationShip(int id)
{

if (_client != null)
{

var inputString = string.Format("({0}:{1})", "record", typeof(T).Name) + "-[r]-()";

_client.Cypher

.Match(inputString)

.Where((T record) => record.Id == id)

.Delete("record,r")

.ExecuteWithoutResults();


}

}
Left by Soumendra Hati on Jan 13, 2014 2:16 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Soumendra,

Excellent addition! Thank you for sharing!

Brian
Left by Brian C. Lanham on Jan 13, 2014 11:07 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hi ,this is excellent work.
I would just this code :
====================================================================================

public Concept Retrieve(int id)
{
var client = new GraphClient(new Uri("http://localhost:7474/db/data"));
client.Connect();
Concept result = null;

try{
if (client != null)
{
var inputString = string.Format("({0}:{1})","record", "Concept");
var query = client.Cypher
.Match(inputString)
.Where((Concept record) => record.Id_Con == id)
.Return(record => record.As<Concept>());
result = query.Results.FirstOrDefault();
}
}
catch (Exception e) { Console.WriteLine("IOException Message: {0}", e.Message); }

return result;
====================================================================================
but Visual studio give me this error :"You've called As<Concept>() in your return clause, where Concept is not a supported type.
It must be a simple type (like int, string, or long), a class with a default constructor (so that we can deserialize into it),
RelationshipInstance, RelationshipInstance<T>, list of RelationshipInstance, or list of RelationshipInstance<T>.\r\nNom du paramètre :
expression"}
Left by mro on Apr 15, 2014 9:05 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
what should do to Create a node, only if they don't already exist ??
Left by Denguiri M on Apr 16, 2014 7:40 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
mro,

Do you have a class "Concept" with a default, parameterless, public constructor? That's what you need it seems.
Left by Brian Lanham on Apr 18, 2014 7:38 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Denguiri M,

You should be able to use the Create<T> method to create it. I don't understand your question.
Left by Brian Lanham on Apr 18, 2014 7:39 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
HI Brian Lanham,
I want to create a graph using the neo4jclient , .I explain my problem , I have a dictionary that hold 2 kinds of object ( documents , words)

Dictionary<Document, List<Concept>>

as you can see ,each document hold a list of concept ,a relationship must be between them
(doc)<-[:IN]-(con)

and i want to create a function that create nodes without redundancy . I try to make it using (cypher.. and.foreach. Merge.) but i ran into a bizarre error ,because i didn't understand well how it work . would you please explain to me what should i do to resolve it. Thanks
Left by Denguiri M on Apr 18, 2014 10:17 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hello,

First of all, thanks for this excelent work. I think it is a starting point for something that can be very useful. Have you thought of doing a github ou nuget?

Thanks
Left by David g. on May 06, 2014 6:31 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
David g.,

Thanks for the suggestion. I had not but now I will definitely do that! I think both are good ideas. Thank you!

Brian
Left by Brian Lanham on May 18, 2014 12:51 PM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hi All,

can you help me ... Visual studio gives me the run-time error for the below line of code :

var client = new GraphClient(new Uri("http://localhost:7474/db/data"));
client.Connect();
I have exactly create the class but while creating the Node (person ) I have added the above line for Connection but it seem to be not working . Can any one help to solve my issue . Thanking you in advance ...
Left by Samrin Shaikh on Aug 13, 2014 3:57 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Is Neo4j running? Make sure it is running.
Left by Brian Lanham on Aug 13, 2014 1:33 PM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hello ,
I am trying to used the Retrieve(int id)
the function is giving compile time error on below line of code :

.Return(record => record.As <T); (error Message: 'T' is a 'type parameter' but is used like a 'variable')
result= query.Results.FirstOrDefault();(error Message : Cannot implicitly convert type 'bool' to 'T')

Can any1 help to resolve the exception


Regards
Samrin Shaikh
Left by Samrin Shaikh on Aug 26, 2014 7:59 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hello,

Can any1 provide me the Usage of delete function & also Can any 1 help me to create a function that will delete the node as well the relation ship


Samrin Shaikh
Left by Samrin Shaikh on Aug 26, 2014 8:52 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Samrin,

There is a missing ">" character in my code sample. You need to add it right after the T in the .Return line.

Regarding delete - using the API I provide you will have to make two separate calls. Is that what you mean?
Left by Brian Lanham on Aug 26, 2014 11:19 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hi ,

I try adding the missing ">" character but still showing error .

Regarding Delete I mean i have node A which have 3 Child nodes (3 nodes B,C,D belong to A) belong to is the relation among A- B , A-C, A-D . if i delete A then i want to delete automatically the relation between A-B, A-C & A-D ..

Left by Samrin Shaikh on Aug 27, 2014 3:10 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
how connect neo4j version 2.1.4 to asp.net MVC4?
Left by neeta on Sep 22, 2014 2:07 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hi Samrin Shaikh

I try .Return(record => record.As<T>());


Hope that helps!
Left by Diego Villafanes on Aug 13, 2016 1:58 AM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
Hi Samrin Shaikh

I try .Return(record => record.As<T>());


Hope that helps!
Left by Diego Villafanes on Aug 19, 2016 1:53 PM

# re: Generic C# Data Providers for Neo4j
Requesting Gravatar...
I don't know why this site broke the code "record.As<T>() "
Left by Diego Villafanes on Aug 19, 2016 1:56 PM

Your comment:
 (will show your gravatar)


Copyright © Brian Lanham | Powered by: GeeksWithBlogs.net