Last week, I wrote about the evolution of the CVOYA Graph. After a lot of progress this week, I am graduating the library to v1.0.
CVOYA Graph is a type-safe .NET library for modeling and querying graphs with LINQ. It offers three graph providers: in-memory, Neo4j, and Apache AGE. Version 1.0 establishes a stable, provider-tested foundation with explicit capabilities and a reusable compatibility test kit.
This is intentionally a breaking release from GraphModel and the prerelease line. If you are upgrading an existing application, please read the migration guide.
The packages are now available on NuGet, with documentation and source on GitHub.
An important note: Since last week, I fixed many bugs and completed the support for AGE 1.7 as a provider. Along the way, I made a big change to CVOYA Graph’s .NET data model. The foundational IEntity interface no longer defines a mandatory Id property. That has major consequences to the API. A CVOYA Graph-defined ID can no longer be used to identify nodes and relationships in Get, Update, and Delete operations. You have to use LINQ expressions to identify a node or relationship. However, creating a relationship is easier now through the introduction of an atomic CreateAsync<TSource, TRelationship, TTarget>() operation. You can still model your domain types to have an ID property and even identify it as a primary key. It’s just that CVOYA Graph doesn’t dictate such an identifier any more.
using Cvoya.Graph;
using Cvoya.Graph.Neo4j;
// Instantiate your provider
await using var store = new Neo4jGraphStore(
"bolt://localhost:7687",
"neo4j",
"password",
databaseName: "myapp");
// Get the graph abstraction
var graph = store.Graph;
// All subsequent operations are against the IGraph interface, not the provider
// Create two nodes
var alice = new Person
{
Tenant = "northwest",
Email = "alice@example.com",
Name = "Alice",
Age = 30,
};
var bob = new Person
{
Tenant = "northwest",
Email = "bob@example.com",
Name = "Bob",
Age = 28,
};
// Create a relationship
await graph.CreateAsync(
alice,
new Knows { Since = DateTime.UtcNow },
bob);
// Query the nodes
var aliceSelection = graph.Nodes<Person>()
.Where(person => person.Tenant == "northwest" &&
person.Email == "alice@example.com");
// Update
await aliceSelection.UpdateAsync(setters => setters
.SetProperty(person => person.Age, person => person.Age + 1)
.SetProperty(person => person.Name, "Alice Smith"));
// Get relationsihps
var connections = await aliceSelection
.PathSegments<Person, Knows, Person>()
.ToListAsync();
foreach (var segment in connections)
{
Console.WriteLine(
$"{segment.StartNode.Name} --{segment.Relationship.Type}--> " +
$"{segment.EndNode.Name} ({segment.Direction})");
}The docs contain many more examples.
Here is the capability matrix for the three providers included in v1.0:
| Capability | Neo4j | in-memory | AGE |
|---|---|---|---|
FullTextSearch | pass | pass | pass |
Transactions | pass | pass | pass |
ComplexPropertyCascade | pass | pass | pass |
CallSubqueries | pass | pass | pass |
PatternSizeProjection | pass | pass | pass |
MultiLabelMatch | pass | pass | pass |
LabelFiltering | pass | pass | pass |
OrderByEntity | pass | skip | skip |
OptionalTraversal | pass | pass | pass |
GroupByAggregation | pass | pass | pass |
NestedTransactions | — | — | — |
RelationshipPredicates | pass | pass | pass |
ShortestPath | pass | pass | skip (issue 355) |
SetOperations | pass | pass | pass |
NullElementsInSimpleCollections | pass | pass | pass |
I chose not to implement shortest path for AGE at this stage since the 1.8.0 release is just around the corner and comes with native support for the feature.
Enjoy!
Last year, I released the "Graph Model" library for .NET as an open source project. It…
Today I'm pushing Spring Voyage out of the harbor. You can track its journey on…
In my last post, I wrote that "the typing of code was parallelized and delegated.…
In February, I wrote about the small team I'd stood up instead of hiring humans:…
Assembling a dream team without a single hire I've been making great progress on CVOYA's…
As 2025 is now behind us, I wanted to share a few reflections from my…