Skip to main content

Modelling Intro

Building a data model in Cognibase is done entirely in C# using .NET attributes (annotations). Your domain is a standard .NET class library whose classes inherit from DataItem and are decorated with Cognibase attributes. The framework reads these attributes at runtime to handle persistence, real-time synchronization, and ORM mapping automatically — no separate schema files or code generation required.

Domains

A Domain is a named set of DataItem classes, typically packaged in a single .NET assembly. You declare a domain by applying the [RuntimeDomain] attribute to the assembly in your AssemblyInfo.cs (or any file with [assembly: ...] declarations):

[assembly: RuntimeDomain(DomainName = "MyApp.Domain")]

When your domain depends on classes from another domain assembly, declare that dependency with [RequiredDomain]:

[assembly: RuntimeDomain(DomainName = "MyApp.Domain")]
[assembly: RequiredDomain(typeof(CommonDomainFactory))]

For optional, loosely-coupled domain references use [IncludedDomain] instead.

See Domain Composition for the full set of options.

Defining Classes

Persisted Classes

A persisted class is saved to the database and automatically synchronized across all connected nodes. Decorate it with [PersistedClass]:

[PersistedClass]
public class Product : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }

[PersistedProperty(IsMandatory = true)]
public string Name { get => getter<string>(); set => setter(value); }

[PersistedProperty]
public decimal Price { get => getter<decimal>(); set => setter(value); }

[PersistedProperty]
public DateTime CreatedAt { get => getter<DateTime>(); set => setter(value); }
}

Common class-level options on [PersistedClass]:

OptionTypeDescription
IsReadOnlyboolMarks the class as read-only — no create/update/delete from clients
IsArchivableboolEnables archival support for instances of this class
IsClientPreLoadedboolAutomatically loads all instances on client startup
IsServerPreLoadedboolAutomatically loads all instances on server startup
PersistedAsstringOverrides the database table name
IsTemplateboolMarks the class as a template (not directly instantiated)

Runtime Classes

A runtime class is distributed across all connected nodes in real-time but is not persisted to the database. Use [RuntimeClass] for objects that carry transient or live state:

[RuntimeClass]
public class ScreenStatus : DataItem
{
[RuntimeProperty]
public bool IsOnline { get => getter<bool>(); set => setter(value); }

[RuntimeProperty]
public string ActiveContentName { get => getter<string>(); set => setter(value); }
}

Abstract Base Classes and Inheritance

Standard C# inheritance is fully supported. Mark abstract base classes with [PersistedClass] to share common properties across a hierarchy:

[PersistedClass]
public abstract class Content : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }

[PersistedProperty(IsMandatory = true)]
public string Name { get => getter<string>(); set => setter(value); }
}

// Canvas inherits Id and Name from Content
[PersistedClass]
public class Canvas : Content
{
[PersistedProperty]
public float Width { get => getter<float>(); set => setter(value); }

[PersistedProperty]
public float Height { get => getter<float>(); set => setter(value); }
}

// Playlist inherits Id and Name from Content
[PersistedClass]
public class Playlist : Content
{
[PersistedProperty(
AssociationType = AssociationType.CompositionParent,
ReverseRef = nameof(PlaylistItem.Playlist))]
public DataItemRefList<PlaylistItem> Items => getList<PlaylistItem>();
}

Concrete subclasses are each mapped to their own database table. References to the abstract base type (e.g. a property typed as Content) can hold an instance of any concrete subclass.

Properties

Scalar Properties

Scalar properties map to individual columns in the database. Cognibase supports all common .NET value types and string:

[PersistedProperty]
public string Description { get => getter<string>(); set => setter(value); }

[PersistedProperty]
public int Count { get => getter<int>(); set => setter(value); }

[PersistedProperty]
public float Scale { get => getter<float>(); set => setter(value); }

[PersistedProperty]
public bool IsVisible { get => getter<bool>(); set => setter(value); }

[PersistedProperty]
public DateTime Timestamp { get => getter<DateTime>(); set => setter(value); }

[PersistedProperty]
public TimeSpan Duration { get => getter<TimeSpan>(); set => setter(value); }

Binary data is stored using BLOB and ImageBLOB:

[PersistedProperty]
public BLOB FileData { get => getter<BLOB>(); set => setter(value); }

[PersistedProperty]
public ImageBLOB Thumbnail { get => getter<ImageBLOB>(); set => setter(value); }

Identity and Key Properties

Use IdOrder to designate primary key fields. Multiple properties with IdOrder form a composite key. Use AutoValue = AutoValue.Identity for auto-increment columns:

// Single auto-increment identity key
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }

// String-based natural key
[PersistedProperty(IdOrder = 1)]
public string Code { get => getter<string>(); set => setter(value); }

// Composite key — two properties together form the unique identifier
[PersistedProperty(IdOrder = 1)]
public long DomainId { get => getter<long>(); set => setter(value); }

[PersistedProperty(IdOrder = 2)]
public long SequenceNo { get => getter<long>(); set => setter(value); }

Runtime Properties

Runtime properties are synchronized in real-time across all nodes but are not written to the database. Use [RuntimeProperty] on properties that carry live or transient state even within otherwise persisted classes:

[PersistedClass]
public class Screen : DataItem
{
// persisted fields
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }

[PersistedProperty]
public string HostAddress { get => getter<string>(); set => setter(value); }

// runtime field — not written to the database
[RuntimeProperty]
public bool IsAlive { get => getter<bool>(); set => setter(value); }
}

Calculative Properties

Calculative properties return a value computed by your own code. They are neither persisted nor distributed to other nodes:

[CalculativeProperty]
public string DisplayLabel => $"[{Code}] {Name}";

Indirect Properties

Indirect properties expose a value obtained by navigating through a reference chain. The Target parameter is the dot-separated path to the source value:

// Exposes the name of the category that this product belongs to
[IndirectProperty(Target = "Category.Name")]
public string CategoryName { get; }

Relationships

Single Reference (Many-to-One)

Reference another entity with a standard typed property:

[PersistedClass]
public class Screen : DataItem
{
// ... other properties ...

// Reference to the content currently displayed on this screen
[PersistedProperty]
public Content ActiveContent { get => getter<Content>(); set => setter(value); }
}

The referenced type can be a concrete class or an abstract base class. Cognibase resolves the actual subtype at runtime.

Collection Reference (One-to-Many)

Use DataItemRefList<T> for collections of related objects:

[PersistedProperty]
public DataItemRefList<Screen> Screens => getList<Screen>();

List properties are declared with a getter-only expression (=>). They are loaded lazily by the framework.

Composition (Parent–Child Ownership)

Composition expresses that the parent owns its children. Children that lose their parent are automatically deleted. Use AssociationType.CompositionParent on the collection side and AssociationType.CompositionChild on the back-reference, and declare ReverseRef on both ends:

// ──────────────── PARENT ────────────────
[PersistedClass]
public class Canvas : Content
{
[PersistedProperty(
AssociationType = AssociationType.CompositionParent,
ReverseRef = nameof(Tile.Canvas))]
public DataItemRefList<Tile> Tiles => getList<Tile>();
}

// ──────────────── CHILD ─────────────────
[PersistedClass]
public class Tile : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }

[PersistedProperty]
public float X { get => getter<float>(); set => setter(value); }

[PersistedProperty]
public float Y { get => getter<float>(); set => setter(value); }

// Back-reference to the owning canvas — setting this to null deletes the tile
[PersistedProperty(
AssociationType = AssociationType.CompositionChild,
ReverseRef = nameof(Canvas.Tiles),
IsMandatory = true)]
public Canvas Canvas { get => getter<Canvas>(); set => setter(value); }
}

Bidirectional Association

For non-composition bidirectional relationships, declare ReverseRef on both sides pointing to the property name on the other class:

// CityObject references multiple CityModels
[PersistedProperty(ReverseRef = nameof(CityModel.CityObjects))]
public DataItemRefList<CityModel> CityModels => getList<CityModel>();

// CityModel references multiple CityObjects (the other side of the same link)
[PersistedProperty(ReverseRef = nameof(CityObject.CityModels))]
public DataItemRefList<CityObject> CityObjects => getList<CityObject>();

Enumerations

Standard C# enums are directly supported as persisted property types:

public enum TileControlType
{
VideoStream,
Diagram,
GeoDiagram,
GridView,
UserControl
}

[PersistedClass]
public class Tile : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }

[PersistedProperty(IsMandatory = true)]
public TileControlType ControlType { get => getter<TileControlType>(); set => setter(value); }
}

Enum values are stored as their underlying integer in the database.

Validation Constraints

[PersistedProperty] and [RuntimeProperty] both support declarative constraints that are enforced by the framework:

// Mandatory — null or empty string is rejected
[PersistedProperty(IsMandatory = true)]
public string Name { get => getter<string>(); set => setter(value); }

// Numeric range
[PersistedProperty(Min = 0.0, Max = 100.0)]
public double Percentage { get => getter<double>(); set => setter(value); }

// Write-once — can be set on creation but not changed afterwards
[PersistedProperty(IsWriteOnce = true)]
public string SerialNumber { get => getter<string>(); set => setter(value); }

// Default value applied when a new instance is created
[PersistedProperty(DefaultValue = 1)]
public int Quantity { get => getter<int>(); set => setter(value); }

Display Metadata

Use [DisplayAs] to attach human-readable labels and descriptions to classes, properties, and methods. These annotations are used by Cognibase UI components and security management tools:

[DisplayAs(Label = "Product", Description = "A sellable item in the catalogue")]
[PersistedClass]
public class Product : DataItem
{
[DisplayAs(Label = "Product Name", Description = "The full commercial name")]
[PersistedProperty(IsMandatory = true)]
public string Name { get => getter<string>(); set => setter(value); }

[DisplayAs(Label = "Unit Price")]
[PersistedProperty]
public decimal Price { get => getter<decimal>(); set => setter(value); }
}

Actions

Methods on a DataItem class can be exposed as named actions using [RuntimeAction]. Actions are callable remotely and are integrated into the Cognibase security model:

[RuntimeAction(Description = "Activate this screen")]
public void Activate()
{
IsAlive = true;
}

[RuntimeAction(Description = "Send content to this screen", IsDisplayed = false)]
public void PushContent(Content content)
{
ActiveContent = content;
}

Further Reading