Playing with graphs and neo4j

After my initial implementation of some BrainExpanded-related ideas on top of dgraph using its GraphQL interface, I realized that the GraphQL data model was a bit restrictive for what I needed. It’s a long story so I won’t bore you.

So, I started thinking of Neo4j. Jim pinged me and said something along the lines of “how dare you not use Neo4j?” Well… he didn’t really say that but I know that’s what he meant 🙂

The challenge – mapping between data models

The first thing I started was to change the implementation of my Web API (implemented in .NET) to interact with Neo4j. I very quickly realized that having to map my domain data model to/from Neo4j’s data model was going to be a pain, whether I used the Neo4j’s .NET driver or the Neo4j Web API. Why worrying about serializing/deserializing objects when middleware can do that automatically? Granted, I may not be able to do absolutely everything that Neo4j has to offer but the middleware should be able to handle most things for me such serialization/deserialization, mapping between .NET and Neo4j data types, representation of nodes and relationships, simple graph queries, navigational queries with projects/filtering, graph traversals, etc.

So, with the help of Github’s copilot and its various models, I built the “Graph Model” abstraction and an implementation of it for Neo4j.

Welcome to the Graph Model

This project is just a tool for what I am doing with my main project so I decided to open source the code in case others are interested. There is a ton more to be done and a lot of cleaning up to do. The coding models tend to write a lot of code which isn’t necessarily beautiful and well-organized. I collaborated with the models by writing tests. I wrote the Graph.Model interfaces, most of the Neo4jGraphProvider class, and most of the tests. The coding AIs wrote most of the LINQ expression → Cypher logic.

What is the Graph Model?

GraphModel is a .NET library that provides a clean abstraction over graph databases, particularly Neo4j. It offers a set of interfaces and tools to model, query, and manage graph data effectively. Think of it as your trusty compass in the vast forest of nodes and relationships.

🚀 Getting Started

Ready to dive in? Here’s how you can get started.

1. Install the NuGet Package:

> dotnet new console
> dotnet add package Cvoya.Graph.Model
> dotnet add package Cvoya.Graph.Provider.Neo4j

2. Define your application domain entities

using Cvoya.Graph.Model;

public class Person : Node
{
    public string Name { get; set; }
}

public class Knows : Relationship
{
    public DateTime Since { get; set; }
}

3. Initialize a provider

Providers implement the Cvoya.Graph.Model.IGraph interface. The Cvoya.Graph.Provider.Neo4j is such a provider.

var provider = new Neo4jGraphProvider("bolt://localhost:7687", "neo4j", "password");

4. Ready to partyyyyy

var alice = new Person { Name = "Alice" };
var bob = new Person { Name = "Bob" };
var knows = new Knows { Since = DateTime.Now };

context.CreateRelationship(alice, knows, bob);

And voilà! You’ve just established a relationship between Alice and Bob.

🔍 Querying the Graph

GraphModel requires providers to implement LINQ queries. The Neo4j provider offers extensive support for queries over the graph database. Check out the tests in the graphmodel repo to get ideas.

var friendsOfAlice = context.Match<Person>()
    .Where(p => p.Name == "Alice")
    .Traverse<Knows>()
    .To<Person>();

🔄 Transactions

Need to perform multiple operations atomically? GraphModel has you covered:

using var tx = context.BeginTransaction())
var charlie = new Person { Name = "Charlie" };
context.CreateNode(charlie);
tx.Commit();

🧩 Attributes

Customize your graph entities using attributes:

[Node(Label = "User")]
public class Person : Node
{
    [Property(Name = "full_name")]
    public string Name { get; set; }
}

var savas = new Person { Id = "savas", Name = "Savas Parastatidis" }
await provider.CreateNode(savas)

This maps the Person class to a User node in Neo4j, with the Name property stored as full_name. It is now possible to retrieve the same node using a completely different class. In other words, there is no tight-coupling between the Person class and the Neo4j representation’s of the graph node.

[Node(Label = "User")]
public class SomeOtherPersonClass : Node
{
    [Property(Name = "full_name")]
    public string FullName { get; set; }
}

var greekDude = await provider.GetNode<SomeOtherPersonClass>("savas")

There is so much more but this entry is a good start.

Feedback

I am sure there are many issues with the project. Please feel free to send me feedback via email or submit an issue. Pull requests are also very much welcomed 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *