EF Core already knows
Entities and keys, change detection, snapshots, tracked relationships, identity resolution, LINQ compilation and the provider infrastructure.
EF Core already understands your objects and their changes. Neo4j already understands your graph. GraphBridge is a real EF Core provider that connects them at their natural boundary.
● Open source · EF Core 10 · official Neo4j .NET driver · developer preview
The hard parts were already solved, just in two different places. GraphBridge connects them instead of rebuilding either.
Entities and keys, change detection, snapshots, tracked relationships, identity resolution, LINQ compilation and the provider infrastructure.
Native nodes and directed relationships, Cypher planning, indexes and constraints, transactions, and Bolt transport through its official .NET driver.
A validated graph mapping, LINQ and Fluent Cypher translation to parameterized Cypher, EF-compatible materialization, and native node and relationship writes.
EF already tracks many-to-many associations as join entities. GraphBridge reuses that lifecycle but stores the association as one native Neo4j relationship, not a join table or an artificial join node.
Map the join entity once. Adding to a navigation creates one directed relationship with its payload; reads check that each pair has exactly one relationship before EF shapes the entity.
join.HasKey(l => new { l.PersonId, l.MovieId });
join.ToGraphRelationship("LIKES",
[nameof(Like.PersonId)], [nameof(Like.MovieId)]);
hugo.LikedMovies.Add(wick); // EF creates the Like join entity
await db.SaveChangesAsync(); // GraphBridge writes (:Person)-[:LIKES]->(:Movie)MATCH (s:`Person`)-[r:`LIKES`]->(t:`Movie`)
CALL (s, t) {
MATCH (s)-[candidate:`LIKES`]->(t)
WITH candidate LIMIT 2
RETURN count(*) AS `__gb_association_count`
}
WITH s, r, t, `__gb_association_count`
WHERE coalesce(r.`stars` >= $p0, false)
RETURN s.`id` AS `c0`, t.`id` AS `c1`, r.`stars` AS `c2`, `__gb_association_count` AS `__gb_association_count`
ORDER BY r.`stars` IS NULL ASC, r.`stars` DESCYou choose the shape explicitly in the model. Keys are application keys backed by reviewed uniqueness constraints; GraphBridge never creates schema on its own.
ToGraphNode("Person"): a label, an application key and mapped properties.
ToGraphReference("HAS_REVIEW"): a parent-to-child relationship, required or optional, with no foreign-key property on the child.
ToGraphRelationship("LIKES", …): one relationship per pair, with payload and skip navigations.
ToGraphIdentifiedRelationship("ACTED_IN", …): parallel relationships, such as two roles in one movie.
All three share the context's driver and transactions. Start at the highest level that can express the question, and step down only when you need to; every refusal names its alternative.
Filters, ordering, paging, projections, aggregates and Include translate to one parameterized Cypher statement. Null comparisons keep C# meaning. Unsupported shapes are refused with a code such as GBQ101, never run on the client.
var name = "Keanu Reeves";
var people = await db.People
.Where(p => p.Name == name)
.OrderBy(p => p.Name)
.Take(5)
.ToListAsync();MATCH, OPTIONAL MATCH, WITH, counts, aggregates, EXISTS and bounded paths over your EF mappings. C# lambdas are accepted only where they return the same rows as native Cypher; explicit methods state native meaning.
var actor = Graph.Node<Person>();
var movie = Graph.Node<Movie>();
var coactor = Graph.Node<Person>();
var names = await db.Cypher()
.Match(actor.Out<Credit>(movie).In<Credit>(coactor))
.Where(() => actor.Value.Name == name
&& coactor.Value.Id != actor.Value.Id)
.ReturnDistinct(() => coactor.Value.Name)
.OrderBy(() => coactor.Value.Name)
.Limit(100)
.ToListAsync();Parameterized Cypher with an explicit projector, in the same transaction as EF. Use it for graph algorithms, procedures and shapes the typed surfaces do not cover.
var rows = await db.Database.QueryCypherAsync(
"MATCH (p:Person)-[:ACTED_IN]->(m:Movie {title: $title}) " +
"RETURN p.name AS name ORDER BY name",
new Dictionary<string, object?> { ["title"] = "Cloud Atlas" },
r => r["name"].As<string>(),
maxRows: 100);Three ways to author the model, one finalized EF model at runtime. Every path ends in ordinary, reviewable EF configuration.
Write ordinary entities and EF configuration, or annotate them and let the mapping source generator write it, with build-time diagnostics. GraphBridge validates the finalized model and reports its storage manifest.
protected override void OnModelCreating(ModelBuilder model)
{
model.Entity<Person>(person =>
{
person.ToGraphNode("Person");
person.Property(p => p.Id).ValueGeneratedNever()
.HasGraphPropertyName("id");
person.Property(p => p.Name).HasGraphPropertyName("name");
});
}Start from a Turtle vocabulary and a small YAML persistence policy that decides keys, labels, directions and conversions. GraphBridge.Tool generates C#, EF mappings and schema proposals; SHACL validation runs separately. There is no OWL reasoning.
ex:Person a owl:Class .
ex:Organization a owl:Class .
ex:worksFor a owl:ObjectProperty ;
rdfs:domain ex:Person ; rdfs:range ex:Organization .
# graphbridge.policy.yaml
# ex:Person: storage node, keyProperties [Id]
# ex:worksFor: relationship, outgoingInspect labels, relationship types, constraints and sampled properties, review the keys and mapping policy, then scaffold an EF model. Sampling never invents a durable key or a cardinality.
dotnet run --project src/GraphBridge.Tool -- inspect \
--labels Person,Movie --relationship-types ACTED_IN \
--output movies.snapshot.json
dotnet run --project src/GraphBridge.Tool -- scaffold \
--snapshot movies.snapshot.json \
--policy graphbridge.reviewed.policy.json --output generatedThe runtime is one package on top of EF Core and the official driver. Everything else is optional tooling that reads the same finalized EF model.
The EF Core programming model, plus an explicit graph-native lane
IModel · change tracker · join entities · identity resolution · query compilation
Nodes, references, associations, identified relationships · keys · converters · direction · capability checks
Expression translation · typed query plan · EF shaper, tracking and graph validation
Node and relationship mutations · concurrency tokens · one transaction per save
Connection pool · sessions · cursors · transactions · native graph storage. GraphBridge adds no automatic retries.
EF preprocesses the query and hands the shape to the provider.
Translate predicates and projections; refuse what cannot be translated faithfully.
The driver sends one parameterized statement; Neo4j plans and runs it.
Graph shape is checked, then EF materializes and tracks entities.
Added, modified and deleted entities with original and current values.
Classify nodes and relationships, order them, check row versions.
All node and relationship commands commit together or not at all.
Entries are accepted, or a concurrency or update exception explains what changed.
hugo.LikedMovies.Add(wick) records an association.
EF's own join-entity machinery represents the change.
Resolve endpoints, type, direction and payload from the mapping.
One real directed relationship, read back and validated as one per pair.
Optional tools built on the same finalized EF model. None of them is needed at runtime, and the diagnostic tools only read: they never write to your database.
Checks the model and registered queries offline, and the database schema read-only, with stable diagnosis codes.
Compiles every registered query, plans it with EXPLAIN and compares results with independent controls on a disposable database.
C# source, generated Cypher and evidence side by side, as a static HTML report or in the VS Code extension.
Writes EF graph configuration from attributes at build time, with diagnostics for unsupported mappings.
Ontology compilation, SHACL validation, graph inspection and reviewed database-first scaffolding.
A dotnet new project template, plus migration listing, preview and status commands.
GraphBridge is a developer preview: every supported behavior is tested against a real Neo4j database, and every limit is written down.
Install, configure UseNeo4j, map an entity, save it and see the Cypher.
31 runnable lessons with scripts, from a first context to Fluent Cypher and the tools.
Start lesson 00 →Code-first, ontology-first and database-first applications, design-time commands and a Fluent Cypher gallery.
Browse samples →What is refused and why, the pinned stack and the supported Neo4j versions.
Read the limits →No promise of full relational EF compatibility, no client-side evaluation fallback, no OWL reasoning, no Neo4j elementId as an EF key, no silent DETACH DELETE and no automatic schema creation.
Configure UseNeo4j, insert a Person and a Movie through EF, add a like through a tracked navigation, and see the native LIKES relationship in Neo4j Browser. Query it from a fresh context, with correct tracking and rollback.
EF Core already knows how to track entities, resolve identities and represent associations. Neo4j already knows how to store and traverse graphs. GraphBridge connects the two at the smallest meaningful seam: it lowers EF changes into native nodes and relationships, and translates LINQ and typed graph patterns into parameterized Cypher.
You keep familiar EF patterns, gain an explicit graph-native lane, and can author the model from code, an ontology or an existing database. One validated binding keeps those paths consistent.
The engineering achievement isn't the infrastructure we built. It's how much infrastructure we didn't have to build.