Relatude.DB.CMS 1.0.37

There is a newer version of this package available.
See the version list below for details.
dotnet add package Relatude.DB.CMS --version 1.0.37
                    
NuGet\Install-Package Relatude.DB.CMS -Version 1.0.37
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Relatude.DB.CMS" Version="1.0.37" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Relatude.DB.CMS" Version="1.0.37" />
                    
Directory.Packages.props
<PackageReference Include="Relatude.DB.CMS" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Relatude.DB.CMS --version 1.0.37
                    
#r "nuget: Relatude.DB.CMS, 1.0.37"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Relatude.DB.CMS@1.0.37
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Relatude.DB.CMS&version=1.0.37
                    
Install as a Cake Addin
#tool nuget:?package=Relatude.DB.CMS&version=1.0.37
                    
Install as a Cake Tool

Relatude.CMS

A .NET 8 content backend layered on Relatude.DB, an in-process object-oriented graph database (NuGet Relatude.DB.Server). You model your domain as C# interfaces (the engine generates the implementing classes), declare relationships as small marker classes, and read/write through a NodeStore. Provides the base content model (IItem, IPage, ISite, ITemplate), dynamic URL routing, and DB access plumbing.

Using an AI coding agent? This package ships a Claude Code skill (relatude-cms) with the same guidance below. The commerce layer is documented in Relatude.Ecom (relatude-ecom skill), which builds on this one.

Host setup (Program.cs)

Relatude.DB runs in-process inside an ASP.NET Core app:

var builder = WebApplication.CreateBuilder(args);
builder.AddRelatudeDB();          // from Relatude.DB.Server, no namespace needed
builder.Services.AddHttpContextAccessor();
// register CMS services (routing, URL service, request context) here

var app = builder.Build();
app.UseRelatudeDB();              // starts the engine; admin UI at /relatude.db
app.Run();

AddRelatudeDB registers a DI type Database : NodeStore (transient) you can inject directly. The schema (datamodel) is managed through the admin UI at /relatude.db, where you point a datamodel source at the assembly/types holding your model interfaces.

Getting a NodeStore

NodeStore (namespace Relatude.DB.Nodes) is the entire data API. Obtain one by injecting Database, by injecting IRelatudeDBAccessor (Relatude.CMS.Engine) and using .Database, or statically via RelatudeDBRuntime.Server. Convention: methods that touch data take NodeStore db as a parameter rather than capturing it.

Modeling: interfaces, not classes

An entity is an interface extending IItem (and usually IPage for routable content):

public interface IPage : IItem {
    string MetaTitle { get; set; }
    string MetaDescription { get; set; }
    TemplatePages.Template Template { get; set; }   // relation end → the one Template for this page
}

IItem gives every node Guid Id, string Name, NodeMeta Meta (Meta.Address is the URL slug). IPage adds SEO metadata and a Template relation end. Other base types: ISite, ITemplate, ITree, ICountry, ICMSUser, ICmsUserGroup.

Relations are declared as classes deriving from OneToMany<,>, ManyToMany<,>, OneToOne<,>, OneOne<>, or ManyMany<>, with nested marker classes naming each end:

public class TemplatePages : OneToMany<ITemplate, IPage> {
    public class Template : One { }   // exposed on IPage
    public class Pages : Many { }     // exposed on ITemplate
}

OneToMany<TOne,TMany> exposes One/Many; ManyToMany<TFrom,TTo> exposes ManyFrom/ManyTo; OneToOne<,> exposes OneFrom/OneTo. Attributes (Relatude.DB.Nodes) tune mapping: [Node(...)] on the interface, [Exclude], and per-type property attributes ([StringProperty], [IntegerProperty(Indexed=true, UniqueValues=true)], [DecimalProperty], [GuidProperty], …). Embedded value maps use [EmbeddedMapProperty(KeyProperty = ...)].

Relation ends are objects, not plain collections:

if (page.Template.IsSet()) { … }          // One: has value? / Many: Count() > 0
var template = page.Template.Get();         // One → T (throws if not set; guard first)
page.Template.TryGet(out var t);            // safe variant
foreach (var child in node.Children.Get())  // Many → IEnumerable<T>

In queries prefer Include(...) over lazy .Get() to avoid N+1.

CRUD through NodeStore

var page = db.Create<IPage>();             // in-memory, NOT yet persisted
db.Insert(page);                            // or CreateAndInsert<IPage>(p => { ... })
db.Update(existing);                        // UpdateOrFail / UpdateIfExists / Upsert / ForceUpsert
db.Delete(node);                            // also Delete(Guid id) / Delete(IEnumerable<Guid>)
var p = db.Get<IPage>(id);                  // throws if missing
db.TryGet<IPage>(id, out var page2);        // safe
bool exists = db.Exists<IPage>(id);

Relations are set/cleared as their own operations (property-selector lambda):

db.SetRelation(page, p => p.Template, templateId);  // one-end: replaces
db.AddRelation(template, t => t.Pages, page);        // add to many-end
db.RemoveRelation(template, t => t.Pages, page);     // remove one link
db.ClearAndSetRelation(node, x => x.Many, newSet);   // replace whole set

Write methods accept flushToDisk (default false) and have …Async counterparts. Mutating a property on a loaded node still requires an Update call to persist.

Querying

db.Query<T>() returns IQueryOfNodes<T,T> — fluent and expression-based; nothing runs until a terminal call:

var results = db.Query<IPage>()
    .Where(p => p.MetaTitle != null)
    .Include(p => p.Template)            // eager-load; ThenInclude to go deeper
    .OrderBy(p => p.Name)
    .Page(0, 20)                          // pageIndex0based, pageSize (or Take/Skip)
    .Execute();                           // ResultSet<IPage>; .ToList()/.ToArray() also

var one  = db.Query<IPage>().Where(p => p.Meta.Address == slug).FirstOrDefault();
long n   = db.Query<IPage>().Count();

Also: WhereSearch/Search (BM25 + optional semantic/vector), WhereRelates/WhereNotRelates/WhereRelatesAny, WhereIn, WhereTypes, Facets(), Sum(...), SelectId(), a string-based Where("a => …") overload, and db.TryGetFromAddress<IPage>(address, out var page) for URL → node. Query context controls culture/visibility/revisions: db.Context.Culture("nb-NO").Hidden().Admin().Create(). Default queries exclude hidden nodes.

Transaction plugins (triggers)

Cross-cutting node behavior goes in a plugin. Derive NodeTransactionPlugin<T>, override OnBeforeNodeAction / lifecycle hooks, register with db.RegisterTransactionPlugin(...). Example: UrlAddressPlugin : NodeTransactionPlugin<IPage> generates a unique URL slug on upsert via transaction.UpdateAddress(...). Switch on the NodeOperation enum (Upsert, Insert*, Update*, ForceUpsert, Delete*).

Dynamic URL routing (Relatude.CMS.Engine.Web)

  • RelatudeDynamicRouting : DynamicRouteValueTransformer maps URL → {controller, action, id} via IRelatudeAddressResolver (→ RequestInfo), with per-request state in IRelatudeRequestContext.
  • IRelatudeUrlService.GetUrl(IPage) reverses it: stored Meta.Address, then a template-based action link, then /rnode/{id} fallback.
  • RelatudeUrlHelper.GetUniqueUrlSlug(name, db) generates collision-free slugs.

Donations (Relatude.CMS.Models.Donations)

A generic donation node model with the list/detail projection behind the WAF.Client Donations admin module (postWebAPI(session, "Donations", "List" | "Get", …)). The model namespace holds only the IDonation interface — a datamodel source scans it, so anything else in that namespace would be registered as a node type. The wire DTOs (DonationRow/DonationListResult/DonationDetail) and the pure DonationAdminProjection live in Relatude.CMS.Models.Donations.Admin. Sites extend the model with their own node interface (public interface IMyDonation : IDonation { … }) — a member may be declared in only one interface of the hierarchy.

Both namespaces are registered as datamodel sources — matching is exact, and an unregistered base interface silently drops all inherited fields from the derived type:

"DatamodelSources": [
  { "Id": "<unique guid>", "Name": "My site",   "Namespace": "MySite.Models",                 "Type": "AssemblyNameReference", "Reference": null },
  { "Id": "<unique guid>", "Name": "Donations", "Namespace": "Relatude.CMS.Models.Donations", "Type": "AssemblyNameReference", "Reference": "Relatude.DB.CMS" }
]

The admin controller stays in the host — it derives from the host framework's admin API base (e.g. WAF's BaseAdminAPIController), which this package does not reference. Routed as wafapi/Donations/{action}, the shape is:

[HttpPost]
public DonationListResult List(int pageSize = 25, int pageIndex = 0, string? sortBy = null, bool sortDesc = true,
    string? categories = null, string? statuses = null, string? paymentMethods = null, string? search = null) {
    _ = Context.Session;                             // WAF's admin gate — throws (→ 403) without a session
    var query = _cx.Database.Query<IMyDonation>()    // WhereIn(d => d.Status/…) for non-empty filters
        .OrderBy(DonationAdminProjection.SortExpression<IMyDonation>(sortBy), sortDesc);
    var page = query.Page(pageIndex, pageSize).Execute();
    return new DonationListResult { Rows = page.Values.Select(d => DonationAdminProjection.ToRow(d)).ToList(), TotalCount = page.TotalCount };
}

Get(id) is TryGet<IMyDonation>DonationAdminProjection.ToDetail(d, formGuid: …), where the host resolves the originating form's content guid from IDonation.FormId (see DonationDetail.FormGuid — the admin client cannot query by ContentId). The host also snapshots the form's name onto IDonation.FormName when the donation is created — denormalized on purpose, since the list sorts and searches it server-side (AQL orders node properties, not values computed at projection time); a later rename therefore does not retro-rename existing donations. Category (onetime/recurring/business) and free-text search have no AQL form — when active, materialise the ordered set, filter with MatchesCategory/MatchesSearch, then page.

Notes

  • Create<T>() does not persist — follow with Insert/CreateAndInsert; call Update after mutating a loaded node.
  • Relation ends aren't Lists — use .Get() / .IsSet() / .TryGet().
  • Relation changes go through SetRelation/AddRelation/RemoveRelation, not property assignment.
  • This is a library with no entry point; engine start and schema live in the host app and the /relatude.db admin UI.
  • Relatude.DB source/API reference: https://github.com/Relatude/Relatude.DB
  • Donations: every DatamodelSources entry needs its own unique Id — the store refuses to boot without one.
  • Donations: the enum-like string values (PaymentMethod, Status, GiftType, Frequency) are matched case-sensitively by the admin module's filters — store exactly the values documented on IDonation (all lowercase, e.g. vippsrecurring).
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Relatude.DB.CMS:

Package Downloads
Relatude.Core

Relatude .Net 8.0 Headless CMS and E-commerce solution

Relatude.DB.Ecom

Relatude E-commerce backend API for Relatude.DB. Builds on the Relatude.CMS package.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.38 70 9/1/2026
1.0.37 110 8/26/2026
1.0.36 107 8/21/2026
1.0.35 117 8/18/2026
1.0.34 99 8/17/2026
1.0.33 114 8/17/2026
1.0.32 146 8/5/2026
1.0.31 110 8/4/2026
1.0.30 102 8/3/2026
1.0.29 112 7/26/2026
1.0.28 115 7/26/2026
1.0.27 120 7/26/2026
1.0.26 121 7/21/2026
1.0.25 113 7/21/2026
1.0.24 115 7/21/2026
1.0.23 118 7/21/2026
1.0.22 123 7/7/2026
1.0.21 152 6/26/2026
1.0.20 138 6/26/2026