Neo4jClient 4.0

After what probably seems like ages to you (and indeed me) Neo4jClient 4.0 has finally left the pre-release stages and is now in a stable release.

Being a major version change, that means there are breaking changes, and you should be in the process of testing stuff before you just use it. Having said that, the changes which are there hopefully make sense, and will make it better in the long run.

It should be noted, that some of the things done are only applicable to Neo4j 4.x servers, and if you’re not using .NET Core, or not using Transactions – staying with the 3.2.x release of the client will be fine for now.

OK. Onto the changes, we have 4 broad categories, Breaking changes, General changes, Additions and Removals. I’ll try to demo as many of them as possible with code, but for the self-explanatory ones – well – I probably won’t 😮

Breaking

These are ones which will require you to do some code changes, how much will vary depending on your codebase.

Async Only

There are no ‘Sync’ methods anymore, decision wise – .NET code has been increasingly moving towards Async, and the client has always supported it. If there is enough clamour – I will look into re-adding the Sync wrappers, but at the moment, less-code = less to maintain.

No MSDTC (TransactionScope) Support

The 3.x versions of the client used TransactionScope to provide transaction support – which had a nice benefit of being able to support MSDTC. But, as I’m sure many know, it also prevented .NET Core support for transactions due to the availability of TransactionScope and the supporting classes in Core. With .NetStandard 2.0 TransactionScope was added, but the supporting classes not, and whilst now they are available in NetStandard 2.1 – I don’t want to push the minimum requirements for the Client to NetStandard 2.1. As a consequence, the decision has been made to remove support for TransactionScope.

Long term – the aim is to have an ITransactionManager that you can inject into the Client to allow you to provide a home rolled TransactionScope manager if you really want it. This doesn’t exist – and won’t for a while as because as far as I’m aware, there was only one company using it, and even they said they were moving away from it.

Neo4j Server 3.5+ only

Neo4j don’t support server versions lower than 3.5, and whilst the GraphClient should work with any of the 3.x servers (which support transactions), the BoltGraphClient will only work back to 3.5.

Personally – I’m not to worried about this, aside from Transactions all of the additions to the client wouldn’t work on older server versions anyhow, as they didn’t exist. Basically – if you use an older version of the server – use the older client!

Other

There are other changes that will come with the ‘Removals’ section below, but I thought I’d write about them there!

General Things

So some general changes here about the client, you might find it interesting, you might not :/

NetStandard 2.0

Thanks to a PR from tobymiller1 – (https://github.com/tobymiller1) the client is back to being just one project, and targets NetStandard 2.0

URI Schemes

The client now supports all the schemes Neo4j does, so neo4j, neo4j+s, neo4j+sc and the bolt equivalents.

Transactions

I wasn’t sure if this should be Addition or Removal or … so I’ve gone with ‘General’. Transactions needed to be changed, as I noted above, and as part of the ability to target Neo4j 4.x we needed to support Multi-tenancy – that’s multi-databases within one server.

Let’s have a look at some examples:

using(var tx = gc.BeginTransaction(TransactionScopeOption.Join, null, "neo4jclient"))
{
    await gc.Cypher.Create("(n:Node {Id:1})").ExecuteWithoutResultsAsync();
    var insideResults = await gc.Cypher.Match("(n:Node)").Return(n => n.As<Node>()).ResultsAsync;
    insideResults.Dump("In the Transcation");

    await tx.RollbackAsync();
}

var outsideResults = await gc.Cypher.Match("(n:Node)").Return(n => n.As<Node>()).ResultsAsync;
outsideResults.Dump("Out of the Transcation");

Which gives out:

Let’s take the code apart a bit.

using(var tx = gc.BeginTransaction(TransactionScopeOption.Join, null, "neo4jclient"))

Transactions are IDisposable – so you ideally should be using them with a using statement, but if not, remember to dispose()! In this version, you have to supply the TransactionScopeOption and a bookmark (the null) in this case, to be able to define the database ("neo4jclient"). That’ll probably be simplified later…

Next, we’re just executing Cypher, as we’ve done plenty of times before, and as I’m using LinqPad I can call .Dump() on any output to get the results (we saw in the picture). So I can access the stuff I’ve put in the database, within the transaction.

I then .RollbackAsync() the transaction, the effects of which can be seen by the second .Dump() I execute showing nothing in the database. Also note, with the second Results query – I have to provide a WithDatabase parameter, else I would be querying the default database.

You HAVE to .CommitAsync() a transaction for it to be committed, if I didn’t have the .RollbackAsync()) call, and just let the tx be Dispose()-ed by the using statements closing, it would automatically be rolled back. In the code above, the .RollbackAsync() call is redundant.

Write transactions are on one database only, an attempt to write to another database (using WithDatabase or Use) will result in a ClientException – nb. you can use Use to read from another database in a transaction.

Additions

New stuff for you to use! After each heading will be a list of the versions of Neo4j Server that the additions will work on.

DefaultDatabase [4.x]

Multi-tenancy brings a load of new things to the Neo4j world, but how can you use them from Neo4jClient?? First off let’s talk about DefaultDatabase. This is a property of the GraphClient/BoltGraphClient itself. By default it’s set to ‘neo4j’ (which is the default the server has), but you can set it to any other database, and every query will be against that one.

var gc = new GraphClient(...){DefaultDatabase = "neo4jclient"};

.WithDatabase() [4.x]

But what if you want to run just one of your queries (or more!) against another database? That’s where WithDatabase comes in. WithDatabase is a per query setting to choose the database a query will run on:

gc.Cypher.WithDatabase("neo4jclient").Match(...)

CreateDatabase (system) [4.x]

Want to create a database? Sure, you can do that! This needs to be executed against the system database, and you have to use the WithDatabase call do to that:

gc.Cypher.WithDatabase("system").CreateDatabase("neo4jclient", true).ExecuteWithoutResultsAsync();

Wait? What’s this true parameter? It’s the ifNotExists parameter which means that we will only create the database if it doesn’t exist.

StartDatabase (system) [4.x]

Well, now we’ve created our database, we need to start it:

gc.Cypher.WithDatabase("system").StartDatabase("neo4jclient").ExecuteWithoutResultsAsync();

If you run this on an already started database, it’ll do nothing!

StopDatabase (system) [4.x]

We’ve started it, now let’s stop it. No surprises here:

gc.Cypher.WithDatabase("system").StopDatabase("neo4jclient").ExecuteWithoutResultsAsync();

Again, as per the StartDatabase method, stopping a stopped database does nothing.

DropDatabase (system) [4.x]

Dropping a database is the quickest way to ‘truncate’ your entire database without resorting to stopping the server. As with CreateDatabase we have two parameters here, the first is the database name, the second is dumpData (default false). If dumpData is false then calling DropDatabase will delete all the data files, if it’s true the data will first be dumped to the location the server has specified (see the documenation for more information)

gc.Cypher.WithDatabase("system").DropDatabase("neo4jclient", true).ExecuteWithoutResultsAsync();

If you run this on a database that has already been dropped – you will get a FatalDiscoveryException thrown, if you want to avoid that, you need to use:

DropDatabaseIfExists (system) [4.x]

Pleasingly this is the same as DropDatabase just that you won’t get the exception if the database doesn’t exist. You have the same option to dumpData or not. It’s all up to you!

gc.Cypher.WithDatabase("system").DropDatabaseIfExists("neo4jclient", true).ExecuteWithoutResultsAsync();

Use [4.x]

Use() allows you to use a database within a query or query part. It means you can avoid having to use WithDatabase, or you can use it within a query that is using another database. Whaaaat?! Confusing?! Yes!

gc.Cypher.Use("neo4jclient").Match("(n)").Return(n => n.As<Node>())

Gives us:

USE neo4jclient
MATCH (n)
RETURN n

BUT you can’t do this for things like the system database calls above, you have to use the WithDatabase clause for that. Use is particularly Useful (ha!) for Fabric use cases.

WithQueryStats [3.5,4.x]

With 3.x when you executed a query, you couldn’t tell what that query had done, in fact, all you could know was that you executed a query (especially if it was a ‘non results’ one). Now you can use the .OperationCompleted event to get the stats of your query:

void OnGraphClientOnOperationCompleted(object o, OperationCompletedEventArgs e)
{        
    e.QueryStats.Dump();
}
gc.OperationCompleted += OnGraphClientOnOperationCompleted;
await gc.Cypher.WithQueryStats.Create("(n:Node {Id: 10, Db: 'neo4j'})").ExecuteWithoutResultsAsync();
gc.OperationCompleted -=OnGraphClientOnOperationCompleted;

Which will give you a QueryStats object that looks like:

Check that out! I added a new Label, 1 Node and 2 Properties!

This isn’t on by default, as it sends back more data over the wire, and if you don’t need it (which so far 100% of people haven’t) then it’s optional!

Neo4jIgnoreAttribute [3.5,4.x]

From a PR by @Clooney24) This will provide the ability to ignore properties for the BoltGraphClient as well as the GraphClient.

So, let’s have our class:

public class Node 
{
    public string Db {get;set;}
    public int Id {get;set;}

    [Neo4jIgnore]
    public string Ignored {get;set;}
}

We’ve defined the Ignored property with the [Neo4jIgnore] attribute, so now when we insert the data:

var node = new Node { 
        Id = 11, 
        Db = gc.DefaultDatabase, 
        Ignored = "You won't see this!" 
    };
await gc.Cypher.WithQueryStats.Create("(n:Node $newNode)").WithParam("newNode", node).ExecuteWithoutResultsAsync();

and then pull it back:

(await gc.Cypher.Use("neo4j").Match("(n)").Return(n => n.As<Node>()).ResultsAsync).Dump("Ignored");

We can see that Ignored is null:

And this isn’t just because we’re ignoring bringing it back, but it’s not in the database either:

Removals

These are all things that have been removed, largely, they were marked as [Obsolete] so you can’t say you weren’t warned! If you are using these, then you need to stay on a 3.x release of the client.

Start

This hasn’t been around since well, 3.0 I think, and has certainly deprecated for a long time. As it wouldn’t work in 3.5 onwards anyway I’m content to remove it.

Create(string, params object[]) 4/n

Again, marked as [Obsolete] and you should be using the alternative Create options instead.

Return<T>(string, CypherResultMode)

This was accidently made public, it was never intended to be, and as it has had at least 1 major version of Obsolete-ness, it’s gone.

StartBit

This pretty much comes with the Start code above.

Gremlin support

If you’re using Gremlin – there are no benefits to this version of the client, so stick with what you have. You should progress to Cypher if you can, it’s actively developed and is very closely linked to the new GQL standards that ISO have started to work on.

From a ‘career’ point of view, this means that learning Cypher is like learning GQL but with maybe a different accent, but not a different language. As GQL becomes a standard, other Graph databases will start to use it, and you’ll be ahead of the game.

Finally

Some other bits of tidying up!

URIs

You may (or may not) have noticed that the URI for the client has changed from: https://github.com/Readify/Neo4jClient to https://github.com/DotNet4Neo4j/Neo4jClient. This means that the project is now part of an Organisation (DotNet4Neo4j) which is focused on things that link Neo4j and .NET together.

Err.

I think that’s about it.

༼ つ ◕_◕ ༽つ

Reactive Neo4j using .NET

Version 4.0 of Neo4j is being actively worked on, and aside from the new things in the database itself, the drivers get an update as well – and one of the big updates is the addition of a Reactive way to develop against the DB.

Now – I’ve not done reactive programming for a long time, I think I did play around with it when .NET 4 was first released, but I have no idea where that blog post has gone – so I may as well start as new.

I found it! Not the post, but the application – MousePath – which is now on GitHub: MousePath – aside from it ‘working’ it’s not performant in any way.

What is Rx/Reactive?

Reactive in .NET is all about the IObservable<T>/IObserver<T> interfaces. They’ve been around since .NET 4, but personally I’ve never really used them. They allow application code to react to data being pushed to it, rather than the more traditional way of requesting the data.

There’s a good book (Intro to Rx) which I will been using to work this out, which is freely available online: http://introtorx.com/ .

Starting off

For this project, we’re going to need the nuget package – which in this case isn’t Neo4j.Driver – but Neo4j.Driver.Reactive. When we add this to our project – and create a driver in the normal way- we can see we now have an ‘RxSession‘ which is an extension method of the IDriver.

So let’s create a reactive session and see what we can see.

RxSession

We get IObservable as opposed to the AsyncSession giving us Tasks

AsyncSession

Doing a Run-ner

So, back to our RxSession, lets do a basic version, just using Run

var session = Driver.RxSession();
var rxStatementResult = session.Run("MATCH (m:Movie) RETURN m.title");
rxStatementResult
    .Records()
    .Subscribe(
        record => Console.WriteLine("Got: " + record.Values["m.title"].As<string>())
    );

In here, we’re hooking up to the ol’ classic Movies database, and simply writing the titles to the screen. NB – Driver is a static property of type IDriver I have defined elsewhere.

The first two lines look pretty much like our normal code – the only real difference being the use of the ‘RxSession‘ as opposed to just ‘Session‘.

Run on an RxSession returns an IRxStatementResult – which has 3 methods we’re interested in, (well actually only 1 at the moment) – Records(), Consume() and Keys().

Records() gets us the records from the database, so the stuff we want to do things with, Consume() whips through those records so we can get an IResultSummary telling us what is going on, and Keys() gets us the keys that are returned, in the simple statement I’ve done – ‘m.title‘ is the only key.

Records() is what we’re using, as we want to deal with the data, Records() return us an IObservable<IRecord> and being IObservable – we need to Subscribe() to it to get the data. Subscribing means we will provide an IObserver that will be notified whenever an IRecord arrives.

In this case, we have the contents being written to the console. Aces.

Quitting

Being a console app – doing tiny amounts of work – I largely don’t need to worry about disposing of my resources, but let’s imagine resource usage is something we do care about. How do you go about disposing of your resources?

IDisposable? INosable! – the IRxSession doesn’t implement IDisposable, instead we have to Close<T>() it – and this is where things have got a little fuzzy for me – I’m not entirely sure I’m closing it correctly.

var session = Driver.RxSession();
var rxStatementResult = session.Run("MATCH (m:Movie) RETURN m.title");
rxStatementResult
    .Records()
    .Subscribe(
        record => Console.WriteLine("Got: " + record.Values["m.title"].As<string>()));

session.Close<IRecord>();

Now, I expect to get either no results, or a smaller subset (depending on the speed of the code running) – what I get is the close being called, but still getting the full amount of data – I suspect I misunderstand what is going on here.

Let’s say I do want a smaller subset – or to quit – how do I do it? Well, the Subscribe() method actually returns an IObservable<IRecord> – which is also IDisposable – so we ‘unsubscribe’ by disposing of our subscriber:

var session = Driver.RxSession();
var rxStatementResult = session.Run("MATCH (m:Movie) RETURN m.title");
var subscription = rxStatementResult
    .Records()
    .Subscribe(record => Console.WriteLine("Got: " + record.Values["m.title"].As<string>()));

await Task.Delay(220);
subscription.Dispose();
session.Close<IRecord>();

The ‘delay’ magic number there is enough time to get some records, but not all, anything less than that gave no results, anything more – all the results. ¯\_(ツ)_/¯

Ooook So – Why Rx?

It seems more complex right? Subscribe(), unsubscribe – no foreach in sight! What’s the point?

My understanding – and this could be/probably is wrong – is that by using Rx – we’re reducing our overheads – i.e. instead of streaming everything, we can just stream what we’re consuming at the time.

The other key benefits come from things like ‘.Buffer‘ and the other commands (Skip, Last, etc) allowing you to stream things in a better way.

One nice thing about Rx in .NET is that it’s not the same as async – you don’t have to have your entire stack in Rx to get the benefits – you can do bits and pieces where you need to – if you’ve got a lot of data maybe it makes sense for a given query.

Neo4j with Azure Functions

Recently, I’ve had a couple of people ask me how to use Neo4j with Azure functions, and well – I’d not done it myself, but now I have – let’s get it done!

  1. Login to your Azure Portal

  2. Create  a new Resource, and search for ‘function app’:

image

  1. Select ‘Function App’ from the Market Place:

image

  1. Press ‘Create’ to actually make one:

image

  1. Fill in your details as you want them

image

I’m assuming you’re reasonably au fait with the setting here, in essence if you have a Resource Group you want to put it into (maybe something with a VNet) then go for it, in my case, I’ve just created a new instance of everything.

  1. Create the function, and wait for it to be ready. Mayhaps make a tea or coffee, have a break from the computer for a couple of mins – it’s all good!

image

  1. When it’s ready, click on it and go into the Function App itself (if it doesn’t take you there!)

  2. Create a new function:

image

  1. We want to create an HttpTrigger function in C# for this instance:

image

  1. This gives us a ‘run.csx’ file, which will have a load of default code, you can run it if you want,

image

and you’ll see an output window appear which will say:

image

Well – good – Azure Functions work, so let’s get a connection to a Neo4j instance – now – for this I’m assuming you have an IP to connect to – you can always use the free tier on GrapheneDB if you want to play around with this.

  1. Add references to a driver

We need to add a reference to a Neo4j client, in this case, I’ll show the official driver, but it will work as well with the community driver. First off, we need to add a ‘project.json’ file, so press ‘View Files’ on the left hand side –

image

Then add a file:

image

Then call it project.json – and yes it has to be that name:

image

With our new empty file, we need to paste in the nuget reference we need:

{
   "frameworks": {
     "net46":{
       "dependencies": {
         "neo4j.driver": "1.5.2"
       }
     }
    }
}

Annoyingly if you copy/paste this into the webpage, the function will add extra ‘closing’ curly braces, so just delete those.

image

If you press ‘Save and Run’ you should get the same response as before – which is good as it means that the Neo4j.Driver package has been installed, if we look at files, we’ll see the ‘project.json.lock’ file which we want to.

image

  1. Code

We want to add our connection information now, we’re going to go basic, and just return the COUNT of the nodes in our DB. First we need to add a ‘using’ statement to our code:

So add,

using Neo4j.Driver.V1;

Then replace the code in the function with:

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
     using (var driver = GraphDatabase.Driver("bolt://YOURIP:7687", AuthTokens.Basic("user", "pass")))
     {
         using (var session = driver.Session())
         {
             IRecord record = session.Run("MATCH (n) RETURN COUNT(n)").Single();
             int count = record["COUNT(n)"].As<int>();
             return req.CreateResponse(HttpStatusCode.OK, "Count: " + count);                  
         }
     }
}

Basically, we’ll create a Driver, open a session and then return a 200 with the count!

  1. Run

You can now ‘Save and Run’ and your output window should now tell you the count:

image

  1. Done

Your first function using Neo4j, Yay!

Neo4jClient 1.1.0.1

Originally posted on: http://geekswithblogs.net/cskardon/archive/2015/08/07/neo4jclient-1.1.0.1.aspx

Big milestone this one, Neo4jClient now supports Transactions, Authentication and some other little changes.

Transaction info is all here: https://github.com/Readify/Neo4jClient/wiki/Transactions

You can find the connecting stuff here (at the bottom):

https://github.com/Readify/Neo4jClient/wiki/connecting

And the other big(ish) change is the change to make the ‘CollectAs’ method return the class type specified (<T>) instead of Node<T>.

Big thanks for this go to Arturo Sevilla (https://github.com/arturosevilla) for the original Pull request that has been merged in.

There’ll be more info in a while. But for now, download the new version via nuget: https://www.nuget.org/packages/Neo4jClient and have graphy fun!

Ternary operators don’t work with nullable value types?

I’ve got the following situation:

DateTime? myDt = (DateTime) row["Column"];

This fails when retrieving a DBNull value, so we check for that:

DateTime? myDT = (row["Column"] == DBNull.Value) ? null : (DateTime) row["Column"];

This won’t compile, however doing:


DateTime? myDT;
if(row["Column"] == DBNull.Value)
myDT = null;
else
myDT = row["Column"];

works fine, now, I realise I can simplify that statement, but, for the purposes of this post, it is a closer match to the ternary operator.

Why won’t the ternary op work with the value type? It works perfectly with a string…

Production LINQ!

I’m happy today as I’ve written my first bit of Production LINQ code in the form of LINQ-to-XML.
It’s not the most complicated bit of code – only a grabbing of data from a file, but it does do what it says on the tin – uses C# 3.0 and (unfortunately) causes Resharper to complain on a constant basis 🙂