Cvoya.Graph.Age
1.0.0
Prefix Reserved
dotnet add package Cvoya.Graph.Age --version 1.0.0
NuGet\Install-Package Cvoya.Graph.Age -Version 1.0.0
<PackageReference Include="Cvoya.Graph.Age" Version="1.0.0" />
<PackageVersion Include="Cvoya.Graph.Age" Version="1.0.0" />
<PackageReference Include="Cvoya.Graph.Age" />
paket add Cvoya.Graph.Age --version 1.0.0
#r "nuget: Cvoya.Graph.Age, 1.0.0"
#:package Cvoya.Graph.Age@1.0.0
#addin nuget:?package=Cvoya.Graph.Age&version=1.0.0
#tool nuget:?package=Cvoya.Graph.Age&version=1.0.0
Downloadable open-source computer software from CVOYA. See the CVOYA software catalog.
Cvoya.Graph.Age
Cvoya.Graph.Age is the PostgreSQL + Apache AGE provider for CVOYA graph. It uses the shared
Cvoya.Graph.Cypher dialect SPI and exposes the same typed graph, LINQ, serialization, and
transaction APIs as the other providers.
Install
dotnet add package Cvoya.Graph.Age
The PostgreSQL database must have the Apache AGE extension installed. The provider loads AGE and
sets the required ag_catalog search path on every pooled connection.
Connect
using Cvoya.Graph;
using Cvoya.Graph.Age;
await using var store = new AgeGraphStore(
"Host=localhost;Port=5455;Username=postgres;Password=postgres;Database=postgres",
graphName: "my_graph");
await store.CreateGraphIfNotExistsAsync();
var graph = store.Graph;
var activeUsers = await graph.Nodes<User>()
.Where(user => user.IsActive)
.ToListAsync();
Production applications should supply the connection string directly or through
AGE_CONNECTION_STRING; the provider deliberately has no fallback credentials. AGE_GRAPH
selects the default graph name when the constructor does not specify one.
An application that already owns its connection pool can pass an AGE-enabled
NpgsqlDataSource. In that form, disposing the store does not dispose the caller's data source.
Supported surface
- typed and dynamic node/relationship CRUD;
- shared LINQ-to-Cypher translation and streaming queries;
- ACID transactions on a dedicated PostgreSQL connection;
- atomic endpoint–relationship–endpoint creation in one Npgsql batch round-trip, including nested complex-property subtrees and create-missing endpoint semantics;
- complex-property persistence and cascading cleanup;
- native logical AGE labels/types for model roots and catalog-vetted raw native data;
- write-scoped native label/type provisioning (ordinary reads perform no DDL);
- scalar-key grouped aggregation through AGE-native
WITHgrouping and aggregate functions; - correlated collection projections and relationship/complex-collection counts, lowered to AGE-supported grouped matches (see below); and
- agtype adaptation for vertices, edges, paths, maps, arrays, large integers, decimals, and ISO-8601 temporal values.
The provider declares full-text search, relationship predicates, and set operations. It does not
declare nested transactions, whole-entity ordering, or GraphCapability.ShortestPath. Shortest
path is deliberately unsupported for v1.0; the capability-gated TCK test skips for AGE, and #355
tracks any future implementation. Unsupported operations fail during translation or are skipped
only when a compatibility test is explicitly gated by the missing capability.
Native storage and commands
New Person nodes are stored in AGE's Person label table and new KNOWS relationships use the
native KNOWS type. Concrete raw AGE rows need no CVOYA discriminator, inheritance, entity-kind, or
CLR metadata to participate in typed, dynamic, and traversal queries. Provider-owned complex-value
nodes remain isolated through their relationship marker and reserved physical storage.
AGE catalog label names are limited to plain symbolic names. A mapped name outside that grammar (for example, a label containing a space) uses the provider-reserved physical-table representation so the escaped-identifier API remains usable; reserved provider names are rejected before writing.
Set updates and deletes first freeze distinct id(n) / id(r) values inside the active write
transaction, then mutate only those graphids. Endpoint-intent relationship creation likewise joins
selected or newly created endpoints by graphid and supports selected/selected, hybrid, all-new, and
explicit self-loop shapes. Caller-owned multi-statement commands are isolated behind a savepoint.
The public set-based mutation and endpoint-intent APIs use these native command paths without
exposing graphids.
Scalar-key grouped aggregation
The shared planner represents scalar-key GroupBy as a structured WITH stage: the non-aggregate
key establishes AGE's implicit grouping key, while Count, Sum, Average, Min, and Max
become aggregate columns consumed by the final projection. Key-only projections use WITH DISTINCT.
The existing AGE AST passes then lower labels, reserved aliases, and empty sums without inspecting or
rewriting rendered Cypher. The supported and rejected shapes therefore match the provider-neutral
GroupByAggregation contract; unsupported entity keys, element selectors, collection projections,
and filters inside a group fail before database execution with the shared actionable diagnostic.
Correlated collections and pattern counts
Apache AGE 1.7 cannot execute Cypher pattern comprehensions or CALL {} subqueries, and it parses
EXISTS { ... } / COUNT { ... } pattern subqueries but silently matches nothing. The provider
implements the corresponding shared capabilities through an AGE-local structured AST pass before
rendering. Correlated collection queries match the traversal once and group by its source node,
using collect, conditional count, ordinary aggregates, and ordered/nested WITH stages as
required; a filter inside one projection (group.Where(...).Average(...)) becomes conditional
aggregation so it cannot narrow the rows sibling projections aggregate over, and a segment filter
becomes a grouped row-count guard so owners with no qualifying rows produce no result row.
Independent relationship-degree and complex-collection counts — in projections, orderings, and
Where filters (including .Any(...) existence checks) — use sequential OPTIONAL MATCH stages so
owners with no match return zero and multiple counts in one projection do not multiply each other.
Two correlated shapes have no equivalent staging and fail at translation time instead: a filtered
nested grouping and multiple ordered collections in one projection. The resulting nested lists and
maps continue through the provider-neutral result wire model and shared materializer.
Full-text search
AGE full-text search discovers the graph's concrete vertex/edge tables from the AGE catalog, searches
native logical storage and externally managed labels, then correlates the combined distinct result
through transaction-local id(n) / id(r) values. The provider-reserved CvoyaNode and
CvoyaRelationship tables are excluded from external root discovery. Graphids remain provider
plumbing and are never substituted for public Id data; a domain property named Id participates
like any other included string property.
Registered labels search exactly their included string properties, and a registered type with no
included property matches nothing. Global searches use the all-string fallback only for genuinely
unregistered external labels; dynamic entities retain their public all-string-value contract.
Ordinary reads inline the PostgreSQL text-search predicate and require no managed function, index,
or DDL permission. The final native AGE provider owns no index artifacts, so
RecreateManagedIndexesAsync is a successful no-op: it performs no discovery, function creation,
index creation, drop, rebuild, or graph provisioning. Existing PostgreSQL artifacts are untouched.
Local AGE
From the repository root:
scripts/containers/start-age.sh
export AGE_CONNECTION_STRING='Host=localhost;Port=5455;Username=postgres;Password=postgres;Database=postgres'
dotnet test tests/Graph.Age.Tests/Graph.Age.Tests.csproj --configuration Debug
The implementation incorporates and substantially reworks Apache AGE provider ideas from @paule96's PR #66. In particular, the connection bootstrap, agtype conversion, SQL envelope, transaction, serialization, and CRUD designs informed this provider; the dialect-SPI implementation and security hardening were performed as part of issue #86.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Cvoya.Graph (>= 1.0.0)
- Cvoya.Graph.Cypher (>= 1.0.0)
- Cvoya.Graph.Serialization (>= 1.0.0)
- Konnektr.Npgsql.Age (>= 2.0.0)
- Npgsql (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 43 | 7/23/2026 |
| 1.0.0-alpha.20260717 | 50 | 7/17/2026 |