Writing queries
Introduction
The code snippets in this document are designed to complement any . NET template created using the steps outlined in the Getting started section. Throughout this guide, we leverage the domain model introduced by Kelvin Lawrence in PRACTICAL GREMLIN: An Apache TinkerPop Tutorial.
This domain model represents a fictional airline network as a graph, where airports serve as vertices and flights between them as edges. Using this intuitive model as our foundation, we'll explore a variety of Gremlinq queries to extract meaningful insights from the airline network graph. The entities are defined as follows:
public sealed class Airport : Vertex
{
public string? Code { get; set; }
public string? ICAO { get; set; }
public string? City { get; set; }
public string? Region { get; set; }
public string? Country { get; set; }
public string? Description { get; set; }
public int Runways { get; set; }
public int Elevation { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public int LongestRunway { get; set; }
}
public sealed class Route : Edge
{
public long Distance { get; set; }
}
Create the AirRoutes dataset
The AirRoutes dataset is initialized by calling CreateAirRoutesSmall. This method is idempotent - it checks for existing nodes and edges before creating new ones, so calling it multiple times is safe. To improve performance after the initial setup, you can comment out this line once the data has been loaded into your database.
Simple queries
How many airports are in the database?
Which airport have their name starting with the letter 'S'?
Walk the graph
Find all destination airports from SEA
Find all airports connected to SEA within 1500 miles
Find all airports that can reach SEA
Find all airports that are reachable from SEA by taking two flights
Using sub-queries
Find all airports that are reachable from SEA by taking one or two flights
var withinOneOrTwoFlights = await _g
.V<Airport>()
.Where(airport => airport.Code == "SEA")
.Union(
__ => __
.Out<Route>(),
__ => __
.Out<Route>()
.Out<Route>())
.OfType<Airport>()
.Dedup()
.ToArrayAsync();
Simpler:
Find all destinations from SEA that have a connection to Atlanta
Orderings
This section marks the first introduction of a special of a sub-domain-specific language (sub-DSL), enabling developers to ensure that only sensible methods are within scope. This sub-DSL streamlines the programming process by providing a focused and tailored set of methods relevant to the specific context by providing a builder instance.
Order all airports lexically by their code
Order all airports by their longest route
Order all airports by their longest route, then lexically by their code
Limiting results
Get the five airports with the longest routes
Using Range instead
Skip the first five results
Take only the latter five
Step labels
Avoid returning to the departure airport right away
Projections
Project to value tuples
Project to value tuples with sub-query
Project to dynamics
Project to dynamics with explicit names
Project to custom data structure instances
Aggregates
Fold
Sum
Max
Groupings
Building upon the previously introduced concept of sub-DSL, building groups is another case where such a sub-DSL is employed:
Group airports by number of routes
Project to specific group values
The previous example illustrated a grouping by numbers without explicitly specifying a value, resulting in the projection of values to the airports themselves. However, it's worth noting that we can also project values specifically to the airport codes:
Loops
Again, there is a specific DSL for loops that, through a builder, lets the user chain reasonable combinations of the
Emit, Repeat, Until and Times operators:
Where can we go with three flights?
Unroll all the paths from SEA until we reach Atlanta or end up in a cycle
Trees
A tree in the TinkerPop sense is essentially just a wrapper for a Dictionary<TRoot, TTree> where TRoot can be
anything (scalars, entities) and TTree is, again, a Tree. So a tree in this sense can be seen as having many
root nodes where each of the root nodes maps to a subtree.
Gremlinq defines the non-generic ITree-marker interface and two derived classes:
Tree<TNode>is a tree where each node in the tree is of typeTNodeTree<TRoot, TTree>is a tree where the roots are of typeTRootand the subtrees are of typeTTree.
There are currently 3 overloads of the Tree-operator in Gremlinq:
-
Tree()will map to aTree<object>. This is the quickest way to collect the traversers and their paths into a tree structure. Note that although the signature only defines the nodes to be of typeobject, upon deserialization, Gremlinq will usually be able to recognize elements (vertices and edges) and create instances accordingly. -
Tree<TNode>()will deserialize to a tree where all the nodes are known to be of typeTNode. -
Precise type information of the tree nodes might be desired for later processing. Unfortunately, Gremlinq can't record all the types encountered on a path in its type system, so this information is not statically available. It can, however, be provided throgh call a
Tree-overload that provides a special builder-DSL:C#var typedTreeWithOf = await _g .V<Airport>() .OutE<Route>() .InV<Airport>() .Tree(_ => _ .Of<Airport>() .Of<Route>() .Of<Airport>()) .FirstAsync();It's also possible to map to members which will change the node types in the resulting tree