ENTITY FRAMEWORK CORE × NEO4J

A graph database.
No second ORM.

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

Native property graphEF entities → graph
PersonCLR entity MovieGraph node CompanyGraph node ↗ ACTED_IN WORKS_FOR
Hover or tap a node to inspect
Entities become nodesRelationships are first-classOne EF model
01
State ownerEF Core tracks every entity
03
Query surfacesLINQ · Fluent Cypher · native Cypher
03
Authoring pathsCode · ontology · database first
01 / The thesis

Build only the missing semantics.

The hard parts were already solved, just in two different places. GraphBridge connects them instead of rebuilding either.

Reused · .NET

EF Core already knows

Entities and keys, change detection, snapshots, tracked relationships, identity resolution, LINQ compilation and the provider infrastructure.

Reused · graph database

Neo4j already knows

Native nodes and directed relationships, Cypher planning, indexes and constraints, transactions, and Bolt transport through its official .NET driver.

Added · thin binding

GraphBridge adds

A validated graph mapping, LINQ and Fluent Cypher translation to parameterized Cypher, EF-compatible materialization, and native node and relationship writes.

Existing EF machinery + the official Neo4j driver + a small semantic binding = GraphBridge
Design rule: no second ORM, change tracker or Bolt driver. EF Core stays the only state owner, and anything GraphBridge cannot translate faithfully is refused with a clear message, never evaluated on the client.
02 / The defining idea

An EF join entity
becomes a real graph relationship.

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.

Relational mental model
PersonId · NameJoin rowPersonIdMovieIdMovieId · TitleJoin entity stored as a table row
GraphBridge storage lowering
PersonNodeMovieNodeLIKESTracked association → native directed edge
SHIPS TODAY · ToGraphRelationship

Same relationship tracking. Native storage.

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.

  • Associations: one relationship per pair, with payload properties.
  • Identified relationships: their own key, so parallel relationships are allowed.
  • Writes run in one transaction; deletes are explicit, never a silent DETACH DELETE.
C# · mapping and use
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)
Cypher · LINQ over LIKES, as generated (it checks one relationship per pair)
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` DESC
03 / Four storage forms

Every EF concept has
a native graph shape.

You choose the shape explicitly in the model. Keys are application keys backed by reviewed uniqueness constraints; GraphBridge never creates schema on its own.

Entity

Node

(:Person {id, name})

ToGraphNode("Person"): a label, an application key and mapped properties.

One-to-many reference

Native reference

(:Movie)-[:HAS_REVIEW]->(:Review)

ToGraphReference("HAS_REVIEW"): a parent-to-child relationship, required or optional, with no foreign-key property on the child.

Many-to-many join entity

Association

(:Person)-[:LIKES {stars}]->(:Movie)

ToGraphRelationship("LIKES", …): one relationship per pair, with payload and skip navigations.

Relationship with its own key

Identified relationship

(:Person)-[:ACTED_IN {id, role}]->(:Movie)

ToGraphIdentifiedRelationship("ACTED_IN", …): parallel relationships, such as two roles in one movie.

Reads validate the graph: a child read through a reference proves it has exactly one valid parent, and an identified relationship proves its key is unique. Corrupt data fails before EF shapes an entity.
04 / Three query surfaces

EF where it fits.
Graph-native where it matters.

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.

Tracked entities · C# semantics

Ordinary EF Core / LINQ

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.

✓ Tracked entities and DTOs
✓ ToQueryString shows the Cypher
✓ Async only
✓ Coded refusals
C# · LINQ
var name = "Keanu Reeves";
var people = await db.People
    .Where(p => p.Name == name)
    .OrderBy(p => p.Name)
    .Take(5)
    .ToListAsync();
Graph patterns · native semantics

Typed patterns / Fluent Cypher

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.

✓ C# lambdas, checked
✓ IN, STARTS WITH, EXISTS
✓ min, max, sum, avg, collect
✓ Untracked DTOs
C# · Fluent Cypher
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();
Anything else · exactly what you write

Reviewed text / Native Cypher

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.

✓ Parameters, never concatenation
✓ Your projector, your DTO
✓ Shared transactions
✓ Bounded paths API
C# · native Cypher
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);
No client-side fallback: a shape that cannot be translated faithfully fails while the query compiles, with the reason and an alternative. Nothing silently reads a whole graph into memory.
05 / Authoring paths

Start with what you have.
End at the same graph.

Three ways to author the model, one finalized EF model at runtime. Every path ends in ordinary, reviewable EF configuration.

Sample 01 · Blogging

C# entities / Code-first

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.

✓ C# types and DbContext
✓ Optional source generator
✓ Validated mapping
✓ Storage manifest
C# · EF configuration
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");
    });
}
Sample 02 · Organization

Turtle vocabulary / Ontology-first

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.

✓ Generated C# and EF mapping
✓ IRI constants
✓ Schema proposals
✓ SHACL validation
Turtle + policy · inputs
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, outgoing
Sample 03 · Movies

Existing graph / Database-first

Inspect 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.

✓ Bounded inspection snapshot
✓ Reviewed key policy
✓ Generated EF model
✓ Scaffold review report
Shell · GraphBridge.Tool
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 generated
One authoring authorityHandwritten or generated EF configuration
→
Finalized EF modelSingle runtime identity and state model
→
Graph bindingValidated storage projection
06 / The architecture

A genuine EF Core provider.
An intentionally thin graph binding.

The 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.

Your applicationPOCOs · DbContext · DbSet<T> · LINQ · SaveChangesAsync · db.Cypher()

The EF Core programming model, plus an explicit graph-native lane

↓
Reused .NET infrastructureEntity Framework Core 10

IModel · change tracker · join entities · identity resolution · query compilation

↓
◆ GraphBridge.EntityFrameworkCoreThe runtime package
Validated graph binding

Nodes, references, associations, identified relationships · keys · converters · direction · capability checks

Read: LINQ and Fluent Cypher → Cypher

Expression translation · typed query plan · EF shaper, tracking and graph validation

Write: tracked entries → graph

Node and relationship mutations · concurrency tokens · one transaction per save

Schema inspection · migration plans · native Cypher · bounded paths · search · tracingParameters always; identifiers only from validated metadata.
↓
Official transport · no homemade Bolt clientNeo4j.Driver 6 → Neo4j

Connection pool · sessions · cursors · transactions · native graph storage. GraphBridge adds no automatic retries.

Optional tooling (not needed at runtime)
GraphBridge.Tooling Doctor, Verify, Query Lab report
GraphBridge.Generators Mapping source generator
GraphBridge.Tool · template · VS Code Ontology, inspection, scaffolding; project template; Query Lab viewer
How a request flows
01 / EF CORE

LINQ expression tree

EF preprocesses the query and hands the shape to the provider.

02 / GRAPHBRIDGE

Typed Cypher plan

Translate predicates and projections; refuse what cannot be translated faithfully.

03 / NEO4J

Server execution

The driver sends one parameterized statement; Neo4j plans and runs it.

04 / EF CORE

Validate, shape, track

Graph shape is checked, then EF materializes and tracks entities.

No client evaluation. Unsupported shapes fail with a GBQ code and an alternative.
01 / EF CORE

Tracked entries

Added, modified and deleted entities with original and current values.

02 / GRAPHBRIDGE

Graph mutation plan

Classify nodes and relationships, order them, check row versions.

03 / NEO4J

One transaction

All node and relationship commands commit together or not at all.

04 / EF CORE

Accept or report

Entries are accepted, or a concurrency or update exception explains what changed.

Deletion is restrictive: a node with relationships is not deleted, never a silent DETACH DELETE.
01 / NAVIGATION

Change the collection

hugo.LikedMovies.Add(wick) records an association.

02 / EF CORE

Track the join entity

EF's own join-entity machinery represents the change.

03 / GRAPHBRIDGE

Lower to a relationship

Resolve endpoints, type, direction and payload from the mapping.

04 / NEO4J

Native LIKES

One real directed relationship, read back and validated as one per pair.

Reading it back from a fresh context returns the same tracked association.
08 / Start here

Run it.
Then read the contract.

GraphBridge is a developer preview: every supported behavior is tested against a real Neo4j database, and every limit is written down.

What we refuse to fake

Explicit non-goals

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.

What it feels like

The 90-second demo

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.

For the .NET and EF Core community

Neo4j doesn't need
another .NET ORM.

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.