Skip to content

Gremlinq mascot

Introduction

ExRam.Gremlinq is a .NET object-graph-mapper (OGM) for Apache TinkerPop™ enabled databases. An OGM on the .NET platform is for the realm of graph-databases what, among others, Entity Framework or NHibernate (generally called object-relational-mappers, or ORMs) are for relational databases.

TinkerPop-enabled graph databases are queried by a graph query language called Gremlin. ExRam.Gremlinq enables .NET developers to create and issue this kind of queries in a strongly-typed fashion by using the powerful Gremlinq-DSL abstraction over their own model of domain entity classes (usually called POCOs). It will serialize the query to valid Gremlin for execution on the server and deserialize the results to the correct .NET types.

Features

Type safety

ExRam.Gremlinq queries carry and pass on full information about the query result type and its role (vertex, edge, scalar etc.). This minimizes the possibility of writing and executing invalid queries, speeding up development.

Example

For a simple example, let's assume the domain of a simple pet-shop with classes Cat and Dog that both inherit from an abstract base class Animal. A plain Gremlin-query asking for all Cats in the database named 'Poppy' would look like this:

Groovy
g.V().hasLabel('Cat').has('Name', 'Poppy')

ExRam.Gremlinq allows for issuing this query using type-safe C# language constructs over the Cat-class:

C#
var cats = _g.V<Cat>().Where(cat => cat.Name == "Poppy").ToArrayAsync()

At every step in the query, ExRam.Gremlinq is aware that it is dealing with vertices of type Cat and, upon retrieval of results from the server, deseriailze these into instances of type Cat.

Graph model

ExRam.Gremlinq efficiently manages the type hierarchy of Plain Old CLR Objects (POCOs), offering extensive customization options. Users can tailor member names, adjust type name to label mappings, and easily ignore specific type members. This flexibility ensures seamless integration with database schemas and simplifies data model management.

Powerful DSL

The type system of ExRam.Gremlinq itself, represented as various fluent interfaces that carry result type information, enables developers to rapidly write correct queries.

Example

Again assuming the above domain, enriched with SoldTo and Customer classes that represent edges labelled SoldTo pointing from Animals to Customers, we can query for the customer that bought a cat named 'Poppy':

C#
var cat = await g
    .V<Cat>()
    .Where(cat => cat.Name == "Poppy")
    .Out<SoldTo>()
    .OfType<Customer>()
    .FirstOfDefaultAsync()

The ExRam.Gremlinq DSL will only allow fluently using the Out<...> method if it knows that it's currently on a vertex. This avoids accidentally trying to walk edges from e.g. edges or scalar values. The Gremlin language by itself does not prohibit writing such queries, so that bugs could only be detected during integration tests or, even worse, in production.

C# expression recognition

ExRam.Gremlinq is capable of recognizing a wide range of C#-expressions and generate valid Gremlin. This enables developers to focus on the business logic in the language they're comfortable with, instead of dealing directly with the intricacies of the Gremlin query language.

Simple example

Querying for all the Cats whose name starts with the letter "B". Switch the tab to see the according translation into Gremlin.

C#
var cats = await _g
    .V<Cat>()
    .Where(x => x.Name.Value.StartsWith("B"))
    .ToArrayAsync();
Groovy
g.V().hasLabel('Cat').has('Name', startingWith('B'))
More complex example

Here's a more complex example showing the power of ExRam.Gremlinq in recognizing expressions that contain an ordinary LINQ method as well as a reference to a step labels created earlier in the query.

C#
var catsWithSpecificAges = await _g
    .Inject(3, 6, 12)
    .Fold()
    .As((__, ages) => __
        .V<Cat>()
        .Where(cat => ages.Value.Contains(cat.Age)))
    .ToArrayAsync()
Groovy
g.inject(3, 6, 9).fold().as('ages').V().hasLabel('Cat').where(within('ages')).by('Age').by()
Inherent thread safety

In ExRam.Gremlinq, the design prioritizes thread safety and purity throughout query execution. All queries and environment structures are immutable, meaning they cannot be modified once created. This inherent immutability ensures that multiple threads can safely access and operate on these structures concurrently without the risk of unintended changes or data corruption. Additionally, leading up to actual query execution, methods on the Gremlinq interface are considered pure, devoid of side effects. This purity guarantees that invoking these methods won't alter the state of the system, further reinforcing the reliability and predictability of ExRam.Gremlinq in multi-threaded environments.

Customization

ExRam.Gremlinq excels in customization, providing developers control over query serialization, execution, and result deserialization. Custom scalar type registration ensures accurate data integration with native graph types, while query execution interception allows for fine-tuning and optimization. This flexibility enables seamless adaptation to project needs, enhancing efficiency in graph database operations.

Support for Bytecode and Groovy queries

ExRam.Gremlinq supports both Bytecode queries (for AWS Neptune, etc.) and Groovy scripts (the sole format supported by Azure CosmosDB). Developers can effortlessly utilize either format without worrying about compatibility, ensuring smooth operation across different database environments.

ASP.NET integration

Separate NuGet packages seamlessly allow registering ExRam.Gremlinq services with ASP.NET so they are availably via dependency injection in ASP.NET middleware and controllers.

Provider support

ExRam.Gremlinq comes with out-of-the-box support for

  • AWS Neptune
  • Azure Cosmos Db
  • JanusGraph
  • TinkerPop Gremlin Server

Other providers may just work with the generic Gremlin Server-package, be provided by the community or be part of the commercial extension package.