Relatude.DB 01 / 12

Open source / .NET 8+ / one NuGet

Your C# classes
are the schema.

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.

1system to operate
0migrations to write
<1 mstypical query

or scroll to continue

The problem

One application.
Five systems to keep in sync.

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.

Relational storeSchema, migrations, an ORM to map rows back onto objects.SQL + EF Core
Search clusterSeparate index, separate schema, reindex jobs, its own JVM.Elastic / OpenSearch
Vector storeEmbeddings written twice, relevance blended in app code.pgvector / Pinecone
CacheInvalidation logic you wrote, and a second source of truth.Redis
Blob + mediaUpload, resize, convert, CDN URLs — all hand-wired.S3 / Blob + pipeline
plus the glue dual writessync jobsN+1 querieseventual consistency bugs5 deployments5 SLAs5 query languages

The shift

One dependency.
A query is a method call.

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.

  • In-memory indexesTries, hashmaps and bit arrays — with set-operation caching that reuses results across different queries.
  • Append-only logHigh write throughput, crash and power-loss recovery, compaction and backups in background processes that never block live queries.
  • Tune, don't rewriteMove indexes to disk via SQLite or Lucene when memory matters. Same model, same queries.

Datamodel

The model is the C# you were going to write anyway.

[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

  • No migrationsThe binary format casts stored values onto the current schema on read, the way a document database does. Add a property and ship.
  • Attributes only tunePlain properties map by CLR type. Indexed = true is the one knob you need on day one — it makes a property filterable and facetable.
  • Classes, records, structs — or interfacesMark an interface [Node] and implementing classes become queryable subtypes. Multiple inheritance included.

Maps automatically

  • bool
  • int
  • enum
  • long
  • decimal
  • double
  • float
  • Guid
  • DateTime
  • DateTimeOffset
  • TimeSpan
  • string
  • string[]
  • Guid[]
  • int[]
  • byte[]
  • float[] vectors
  • FileValue
  • GeoCoordinate
  • Embedded<T>
  • EmbeddedMap<K,T>
  • Reference<T>
  • References<T>

Relations

Two-way relations. You never store a 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, symmetric
OneToOne<A,B>1↔1
OneToMany<A,B>1↔N
ManyMany<T>N↔N, same type
ManyToMany<A,B>N↔N

Ordered relations, relation facets, graph traversal and ShortestPath come with it.

Queries

One fluent API. Objects in, objects out.

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

  • No mapping layerYou get your own class back, with relations navigable. Nothing to configure, nothing to project into a DTO to make it work.
  • Batch when you want todb.CreateTransaction() groups any number of changes into one atomic commit.
  • Same model, four surfacesTyped C# expressions, TypeScript, a string-based query API for REST, and a generated GraphQL read endpoint.

Search

Keyword and semantic search.
One line. One index.

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

  • BM25 rankingPrefix, infix and fuzzy matching over a trie-based index.
  • Vectors on the same indexCosine similarity semantic search, with semanticRatio as the single dial between keyword and meaning.
  • Files are indexed tooUploaded documents get extracted and indexed by a background task queue, batched automatically.

Facets

Faceted search, from indexed properties, in six lines.

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

Everything below ships in the same NuGet package.

GraphTwo-way relations, ordering, traversal, shortest path.
Structured queriesTyped filters, ranges, sorting, paging, aggregations.
Full-textBM25, prefix, infix, fuzzy, trie-based.
VectorSemantic search, cosine similarity, hybrid ranking.
FacetsValue and range facets with drill-sideways counts.
File storeLocal disk or Azure Blob, image and video conversion, CDN URLs.
GeoCoordinate properties, radius filters, distance sorting.
Admin UIData browser, model inspector, query console, index and backup status.
GraphQLRead endpoint generated from the model, typed filters, introspection.
CulturesPer-object language versions with a fallback chain.
RevisionsDrafts, publishing, archiving of previous versions.
Access controlRead and write rules per object and per property.
Task queueDurable background jobs, batched automatically.
BackupsScheduled to external storage, without blocking queries.
LoggingPer-request query and usage statistics in the UI.
PluginsIntercept queries and transactions to add triggers.

Optional plugin packages swap in Lucene or SQLite indexes, Azure Blob storage and Azure OpenAI embeddings — without touching your model or your queries.

Setup

Add it to any C# project in about a minute.

Add the package

Any .NET 8+ project type. No external services, no container to start.

dotnet add package Relatude.DB.Server
Two lines in Program.cs

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();
Point one file at your model

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

The same feature set, minus four systems.

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 & monitor125+2
Query languages to know1252
Built in Possible, with an add-on or extra code You build and operate it

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

Add the NuGet, write the classes,
point one file at their namespace.

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.

ACIDper transaction
Logappend-only, recoverable
3platforms: linux, mac, windows

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.