Skip to content

Querying

Farhad Nowzari edited this page Apr 8, 2024 · 3 revisions

This library introduces a new way to query which can be unfamiliar to C# developers, specially when ef core is used before. This library implements Eloquent similar to Laravel to perform queries with some tweaks to adopt the idea to C# and querying a graph database such as neo4j.

Match

Any queries in Neo4j must start with a match. The Match() method under a NodeSet<> is exactly that.

graphContext.Movies.Match();
//OR
graphContext.Movies.Match(x => x.Where(y => y.Id, "<some-id>"));

The code above will be immediately translated to it's equivalent cypher code.

MATCH(l0:Movie)
//OR
MATCH(l0:Movie WHERE l0.Id = '<some-id>')

But this still needs some work. You can run the above code as it is to get your result by calling ToListAsync or FirstOrDefaultAsync, or you can extend your query. Let's look a bit closer to the NodeQuery object which is returned by calling the Match method.

Eloquent

This filtering method is used on each match to filter out the matching nodes. The Eloquent is translated to a Where clause and is embedded inside the MATCH statement in cypher. Let's do a more advanced search on just the movie node.

graphContext.Movies
    .Match(x => 
        x
            .Where(y => y.Year, ComparisonOperator.LessThan, 2000)
            .WhereNotIn(y => y.Id, [20, 16, 17])
    )

The above code will be translated to

MATCH (l0:Movie WHERE l0.Year < 2000 AND NOT l0.Id IN [20, 16, 17])

Now this filtering can be also applied to a target node which we are matching the movie with. For example:

graphContext.Movies
    .Match(
        x => x
            .Where(y => y.Year, ComparisonOperator.LessThan, 2000)
            .WhereNotIn(y => y.Id, [20, 16, 17])
    )
    .WithRelation(x => x.Audiences, 
        x => x
                .Where(y => y.Age, ComparisonOperator.GreaterThan, 18)
    )

Will be translated to

MATCH(l0:Movie WHERE l0.Year < 2000 AND NOT l0.Id IN [20, 16, 17])<-[r1:WATCHES]-(l1:Person WHERE l1.Age > 18)

The query above means, give me all Movies, created before year 2000 which their ids is not in the given list, also the movie must be watched by Audiences older than 18 years old.

Relations

Before you execute your query, you may still need to get the nodes which have a specific path. In this case, the NodeQuery will provide the WithRelation method.

WithRelation

This method can be used without and Eloquent or you can filter out the nodes which are being reached out through this relation. When the WithRelation method is called, it will look into the NodeConfiguration, to get the Label for this relation and if it is not defined, it will throw an exception.

Executing queries

The NodeQuery at the moment only support a limited number of querying actions and all the queries will return the values for the node type the match is started with.
For example, when the query looks like the following, it will match all the movies with the given path and will return the count of the movies.

MATCH(l0:Movie WHERE l0.Year < 2000 AND NOT l0.Id IN [20, 16, 17])<-[r1:WATCHES]-(l1:Person WHERE l1.Age > 18)

AnyAsync/Any

This method will check against the database if the returning query result is empty or not. Please note that the query will run with executing the query and the calculated result will return from database.

CountAsync/Count

This method as explained in the above example will return the count of distincted found nodes.

FirstOrDefaultAsync/FirstOrDefault

This method will return the first found node based on the given path and will map it to the type of nodes in the NodeSet<TNode>.

ToListAsync/ToList

This method will return a list of found nodes based on the given path and will map it to the type of nodes in the NodeSet<TNode>.

Executing commands

There are limited amount commands which can be executed on the node(s) which matched with the expected path/query.

UpdateAsync/Update

This method will except an UpdateSet builder. An UpdateSet will tell this method what you want to update. For example you can update:

  • Specific properties which are defined in the C# object
  • Custom properties which doesn't exist in the C# object
  • The complete Node by passing an instance of the node with the newest values you want to set in the database.

UpdateAndReturnAsync/UpdateAndReturn

This method will perform an update with the same steps of UpdateAsync, but it will return the updated nodes from the database.

ConnectAsync/Connect

In case you have two nodes already and now you want to make a relation between them. Let's say that you have created a Movie before and also imported some people and now want to connect some of those people to the created movie. For example

graphContext
    .Movies
    .Match(x => x.Where(y => y.Title, "The Matrix"))
    .ConnectAsync(x => x.Actors, x => x.Where(y => y.Name, "Keanu Reeves"));

The code above will be translated to

MATCH(l0:Movie WHERE l0.Title = 'The Matrix')
MATCH(l1:Person WHERE l1.Name = 'Keanu Reeves')
CREATE (l0)<-[:ACTED_IN]-(l1)

DisconnectAsync/Disconnect

This method will remove the relation between your matched node the query is started with and the last WithRelation. For example

graphContext
    .Movies
    .Match(x => x.Where(y => y.Title, "The Matrix"))
    .WithRelation(x => x.Director)
    .WithRelation(x => x.Actors)
    .DisconnectAsync();

Now the code above will check if there is any movie with name The Matrix and has a Director then will remove all of the connections it has to any actors.

It will be translated to this:

MATCH(l0:Movie WHERE l0.Title = 'The Matrix')
MATCH (l0)<-[r1:DIRECTED]-(l1:Person)
MATCH (l0)<-[r2:ACTED_IN]-(l2:Person)
DELETE r2

ArchiveAsync/Archive

Thi method can be used to SoftDelete a queried node. It will set ArchivedAt to timestamp() in neo4j. Now other services like kafka connectors can decide to remove the archived nodes from other services, such as Elasticsearch. To hard delete the node, later a process can run for example to HardDelete nodes which are deleted x days ago.

Usage:

await graphContext
    .Movies
    .Match(x => x.Where(y => y.Id, "<some-id>"))
    .ArchiveAsync()

This will be translated to:

MATCH(l0:Movie WHERE l0.Id = '<some-id>') SET l0.ArchivedAt=timestamp() return l0

As you see, it will return all archived nodes.

Anonymous feature

This feature lets you query nodes and their relations without having the objects available explicitly in C#. For example after v1.1.0 it is possible to:

graphContext.Anonymous("Movie")
    .Match(x => x.Where("Id", "<some-id>"))
    .FirstOrDefault();

As you see, you need to pass the property names and the label as string. The Anonymous method also accepts a NodeConfiguration which you can define on the fly based on your generic configuration.

⚠️ IMPORTANT: Please check this cypher injection warning out.