The Neo4j Type System

At this point, we should take a look at the Cypher type system. As Neo4j is written in Java (the j in Neo4j stands for Java after all), there are some discrepancies between the types stored in the Neo4j database and native C# types.

Some values like strings, numbers, booleans, dates, and nulls map directly to C# types but more complex types like nodes, relationship, points, durations need special handling.

.NET Types to Neo4j/Cypher Types
Neo4j Cypher Type .NET Type Notes

null

null

List

IList<object>

Map

IDictionary<string, object>

Boolean

bool

Integer

int or long

Float

double

String

string

ByteArray

byte[]

Date

LocalDate

See Temporal Types

Time

OffsetTime

See Temporal Types

LocalTime

LocalTime

See Temporal Types

DateTime*

ZonedDateTime

See Temporal Types

LocalDateTime

LocalDateTime

See Temporal Types

Duration

Duration

Point

Point

Node

INode

See Nodes & Relationships

Relationship

IRelationship

See Nodes & Relationships

Path

IPath

See Nodes & Relationships

You can use the .As<Type> method on any Values type or nested structure to cast underlying values to expected types. See the API docs for IRecord.Values for more information.

For example, in the code block below, the year value is cast as a Number.

c#
Accessing Values
int movieCount = row["movieCount"].As<int>();

Let’s take a look at some of these types in more detail.

Numbers

For numeric values the main confusion comes from the naming, while Neo4j itself can store all kinds of .NET primitive values for a unified surface, Cypher only exposes one floating point type called Float (equivalent to 64-bit double) and one integer type called Integer (equivalent to 64-bit long).

Cypher itself has functions like toFloat() and toInteger() respectively and driver parameters of other types are automatically coerced.

The driver’s Value type can return most .NET numeric types via .As<Type> methods.

Temporal Types

The Temporal types used in the Cypher type system mirror the .NET temporal types, so there are few surprises.

Learn how to interact with Neo4j from .NET using the Neo4j .NET DriverTemporal Types
Type Description Example Access Function

Date

Represents an instant capturing the date, but not the time, nor the timezone.

2020-01-02

As<Date>

DateTime

Represents an instant capturing the date, the time and the timezone identifier.

2020-01-02T01:02:03+04:00

As<DateTime>

LocalDateTime

Represents an instant capturing the date and the time, but not the timezone.

2020-01-02T01:02:03

As<LocalDateTime>

LocalTime

Represents an instant capturing the time of day, but not the date, nor the timezone.

12:34:56

As<LocalTime>

Duration

Represents a duration between two dates or timestamps (different resolutions). Has individual accessors for the parts.

P1M12D

As<DateTimeOffset>

c#
Working with Temporal types
var released = node["released"];
var localDate = released.As<LocalDate>();
var localDateTime = released.As<LocalDateTime>();
var localTime = released.As<LocalTime>();
var dateTimeOffset = released.As<DateTimeOffset>();

Nodes & Relationships

Nodes and Relationships are both returned as similar types with a common interface IEntity.

As an example, let’s take the following code snippet:

c#
Return Nodes and Relationships
var cursor = await tx.RunAsync(@"
    MATCH path = (person:Person)-[actedIn:ACTED_IN]->(movie:Movie)
    RETURN path, person, actedIn, movie,
            size((person)-[:ACTED]->()) as movieCount,
            exists { (person)-[:DIRECTED]->() } as isDirector
    LIMIT 1
");

Nodes

We can retrieve the person value using the .Values["person"] method on the row and then turn it into a INode using as<INode>.

c#
// Get a node
INode person = row["person"].As<INode>();

The value assigned to the person variable will be the instance of an INode implementation. INode is an implementation provided by the Neo4j .NET Driver to represent the information held in Neo4j for a node.

An instance of a Node has three parts:

c#
Working with Node Objects
long nodeId = person.Id; // (1)
IReadOnlyList<string> labels = person.Labels; // (2)
IReadOnlyDictionary<string, object> properties = person.Properties; // (3)
  1. Id - a long representing the internal ID for the node.

  2. Labels - a list of string values, eg. ['Person', 'Actor']

  3. Properties - A dictionary containing all the properties for the node.
    eg. {"name": "Tom Hanks", "tmdbId": "31" }

Properties can also be retrieved from IEntity instances using square brackets, for example person.Properties["name"].

Internal IDs

Internal IDs should be treated as opaque values and just sent back to the database as you get them. These ids can be re-used, a best practice is to always look up a node by its business key and label rather than relying on an internal ID.

Relationships

Relationship objects are also IEntity instances, and therefore also include an Id, and Properties.

c#
Working with Relationships
IRelationship actedIn = row["actedIn"].As<IRelationship>();

long relId = actedIn.Id; // (1)
string type = actedIn.Type; // (2)
IReadOnlyDictionary<string, object> relProperties = actedIn.Properties;
long startId = actedIn.StartNodeId;
long endId = actedIn.EndNodeId;
  1. Id - the internal ID for the relationship.

  2. Type - a string representing the type of the relationship, eg - ACTED_IN

  3. Properties - A .NET Map containing all the properties for the relationship.
    eg. {"role": "Woody" }

  4. StartNodeId - a long representing the internal ID for the node at the start of the relationship

  5. EndNodeId - a long representing the internal ID for the node at the end of the relationship

Paths

If you return a path of nodes and relationships, they will be returned as an instance of the IPath implementation.

c#
Working with Path Objects
IPath path = row["path"].As<IPath>();

INode start = path.Start; // (1)
INode end = path.End; // (2)

IEnumerable<INode> nodes = path.Nodes; // (3)
IEnumerable<IRelationship> rels = path.Relationships; // (4)
  1. Start - the node starting the path

  2. End - the node ending the path

  3. Nodes - An IEnumerable containing the nodes that form the path

  4. Relationships - An IEnumerable containing IRelationship implementations for all segments within the path

Path Segments

A path is split into segments representing each relationship in the path. For example, say we have a path of (p:Person)-[:ACTED_IN]->(m:Movie)-[:IN_GENRE]->(g:Genre), there would be two segments.

  1. (p:Person)-[:ACTED_IN]->(m:Movie)

  2. (m:Movie)-[:IN_GENRE]->(g:Genre)

The IPath implementation can be iterated over to access each relationship within the path.

c#
Iterating over Segments
foreach (var segment in path) {
    Console.WriteLine(segment.Id);
    Console.WriteLine(segment.Type);
    Console.WriteLine(segment.StartNodeId);
    Console.WriteLine(segment.EndNodeId);
    Console.WriteLine(segment.Properties);
}

Check Your Understanding

1. Accessing Node Properties

Which property would you access to log the "name" property for each person to the console?

Select the correct option in the code block below.

c#
IEnumerable<INode> nodes = path.Nodes;

foreach (var node in nodes) {
    Console.WriteLine(node./*select:Properties["name"]*/)
}
  • ❏ Values["name"]

  • ✓ Properties["name"]

  • ❏ Get("name")

  • ❏ Properties[0]

Hint

The properties property on a Node is a dictionary.

Solution

The answer is Properties["name"].

2. Temporal Accessors

Which of the following functions does the Neo4j Value not support for temporal types.

  • As<Date>

  • As<DateTime>

  • As<IsoDuration>

  • As<LocalDateTime>

Hint

Neo4j supports Date, DateTime, LocalDateTime, Ti`me and LocalTime temporal types.

Solution

The only unsupported function above is As<IsoDuration>.

Lesson Summary

In this lesson you have learned how to handle some of the more complex objects returned by a Cypher statement. As we progress through this module, you will use the knowledge gained so far to read data from, and write data back to the database.

In the next Challenge, you will modify code to read from the database.