21
Neo4j AuraDB in C#

This chapter's example uses C# to build a NoSQL graph database in the cloud. It uses the Neo4j AuraDB database to build and perform queries on the org chart shown in Figure 21.1.

A representation of the org chart to perform queries.

FIGURE 21.1

If you skipped Chapter 20, “Neo4j AuraDB in Python,” which built a similar example in Python, go to that chapter and read the beginning and the first three sections, which are described in the following list.

  • “Install Neo4j AuraDB” explains how to install the Neo4j AuraDB graph database.
  • “Nodes and Relationships” explains some basic graph database concepts that you'll need to understand to use AuraDB.
  • “Cypher” briefly introduces the Cypher graph query language that the example uses to interact with the database.

When you reach the section “Create the Program” in Chapter 20, return to this chapter and read the following sections.

CREATE THE PROGRAM

To create a C# program to work with the AuraDB org chart database, create a new C# Console App (.NET Framework) and then add the code described in the following sections.

Like the example described in Chapter 20, this example builds an assortment of helper methods that do things like create nodes, build relationships, and execute queries on the data. It then uses those methods to create two higher-level methods that build and query the org chart. Finally, the main program connects to the database and calls those two methods.

Install the Neo4j Driver

This example uses the Neo4j driver to allow your program to communicate with the AuraDB database sitting in the cloud. To add the driver to your project, follow these steps:

  1. Open the Project menu and select Manage NuGet Packages.
  2. Click the Browse tab and enter Neo4j.Driver.Simple in the search box. Click that entry in the list on the left and then click the Install button on the right. This shouldn't take too long, but it's still nice to have a fast Internet connection.

This example uses a pattern that is slightly different from the one used in Chapter 20. The previous example used action methods that took a transaction object as a parameter and then used that object's methods to do the work.

In this example, the helper methods take a database driver as a parameter. Those methods use the driver's Session method to get a session object that they then use to run database commands.

Session objects can start transactions that you can commit or roll back. This example simply calls the session's Run method to execute database commands, and the Run method uses an auto-commit transaction by default so that we don't need to worry about committing the result.

All of these features are differences in the two database adapters, but the underlying database engine is the same. You will often find multiple database adapters available for a given database engine, so you might want to shop around to find one that provides features that you like.

The session objects used in this example are “simple” sessions that work synchronously. Neo4j also provides asynchronous session objects that may be useful for some applications, particularly if your network is slow. We're using the “simple” sessions because they are, if not exactly simple, at least simpler than the asynchronous version.

The following sections divide the program's code into three parts: action methods that build basic objects such as nodes and relationships, org chart methods that build and explore the org chart, and the main program.

Action Methods

The following sections describe the lower-level action methods. Each takes a driver object as its first parameter. They use the driver to create a session object, and then use that object to run a database command or query.

They follow the same pattern, so you'll probably get the hang of them quickly. The more interesting differences are the query commands that they send to the database. Those queries are vaguely reminiscent of SQL, but they're not the same because they need to work with a graph database instead of a relational database.

Table 21.1 lists the action methods and gives their purposes.

TABLE 21.1: Action methods and their purposes

METHODPURPOSE
DeleteAllNodesDelete all nodes and their links.
MakeNodeCreate a new node.
MakeLinkCreate a relationship between two nodes.
ExecuteNodeQueryExecute a query that returns nodes.
FindPathFind a path between two nodes.

DeleteAllNodes

The following code shows how the DeleteAllNodes method deletes all the nodes in the database:

using Neo4j.Driver;

// Delete all previous nodes and links.
static void DeleteAllNodes(IDriver driver)
{
    using (ISession session = driver.Session())
    {
        string statement = "MATCH (n) DETACH DELETE n";
        session.Run(statement);
    }
}

The module includes the statement using Neo4j.Driver to make it easier to use the driver.

The DeleteAllNodes method takes a driver object as a parameter, uses it to create a session object, and then uses the session object's Run method to execute the database command MATCH (n) DETACH DELETE n.

Notice that the session object is created in a using block, so it is automatically disposed of when we're done with it. The other methods in this example use a similar approach. Alternatively, you could create a single session and pass it around to all the methods.

The MATCH command is similar to a SQL query. It uses a pattern to match objects inside the database. Then later parts of the command do something with any objects that match.

In this case, the pattern (n) matches any node. In general, expressions inside parentheses () match nodes and expressions surrounded by square brackets [] match relationships.

Note that you cannot delete a node if it is involved in any relationships. (This is kind of like a graph database version of a foreign key constraint.) You can either delete the relationships first or include the DETACH keyword to tell the database to delete those relationships automatically with the node.

The last part of the command is DELETE n, which deletes the node n. Here, n is a node that was matched by the MATCH (n) part of the command.

To summarize, this statement says, “Match all nodes, detach their relationships, and then delete them.”

MakeNode

The following code shows the MakeNode method:

// Use parameters to make a node.
static void MakeNode(IDriver driver, string id, string title)
{
    using (ISession session = driver.Session())
    {
        string statement = "CREATE (n:OrgNode { ID:$id, Title:$title })";
        Dictionary<string, object> parameters =
            new Dictionary<string, object>()
            {
                {"id", id},
                {"title", title},
            };
        session.Run(statement, parameters);
    }
}

This method creates a session and then composes a CREATE command to add a node to the database. The n:OrgNode part tells the database that this node's type is OrgNode. That's just a name that I made up for this kind of node. You could create CustomerNode, RecipeNode, SaberToothDuckNode, or any node types that make sense for your application. You also don't need to include the word Node; I just decided that it would make the queries easier to understand.

The part of the statement that's inside the curly brackets tells the database to give the node two properties, ID and Title. We'll provide values for the placeholders with the dollar signs shortly.

Note that property names are case-sensitive, so if you create a Title property and later search for a title value, you won't get any matches. (And you'll waste a lot of time wondering why not.)

The code then makes a dictionary that uses the placeholder names as keys and supplies values for them. The first dictionary entry associates the $id placeholder with the parameter id that was passed into MakeNode. The second entry associates the $title placeholder with the method's title parameter.

The code finishes by calling the session object's Run method, passing it the command string and the parameters dictionary.

MakeLink

The following code shows how the MakeLink method creates a relationship between two nodes:

// Use parameters to create a link between an OrgNode and its parent.
static void MakeLink(IDriver driver, string id, string boss)
{
    using (ISession session = driver.Session())
    {
        string statement =
            "MATCH" +
            "    (a:OrgNode)," +
            "    (b:OrgNode) " +
            "WHERE" +
            "    a.ID = $id AND " +
            "    b.ID = $boss " +
            "CREATE (a)-[r:REPORTS_TO { Name:a.ID + '->' + b.ID }]->(b) ";
        Dictionary<string, object> parameters =
            new Dictionary<string, object>()
            {
                {"id", id},
                {"boss", boss},
            };
        session.Run(statement, parameters);
    }
}

This method creates a REPORTS_TO link for the org chart. The MATCH statement looks for two OrgNode objects, a and b, where a.ID matches the id parameter passed into the method and b.ID matches the boss parameter.

After the MATCH statement finds those two nodes, the CREATE command makes a new REPORTS_TO relationship leading from node a to node b. It gives the relationship a Name property that contains the two IDs separated by ->. For example, if the nodes' IDs are A and B, then this relationship's Name is A->B.

ExecuteNodeQuery

The following code shows the ExecuteNodeQuery method, which executes a query that matches nodes that are named n in the query. This method returns a list of the query's results:

// Execute a query that returns zero or more nodes
// identified by the name "n".
// Return the nodes in the format "ID: Title".
static List<string> ExecuteNodeQuery(IDriver driver, string query)
{
    List<string> result = new List<string>();
    using (ISession session = driver.Session())
    {
        foreach (IRecord record in session.Run(query))
        {
            INode node = (INode)record.Values["n"];
            result.Add($"{node["ID"]}: {node["Title"]}");
        }
    }
    return result;
}

The method first creates a list to hold results. It then runs the query and loops through the records that it returns.

Inside the loop, it uses record.Values["n"] to get the result named n from the current record. That result will be a node selected by the MATCH statement. (You'll see that kind of MATCH statement a bit later.) The code copies the node's ID and Title properties into a string and adds the string to the result list.

After it finishes looping through the records, the method returns the list.

FindPath

The following code shows how the FindPath method finds a path between two nodes in the org chart:

// Execute a query that returns a path from node id1 to node id2.
// Return the nodes in the format "ID: Title".
static List<string> FindPath(IDriver driver, string id1, string id2)
{
    List<string> result = new List<string>();
    using (ISession session = driver.Session())
    {
        string statement =
            "MATCH" +
            "    (start:OrgNode { ID:$id1 } )," +
            "    (end:OrgNode { ID:$id2 } ), " +
            "    p = shortestPath((start)-[:REPORTS_TO *]-(end)) " +
            "RETURN p";
        Dictionary<string, object> parameters =
            new Dictionary<string, object>()
            {
                {"id1", id1},
                {"id2", id2},
            };
 
        // Get one path.
        IRecord record = session.Run(statement, parameters).Single();
        IPath path = (IPath)record.Values["p"];
 
        foreach (INode node in path.Nodes)
        {
            result.Add($"{node["ID"]}: {node["Title"]}");
        }
    }
    return result;
}

This code looks for two nodes that have the IDs passed into the method as parameters. It names the matched nodes start and end.

It then calls the database's shortestPath method to find a shortest path from start to end following REPORTS_TO relationships. The * means the path can have any length. The statement saves the path that it found with the name p.

Note that the shortestPath method only counts the number of relationships that it crosses; it doesn't consider costs or weights on the relationships. In other words, it looks for a path with the fewest steps, not necessarily the shortest total cost as you would like in a street network, for example. Some AuraDB databases can perform the least total cost calculation and execute other graph algorithms, but the free version cannot.

After it composes the database command, the method executes it, passing in the necessary parameters. It calls the result's single method to get the first returned result.

It then looks at that result's p property, which holds the path. (Remember that the statement saved the path with the name p.)

The code loops through the path's nodes and adds each one's ID and Title values to a result list. The method finishes by returning that list.

Org Chart Methods

That's the end of the action methods. They do the following:

  • Delete all nodes.
  • Make a node.
  • Make a link.
  • Execute a query that returns nodes.
  • Find a path between two nodes.

The following sections describe the two methods that use those tools to build and query the org chart. The earlier action methods make these two relatively straightforward.

BuildOrgChart

The following code shows how the BuildOrgChart method builds the org chart:

// Build the org chart.
static void BuildOrgChart(IDriver driver)
{
    // Make the nodes.
    MakeNode(driver, "A", "President");
    MakeNode(driver, "B", "VP Ambiguity");
    MakeNode(driver, "C", "VP Shtick");
    MakeNode(driver, "D", "Dir Puns and Knock-Knock Jokes");
    MakeNode(driver, "E", "Dir Riddles");
    MakeNode(driver, "F", "Mgr Pie and Food Gags");
    MakeNode(driver, "G", "Dir Physical Humor");
    MakeNode(driver, "H", "Mgr Pratfalls");
    MakeNode(driver, "I", "Dir Sight Gags");
 
    // Make the links.
    MakeLink(driver, "B", "A");
    MakeLink(driver, "C", "A");
    MakeLink(driver, "D", "B");
    MakeLink(driver, "E", "B");
    MakeLink(driver, "F", "C");
    MakeLink(driver, "G", "C");
    MakeLink(driver, "H", "G");
    MakeLink(driver, "I", "G");
}

This method calls the MakeNode method repeatedly to make the org chart's nodes. It then calls the MakeLink method several times to make the org chart's relationships.

Notice that each call to MakeNode and MakeLink includes the transaction object that BuildOrgChart received as a parameter.

QueryOrgChart

The following code shows how the QueryOrgChart method performs some queries on the finished org chart:

// Perform some queries on the org chart.
static void QueryOrgChart(IDriver driver)
{
    List<string> result;
 
    // Get F.
    Console.WriteLine("F:");
    result = ExecuteNodeQuery(driver,
        "MATCH (n:OrgNode { ID:'F' }) " +
        "return n");
    Console.WriteLine($"    {result[0]}");
 
    // Who reports directly to B.
    Console.WriteLine("
Reports directly to B:");
    result = ExecuteNodeQuery(driver,
        "MATCH " +
        "    (n:OrgNode)-[:REPORTS_TO]->(a:OrgNode { ID:'B' }) " +
        "return n " +
        "ORDER BY n.ID");
    foreach (string s in result)
        Console.WriteLine("    " + s);
 
    // Chain of command for H.
    Console.WriteLine("
Chain of command for H:");
    result = FindPath(driver, "H", "A");
    foreach (string s in result)
        Console.WriteLine("    " + s);
 
    // All reports for C.
    Console.WriteLine("
All reports for C:");
    result = ExecuteNodeQuery(driver,
        "MATCH " +
        "    (n:OrgNode)-[:REPORTS_TO *]->(a:OrgNode { ID:'C' }) " +
        "return n " +
        "ORDER BY n.ID");
    foreach (string s in result)
        Console.WriteLine("    " + s);
}

This method first calls the ExecuteNodeQuery method to execute the following query:

MATCH (n:OrgNode { ID:'F' }) return n

This simply finds the node with ID equal to F. The code prints it.

Next, the method looks for nodes that have the REPORTS_TO relationship ending with node B. That returns all of the nodes that report directly to node B. The code loops through the results displaying them.

The method then uses the FindPath method to find a path from node H to node A. Node A is at the top of the org chart, so this includes all the nodes in the chain of command from node H to the top.

The last query the method performs matches the following:

(n:OrgNode)-[:REPORTS_TO *]->(a:OrgNode { ID:'C' })

This finds nodes n that are related via any number (*) of REPORTS_TO relationships to node C. That includes all the nodes that report directly or indirectly to node C. Graphically, those are the nodes that lie below node C in the org chart.

Main

The previous methods make working with the org chart fairly simple. All we need to do now is get them started.

The following code shows the main program:

static void Main(string[] args)
{
    // Replace the following with your database URI, username, and password.
    string uri = "neo4j+s://386baeab.databases.neo4j.io";
    string user = "neo4j";
    string password = "InsertYourReallyLongAndSecurePasswordHere";
 
    // Create the driver.
    using (IDriver driver = GraphDatabase.Driver(uri,
        AuthTokens.Basic(user, password)))
    {
        // Delete any previous nodes and links.
        DeleteAllNodes(driver);
 
        // Build the org chart.
        BuildOrgChart(driver);
 
        // Query the org chart.
        QueryOrgChart(driver);
    }
 
    Console.Write("
Press Enter to quit");
    Console.ReadLine();
}

This code first defines the uniform resource identifier (URI) where the database is located, the username, and the password. You can find these in the credential file that you downloaded when you created the database instance. (I hope you saved that file! If you didn't, then this might be a good time to delete the database instance and start over.)

Next, the code uses the URI, username, and password to create a graph database driver. Notice that the program creates the driver in a using block so that it is automatically disposed of when the program is done with it.

The program then calls the DeleteAllNodes, BuildOrgChart, and QueryOrgChart methods to do all the interesting work.

For its grand finale, the program displays a message and then waits for you to press Enter so that the output doesn't flash past and disappear before you can read it.

The following shows the program's output:

Deleting old data…
Building org chart…
Querying org chart…
F:
    F: Mgr Pie and Food Gags
 
Reports directly to B:
    D: Dir Puns and Knock-Knock Jokes
    E: Dir Riddles
 
Chain of command for H:
    H: Mgr Pratfalls
    G: Dir Physical Humor
    C: VP Shtick
    A: President
 
All reports for C:
    F: Mgr Pie and Food Gags
    G: Dir Physical Humor
    H: Mgr Pratfalls
    I: Dir Sight Gags
 
Press Enter to quit

Figure 21.2 shows the same org chart in Figure 21.1, so you can look at it to see that the output is correct.

A representation of the org chart.

FIGURE 21.2

SUMMARY

This chapter showed how you can use C# and a NoSQL graph database to build and explore an org chart. You can use similar techniques to work with other trees and, more generally, graphs that are not trees.

As you work with this example, you might notice that operations are relatively slow, particularly if you have a slow network connection. This is generally true of cloud applications. Network communications tend to be slower than local calculations.

The pattern that this example used was to:

  1. Create a database driver object.
  2. Use the driver to create a session object.
  3. Use the session object's Run method to send Cypher commands to the database engine.

The next two chapters show how to use a NoSQL document database in the cloud. Chapter 22 shows a Python example and Chapter 23 shows a similar example in C#. Before you move on to those chapters, however, use the following exercises to test your understanding of the material covered in this chapter. You can find the solutions to these exercises in Appendix A.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.11.18