Neo4j Json to C# Classes

By Charlotte

Neo4j has a few projects Arrows, Neo4j Data Importer which can export to JSON file, which is handy for passing around. But once you’ve designed your model, you move on to the development side of things.

In the .NET world – this means probably creating a set of classes and then using your preferred client to interact with Neo4j and start doing stuff.

For 1 or 2 Nodes and Relationships, that’s probably ok, for 10-20 pushing it, for 20+ that’s a lot of work.

So today I bring you the Neo4j Jason to C# Classes repository – one of the least catchy names out there, but it does what it says, have a look a the repository to see what it’s doing, and you can compile it yourself (Net 6.0) on Linux / Mac / Windows – or use the binaries (Windows only at the moment :/ in the releases page).

But what is actually generated?

Let’s use the Data Importer with a Jurassic Park dataset (because Dinosaurs πŸ¦•) from Kaggle

I think I could come up with some better names, but it’s good enough for now… I can add the data to my database, and run some queries…

UK Dinosaurs:

Maybe make less Carnivorous dinosaurs in the future?

╒════════════════════════╀═══════════════════╕
β”‚"Diet"                  β”‚"NumberOfDinosaurs"β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════║
β”‚"herbivorous"           β”‚185                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚"carnivorous"           β”‚94                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚"omnivorous"            β”‚27                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚"unknown"               β”‚2                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚"herbivorous/omnivorous"β”‚1                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

But enough fun! We don’t want to give the browser to our Park researchers, we want to create a highly sophisticated user interface that lets them research… but we have to get access to our data which is held in the database.

We could write out each Node / Relationship as a class, in fact, that’s what we’d have to do, and our classes would look something like this:

public class Period 
{
    public static string Labels => "Period";

    public string Period { get; set; }
}

For this model, we’d probably be ok, but what if there was an easier way? Let’s get our JSON file first – on the Data-Importer page – we can select the ellipsis in the top right and then select the ‘Download model’ option (you can get it ‘with data’ if you want – it has no bearing).

Once you have the file you can then run the Neo4jJsonToCSharp executable against it:

.\Neo4jJsonToCSharpClasses.exe --fileIn D:\temp\jurassic-park.json --folderOut D:\temp\generated\ --format dataImporter --ucc false

The arguments are best described (and up to date!) on the GitHub Repository for the project, but in essence, you provide the file, where the generated classes will go and the format (in this case DataImporter, but it will work with Arrows and Cypher Workbench).

You get three files, Nodes.cs, Relationships.cs and RelationshipTypes.cs which can be imported into your projects.

One thing to note in the call is the use of --ucc false – this means the naming of the properties will be the same as in the exported file. The problem with this is that typically – this is not very ‘.net-ty’ i.e. – It’s lower case property naming.

The best solution is to name the properties on the importer/arrows/workbench to be upper-case – else you’ll have to do translation. For simplicity in this post – we’ve gone with the non-style to show what it looks like.

Nodes.cs

You will get a class per node. Each class will have a public const string Labels which will contain the Labels of the node. I’ll show how you might use this in a query further down.

An example of the sort of class we’re generating would be this:

public class SpecimenNode
{
    public const string Labels = "Specimen";
    public string name {get; set;}
    public string length {get; set;}
    public string named_by {get; set;}
    public string taxonomy {get; set;}
    public string link {get; set;}
}

If you want to change / add extra labels, simply modify the call to the Labels const.

Relationships.cs

Not too dissimilar to the Nodes, just with a ‘Types’ const instead.

///<summary>
/// (:<see cref="Specimen"/>)-[:HAS_DIET]->(:<see cref="Diet"/>)
///</summary>
public class HasDietRelationship
{
    public const string Type = "HAS_DIET";
}

For Relationships (only atm) – a comment is generated to provide you with intellisense indicating the direction and start/end node types. If you have multiple relationships with the same Type they will be merged into one class, with all the properties merged as well, and the <summary> tags will reflect all the options.

RelationshipTypes.cs

If your relationships don’t have properties, this can be an easier approach for using relationships in code – simply providing a set of const strings for each relationship.

public static class RelationshipTypes
{
    ///<summary>
    /// (:<see cref="Specimen"/>)-[:HAS_DIET]->(:<see cref="Diet"/>)
    ///</summary>
    public const string HasDiet = "HAS_DIET";
    ...
}

How should I use these?

The main two approaches are to use the official driver, or the community Neo4jClient. In both examples below, we’ll be doing the same thing – getting a list of the names of the Dinosaurs that are Carnivorous in our dataset.

Neo4j.Driver (official)

First we need our query, and we want to try to not ‘hard code’ our strings as much as possible, so I like to make extensive use of string interpolation (or string.Format if that’s what you prefer)

    var query = $@"
MATCH (s:{SpecimenNode.Labels})-[:{HasDietRelationship.Type}]->(d:{DietNode.Labels})
WHERE d.diet = $paramDiet
RETURN s ORDER BY s.{nameof(SpecimenNode.name)}
";

This is the reason I have the const strings in the classes, it allows me to have one place to change if I need to, but also that change point is with the Model class.

 await session.ExecuteReadAsync(async runner =>
    {
        var results = await runner.RunAsync(query, new { paramDiet = "carnivorous" });
        await results.FetchAsync();
        while (results.Current != null)
        {
            Console.WriteLine(results.Current.Values["s"].As<INode>().Properties["name"].As<string>());
            await results.FetchAsync();
        }
    });

We then execute and get our list of Dinos!

Neo4jClient

In a similar approach, we write our query:

 var query = client.Cypher
        .Match($"(s:{SpecimenNode.Labels})-[:{HasDietRelationship.Type}]->(d:{DietNode.Labels})")
        .Where((DietNode d) => d.diet == "carnivorous")
        .Return(s => s.As<SpecimenNode>())
        .OrderBy("s.name");

In this case, we’re using the lambda to make our parsing of the Dinosaur names easier:

    foreach (var specimen in specimens)
        Console.WriteLine(specimen.name);

Wrap up

This will hopefully speed up some code generation procedures – I guess the next steps might be to add ‘CRUD’ features for specific drivers, to generate repositories for example. The project repo has a few issues that should be simple enough to take on, or – you want to add new features?