Open source / .NET 8+ / one NuGet
Relatude.DB is an object-oriented graph database for .NET. Graph relations, BM25 full-text, vector search, facets, file handling and an admin UI — one in-process engine, no migrations, no ORM mapping layer.
↓ or scroll to continue
The problem
A normal web backend today needs structured queries, text search, semantic search, caching and media. Nothing on this list is your product — but all of it is yours to run.
The shift
Relatude.DB runs in-process inside your ASP.NET Core app. The graph is held in memory and persisted to a binary append-only log, so there is no network hop, no serialization boundary and no connection pool between your code and your data.
Every call is its own ACID transaction, durably logged. No change tracking to reason about, no SaveChanges() at the end of a request.
Datamodel
[Node(TextIndex = BoolValue.True)] // whole node is searchable public class Product { [PublicIdProperty] public Guid Id { get; set; } [StringProperty(Indexed = true)] public string Name { get; set; } = ""; public string Description { get; set; } = ""; // no attribute needed [DoubleProperty(Indexed = true)] public double Price { get; set; } [BooleanProperty(Indexed = true)] public bool InStock { get; set; } [StringArrayProperty(Indexed = true)] public string[] Tags { get; set; } = []; [ReferenceProperty(Indexed = true)] public Reference<Brand> Brand { get; set; } = new(); }
that is the entire schema definition
Indexed = true is the one knob you need on day one — it makes a property filterable and facetable.[Node] and implementing classes become queryable subtypes. Multiple inheritance included.Maps automatically
Relations
ParentId.public class Tree : OneToMany<Page, Page> { // a page tree, in 3 lines public class Parent : One { } public class Children : Many { } } // on Page: public Tree.Parent Parent { get; set; } = new(); public Tree.Children Children { get; set; } = new(); // both sides stay consistent, automatically page.Children.Count(); page.Parent.Get().Title; db.SetRelation<Page>(parent, x => x.Children, child);
enumerates lazily, walks either direction
There are exactly five relation shapes, so there is nothing to get wrong. Declare the shape, and the engine maintains both directions, ordering, and index consistency.
OneOne<T>1↔1, same type, symmetricOneToOne<A,B>1↔1OneToMany<A,B>1↔NManyMany<T>N↔N, same typeManyToMany<A,B>N↔NOrdered relations, relation facets, graph traversal and ShortestPath come with it.
Queries
var p = db.Get<Product>(id); var page = db.Query<Product>() .Where(p => p.InStock && p.Price < 500) .OrderBy(p => p.Price) .Page(0, 20) .Execute(); // follow relations eagerly, in one query — no N+1 var pages = db.Query<Page>().Include(p => p.Children).Execute(); // query a base type, every subtype comes back correctly typed db.Query<IContent>().Where(c => c.Title.StartsWith("Hello")).Execute(); // mutations: each call is its own ACID transaction db.Insert(p); db.Update(p); db.Delete(p);
async twins exist: ExecuteAsync, CountAsync, FirstOrDefaultAsync
db.CreateTransaction() groups any number of changes into one atomic commit.Search
// BM25 keyword search db.Query<Product>().WhereSearch("wool jacket").Execute(); // hybrid: blend keyword relevance with vector similarity db.Query<Product>() .WhereSearch("wool jacket", semanticRatio: 0.5) .Execute();
No second cluster to provision, no reindex job to schedule, no embedding written twice. The text index and the vector index live beside the data they describe.
semanticRatio as the single dial between keyword and meaning.Facets
var res = db.Query<Product>() .WhereSearch("jacket") .Facets() .AddValueFacet(p => p.Brand) .AddValueFacet(p => p.Tags) .AddRangeFacet(p => p.Price) .Execute(); foreach (var f in res.Facets) foreach (var v in f.Values) Console.WriteLine($"{f.DisplayName}: {v.DisplayName} ({v.Count})");
value facets, range facets, and facets over relations
Counts adapt to the active result set and drill sideways correctly — selecting a brand does not zero out the other brand counts. On a million nodes this answers in well under a millisecond warm, off the set-operation cache.
Multiple engines in one
Optional plugin packages swap in Lucene or SQLite indexes, Azure Blob storage and Azure OpenAI embeddings — without touching your model or your queries.
Setup
Any .NET 8+ project type. No external services, no container to start.
dotnet add package Relatude.DB.Server
Registers the store, starts it, and mounts the admin UI.
var builder = WebApplication.CreateBuilder(args); builder.AddRelatudeDB(); var app = builder.Build(); app.MapGet("/", (RelatudeDBContext ctx) => $"{ctx.Database.Count()} objects"); app.StartRelatudeDB(); app.MapRelatudeDBAdmin(); app.Run();
This folder is the database, that namespace is the schema. Everything else has a sensible default — and the admin UI writes this file for you.
{ "Name": "MyDatabase", "IOSettings": [{ "Path": "relatude.db", "IOType": "LocalDisk" }], "DatamodelSources": [{ "Namespace": "MyApp.Models", "Type": "AssemblyNameReference" }] }
Run the project and open /relatude.db. The admin UI is already there, your model is already indexed, and your data is already searchable.
Compared to the alternatives
| Capability | Relatude.DB | SQL + EF Core | Assembled stack SQL + search + vector + cache + blob |
Document DB |
|---|---|---|---|---|
| Objects without a mapping layer | ||||
| Schema changes without migrations | ||||
| Two-way relations, navigable both ways | ||||
| BM25 full-text search | ||||
| Vector / semantic search | ||||
| Faceted search with drill-sideways counts | ||||
| File store with image conversion | ||||
| Cultures, revisions, per-property access | ||||
| Admin UI over your own model | ||||
| Queries without a network hop | ||||
| Systems to deploy & monitor | 1 | 2 | 5+ | 2 |
| Query languages to know | 1 | 2 | 5 | 2 |
Every one of these alternatives is a good system. The argument is not that they are slow — it is that a typical web application needs all five capabilities, and assembling them costs you a distributed system you did not set out to build.
Where it stands
Relatude.DB is open source because storage should be inspectable. It runs in your environment or ours — the data is yours either way. It already powers live products, and will become the data layer of the Relatude CMS and E-commerce platform.
Pre-1.0. The project is in early development and the API still moves in small ways. It is in production use, and we are transparent about both facts — start with a real project, and tell us where it hurts.