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!
The Relationship class is defined inside the Neo4j framework.
This post looks very promising based on my needs. However, you refer to one or two things that I don’t see defined: a Relationship class, and Relationship. Can you share the definition of these?