Here I am, rock you like a hurricane!
The Spring framework from the Java “universe” currently supports efficiently the Neo4j graph database. However, the Spring.NET framework from the C# “universe” does not support Neo4j and generally a decent code infrastructure library for Neo4j is missing from the .NET open source world at the moment.
A starting code infrastructure could be a Generic Repository for supporting CRUD operations, a Relationship Manager for managing relationships between nodes and a Unit Of Work for supporting transactions. In a recent project, I have coded a prototype code infrastructure on my own since I couldn’t find a simple, well-written and unit-testable example. This is the reason why I would like to give it to you people so that you can benefit from it.
Let’s start present some code…
All your domain/entity Neo4j models should inherit from the BaseEntity abstract class:
public abstract class BaseEntity : IEntity
{
#region Constructors
protected BaseEntity()
{
Id = Guid.NewGuid();
}
#endregion
#region Public Properties
public Guid Id { get; protected set; }
#endregion
}
The BaseEntity is a simple implementation of the IEntity interface:
public interface IEntity
{
Guid Id { get; }
}
Next, I present to you the IGenericRepository interface for the CRUD operations:
public interface IGenericRepository<TModel> where TModel : IEntity
{
IList<TModel> GetAll();
TModel GetById(Guid id);
TModel Save(TModel model);
TModel Update(TModel model);
void Delete(TModel model);
}
Follows, the INeo4jRepository interface that inherits the IGenericRepository interface:
public interface
INeo4jRepository<TModel> : IGenericRepository<TModel> where TModel : IEntity
{
void SetGraphClient(IGraphClient graphClient);
}
Here, is the Neo4jRepository class which is the implementation of INeo4jRepository:
public class
Neo4jRepository<TModel> : INeo4jRepository<TModel> where TModel : IEntity
{
#region IGenericRepository<TModel> Implementation
public IList<TModel> GetAll()
{
return GraphClient.Cypher.Match($"(e:{typeof(TModel).Name})")
.Return(e => e.As<TModel>())
.Results
.ToList();
}
public TModel GetById(Guid id)
{
return GraphClient.Cypher.Match($"(e:{typeof(TModel).Name})")
.Where<IEntity>(e => e.Id == id)
.Return(e => e.As<TModel>())
.Results
.SingleOrDefault();
}
public TModel Save(TModel model)
{
return GraphClient.Cypher.Create($"(e:{typeof(TModel).Name} {{model}})")
.WithParam("model", model)
.Return(e => e.As<TModel>())
.Results
.Single();
}
public TModel Update(TModel model)
{
Guid id = model.Id;
return GraphClient.Cypher.Match($"(e:{typeof(TModel).Name})")
.Where<IEntity>(e => e.Id == id)
.Set("e = {model}")
.WithParam("model", model)
.Return(e => e.As<TModel>())
.Results
.Single();
}
public void Delete(TModel model)
{
Guid id = model.Id;
GraphClient.Cypher.Match($"(e:{typeof(TModel).Name})")
.Where<IEntity>(e => e.Id == id)
.DetachDelete("e")
.ExecuteWithoutResults();
}
#endregion
#region INeo4jRepository<TModel> Implementation
public void SetGraphClient(IGraphClient graphClient)
{
Ensure.That(nameof(graphClient)).IsNotNull();
_graphClient = graphClient;
}
#endregion
#region Protected Properties
protected IGraphClient GraphClient
{
get
{
if (_graphClient == null)
{
throw new ApplicationException("Initialize the graph client!");
}
return _graphClient;
}
}
#endregion
#region Private Fields
private IGraphClient _graphClient;
#endregion
}
Now, follows the IGenericUnitOfWork interface for transactions support:
public interface IGenericUnitOfWork : IDisposable
{
void BeginTransaction();
void CommitTransaction();
void RollbackTransaction();
}
Next, the Neo4jUnitOfWork which is a IGenericUnitOfWork implementation:
public class Neo4jUnitOfWork : IGenericUnitOfWork
{
#region Constructors
protected Neo4jUnitOfWork(GraphClient graphClient)
{
Ensure.That(nameof(graphClient)).IsNotNull();
_graphClient = graphClient;
}
#endregion
#region Interface Implementation
public void Dispose()
{
if (_graphClient.InTransaction)
{
_graphClient.Transaction.Dispose();
}
_graphClient.Dispose();
}
public void BeginTransaction()
{
if (!_graphClient.IsConnected)
{
throw new ApplicationException("Not connected!");
}
if (_graphClient.InTransaction)
{
throw new ApplicationException("Already running transaction!");
}
_graphClient.BeginTransaction();
}
public void CommitTransaction()
{
if (!_graphClient.IsConnected)
{
throw new ApplicationException("Not connected!");
}
if (!_graphClient.InTransaction)
{
throw new ApplicationException("No transaction exists!");
}
if (!_graphClient.Transaction.IsOpen)
{
throw new ApplicationException("Transaction is not open!");
}
_graphClient.Transaction.Commit();
}
public void RollbackTransaction()
{
if (!_graphClient.IsConnected)
{
throw new ApplicationException("Not connected!");
}
if (!_graphClient.InTransaction)
{
throw new ApplicationException("No transaction exists!");
}
if (!_graphClient.Transaction.IsOpen)
{
throw new ApplicationException("Transaction is not open!");
}
_graphClient.Transaction.Rollback();
}
#endregion
#region Private Readonly Fields
private readonly GraphClient _graphClient;
#endregion
}
Be patient. We have some more classes in our road.
Now, follows the IUnitOfWorkFactory interface:
public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}
Now, the UnitOfWorkFactory which is a simple implementation of IUnitOfWorkFactory:
public class UnitOfWorkFactory : IUnitOfWorkFactory
{
#region Constructors
public UnitOfWorkFactory(IGraphClientFactory graphClientFactory)
{
Ensure.That(nameof(graphClientFactory)).IsNotNull();
_graphClientFactory = graphClientFactory;
}
#endregion
#region Interface Implementation
public IUnitOfWork Create()
{
GraphClient graphClient = (GraphClient)_graphClientFactory.Create();
return new UnitOfWork(graphClient);
}
#endregion
#region Private Readonly Fields
private readonly IGraphClientFactory _graphClientFactory;
#endregion
}
Next, follows the IUnitOfWork which is a domain example of IGenericUnitOfWork:
public interface IUnitOfWork : IGenericUnitOfWork
{
IRefugeeRepository RefugeeRepository { get; }
IHotSpotRepository HotSpotRepository { get; }
IHotSpotRelationshipManager HotSpotRelationshipManager { get; }
IRefugeeRelationshipManager RefugeeRelationshipManager { get; }
}
Also, here is the UnitOfWork which is the implementation of IUnitOfWork:
public class UnitOfWork : Neo4jUnitOfWork, IUnitOfWork
{
#region Constructors
public UnitOfWork(GraphClient graphClient) : base(graphClient)
{
Ensure.That(nameof(graphClient)).IsNotNull();
#region Repositories
#region Refugee
var refugeeRepository = new RefugeeRepository();
refugeeRepository.SetGraphClient(graphClient);
RefugeeRepository = refugeeRepository;
#endregion
#region HotSpot
var hotSpotRepository = new HotSpotRepository();
hotSpotRepository.SetGraphClient(graphClient);
HotSpotRepository = hotSpotRepository;
#endregion
#endregion
#region Relationship Managers
#region HotSpot
var hotSpotRelationshipManager = new HotSpotRelationshipManager();
hotSpotRelationshipManager.SetGraphClient(graphClient);
HotSpotRelationshipManager = hotSpotRelationshipManager;
#endregion
#region Refugee
var refugeeRelationshipManager = new RefugeeRelationshipManager();
refugeeRelationshipManager.SetGraphClient(graphClient);
RefugeeRelationshipManager = refugeeRelationshipManager;
#endregion
#endregion
}
#endregion
#region Public Properties
#region Repositories
public IRefugeeRepository RefugeeRepository { get; protected set; }
public IHotSpotRepository HotSpotRepository { get; protected set; }
#endregion
#region Relationship Managers
public IHotSpotRelationshipManager HotSpotRelationshipManager { get; set; }
public IRefugeeRelationshipManager RefugeeRelationshipManager { get; set; }
#endregion
#endregion
}
Now, I present to you the IRelationshipManager interface for managing the node relationships:
public interface IRelationshipManager
{
void SetGraphClient(IGraphClient graphClient);
}
I said to you to be patient, but you are not 😦
We continue with the RelationshipManager which is the implementation of IRelationshipManager:
public abstract class RelationshipManager : IRelationshipManager
{
#region Interface Implementation
public void SetGraphClient(IGraphClient graphClient)
{
Ensure.That(nameof(graphClient)).IsNotNull();
_graphClient = graphClient;
}
#endregion
#region Protected Properties
protected IGraphClient GraphClient
{
get
{
if (_graphClient == null)
{
throw new ApplicationException("Initialize the graph client!");
}
return _graphClient;
}
}
#endregion
#region Private Fields
private IGraphClient _graphClient;
#endregion
}
Have a 5 minutes break.
Now, here are some domain examples for IGenericRepository:
public interface IHotSpotRepository : IGenericRepository<HotSpot>
{
HotSpot GetByRefugee(Refugee refugee);
}
public interface IRefugeeRepository : IGenericRepository<Refugee>
{
IList<RefugeeWithHotSpot> GetRefugeesWithHotSpots();
IList<RefugeeWithHotSpot> GetRefugeesWithNoFamilyAndWithHotSpots();
}
Now, follow the related implementations:
public class RefugeeRepository : Neo4jRepository<Refugee>, IRefugeeRepository
{
#region Interface Implementation
public IList<RefugeeWithHotSpot> GetRefugeesWithHotSpots()
{
click here for the implementation
}
public IList<RefugeeWithHotSpot> GetRefugeesWithNoFamilyAndWithHotSpots()
{
click here for the implementation
}
#endregion
}
public class HotSpotRepository : Neo4jRepository<HotSpot>, IHotSpotRepository
{
#region Interface Implementation
public HotSpot GetByRefugee(Refugee refugee)
{
click here for the implementation
}
#endregion
}
Now, follow some domain examples for RelationshipManager:
public class RefugeeRelationshipManager : RelationshipManager,
IRefugeeRelationshipManager
{
#region Interface Implementation
public void Relate(Refugee source,
Refugee target,
IsFamilyRelationshipData isFamilyRelationshipData)
{
click here for the implementation
}
public void UnRelate(Refugee refugeeSource, Refugee refugeeTarget)
{
click here for the implementation
}
#endregion
}
public class HotSpotRelationshipManager : RelationshipManager,
IHotSpotRelationshipManager
{
#region Interface Implementation
public void Relate(Refugee refugee, HotSpot hotSpot)
{
click here for the implementation
}
public void UnRelate(Refugee refugee)
{
click here for the implementation
}
#endregion
}
Next, are the related interfaces:
public interface IHotSpotRelationshipManager
{
void Relate(Refugee refugee, HotSpot hotSpot);
void UnRelate(Refugee refugee);
}
public interface IRefugeeRelationshipManager
{
void Relate(Refugee source,
Refugee target,
IsFamilyRelationshipData isFamilyRelationshipData);
void UnRelate(Refugee refugeeSource, Refugee refugeeTarget);
}
I use Unity as my IoC and I have the following dependency injection registrations:
_container.RegisterType<NeoServerConfiguration>(
new ContainerControlledLifetimeManager(),
new InjectionFactory(o => NeoServerConfiguration.GetConfiguration(
new Uri(Settings.Default.Neo4jServerUrl),
Settings.Default.Neo4jUserName,
Settings.Default.Neo4jPassword)));
_container.RegisterType<IGraphClientFactory, GraphClientFactory>(
new ContainerControlledLifetimeManager());
_container.RegisterType<IUnitOfWorkFactory, UnitOfWorkFactory>(
new ContainerControlledLifetimeManager());
Now, the settings from the InjectionFactory (you should change these to your needs):
Neo4jUserName = neo4j Neo4jPassword = neo4j Neo4jServerUrl = http://localhost:7474/db/data
Here, are some domain/entity Neo4j models:
public class HotSpot : BaseEntity
{
#region Public Properties
public string Name { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
#endregion
}
public class Refugee : BaseEntity
{
#region Public Properties
public string Name { get; set; }
public string Nationality { get; set; }
public GenderType GenderType { get; set; }
public string Passport { get; set; }
public int BirthYear { get; set; }
#endregion
}
Here, are some relationships to manage with their internal data class:
public class IsFamilyRelationshipData
{
#region Constructors
public IsFamilyRelationshipData()
{
}
public IsFamilyRelationshipData(FamilyRelationshipDegree degree)
{
Degree = degree;
}
#endregion
#region Public Properties
public FamilyRelationshipDegree Degree { get; protected set; }
#endregion
}
public class IsFamilyRelationship : Relationship<IsFamilyRelationshipData>,
IRelationshipAllowingSourceNode<Refugee>,
IRelationshipAllowingTargetNode<Refugee>
{
#region Public Static Readonly Fields
public static readonly string TypeKey = "IS_FAMILY";
#endregion
#region Constructors
public IsFamilyRelationship(NodeReference targetNode,
IsFamilyRelationshipData isFamilyRelationshipData)
: base(targetNode, isFamilyRelationshipData)
{
}
#endregion
#region Public Overriden Properties
public override string RelationshipTypeKey => TypeKey;
#endregion
}
public class LivesInRelationship : Relationship,
IRelationshipAllowingSourceNode<Refugee>,
IRelationshipAllowingTargetNode<HotSpot>
{
#region Public Static Readonly Fields
public static readonly string TypeKey = "LIVES_IN";
#endregion
#region Constructors
public LivesInRelationship(NodeReference targetNode) : base(targetNode)
{
}
#endregion
#region Public Overriden Properties
public override string RelationshipTypeKey => TypeKey;
#endregion
}
Now, some basic enumerations:
public enum GenderType : byte
{
Male,
Female
}
public enum FamilyRelationshipDegree : byte
{
Parent,
Children,
Cousin,
Grandparent,
Spouse,
Siblings
}
You have reached the end! 🙂
One last thing.
Here, you can find two Web API controllers that actually use all the above code infrastructure:
Happy Hacking!


