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:

  1. HotSpotController
  2. RefugeeController

Happy Hacking!