Unified Object Model
UORM (Unified Object-Relational Mapping) is Altruist's data model system. It provides a consistent set of attributes for defining how your entities are stored, queried, and managed across different persistence providers.
Database Provider
Altruist currently supports PostgreSQL as the primary database provider:
altruist:
persistence:
database:
provider: postgres
host: localhost
port: 5432
username: myuser
password: mypassword
database: mydb
Model Attributes
[Vault] — Table Definition
Defines a stored entity:
[Vault("game_state", StoreHistory = true)]
public class GameState : IVaultModel
{
public string StorageId { get; set; } = Guid.NewGuid().ToString();
}
- Name: The table name
- StoreHistory: If
true, all updates are stored as historical records (creates a_historytable)
Note:
Avoid enabling StoreHistory on frequently updated tables — it can significantly increase storage usage.
[PrimaryKey] — Primary Key
[PrimaryKey("StorageId")]
public class Player : IVaultModel { ... }
[Column] — Explicit Column Name
[Column("player_name")]
public string Name { get; set; }
[ColumnIndex] — Indexed Column
[ColumnIndex]
public int Level { get; set; }
[Ignore] — Exclude from Storage
[Ignore]
public string TemporaryData { get; set; }
[SortingBy] — Default Sort Order
[SortingBy("SaveTime", Ascending: false)]
public class GameSave : IVaultModel { ... }
[VaultUniqueKey] — Unique Constraints
Enforce uniqueness on one or more columns:
[Vault("accounts")]
[VaultUniqueKey("username")]
[VaultUniqueKey("email")]
public class AccountVault : VaultModel
{
[VaultColumn] public override string StorageId { get; set; } = "";
[VaultColumn("username")] public string Username { get; set; } = "";
[VaultColumn("email")] public string Email { get; set; } = "";
}
Note:
Each [VaultUniqueKey] creates a separate unique constraint. Use multiple column names in one attribute for composite unique keys: [VaultUniqueKey("first_name", "last_name")].
[VaultForeignKey] — Foreign Key Relationships
Define foreign key constraints with cascade behavior:
[Vault("character_effects")]
public class EffectVault : VaultModel
{
[VaultColumn] public override string StorageId { get; set; } = "";
[VaultColumn("character_id")]
[VaultForeignKey(typeof(CharacterVault), nameof(CharacterVault.StorageId))]
public string CharacterId { get; set; } = "";
[VaultColumn] public string EffectType { get; set; } = "";
[VaultColumn] public int Duration { get; set; }
}
The third parameter controls ON DELETE behavior:
// Default: CASCADE — delete children when parent is deleted
[VaultForeignKey(typeof(CharacterVault), nameof(CharacterVault.StorageId))]
// Prevent deletion if children exist
[VaultForeignKey(typeof(CharacterVault), nameof(CharacterVault.StorageId),
VaultForeignKeyDeleteBehavior.Restrict)]
// Set to NULL when parent is deleted
[VaultForeignKey(typeof(CharacterVault), nameof(CharacterVault.StorageId),
VaultForeignKeyDeleteBehavior.SetNull)]
| Behavior | SQL | Description |
|---|---|---|
Cascade | ON DELETE CASCADE | Delete children when parent is deleted |
NoAction | ON DELETE NO ACTION | Prevent deletion if children exist (deferred) |
Restrict | ON DELETE RESTRICT | Prevent deletion if children exist (immediate) |
SetNull | ON DELETE SET NULL | Set FK column to NULL |
SetDefault | ON DELETE SET DEFAULT | Set FK column to its default value |
Note:
Foreign keys create database-level constraints. Make sure the referenced table and column exist (the referenced vault model must be registered first). Use IDatabaseInitializer with ordering to control table creation order if needed.
[VaultPrimaryKey] — Composite Primary Keys
For models with composite primary keys (multiple columns forming the key):
[Vault("player_achievements")]
[VaultPrimaryKey(nameof(PlayerId), nameof(AchievementCode))]
public class PlayerAchievementVault : VaultModel
{
[VaultColumn] public override string StorageId { get; set; } = "";
[VaultColumn("player_id")] public string PlayerId { get; set; } = "";
[VaultColumn("achievement_code")] public string AchievementCode { get; set; } = "";
[VaultColumn] public DateTime UnlockedAt { get; set; }
}
Note:
If you don't specify [VaultPrimaryKey], the framework uses StorageId as the default primary key.
Keyspaces
A Keyspace organizes your tables (similar to a database schema):
public class MyKeyspace : IKeyspace
{
public string Name { get; set; } = "myapp";
}
Querying with Vault
The Vault API provides LINQ-like query capabilities:
var vault = repository.Select<Player>();
// Filter
var highLevel = await vault.Where(p => p.Level > 50).ToListAsync();
// Sort
var topPlayers = await vault.OrderByDescending(p => p.Level).Take(10).ToListAsync();
// Single record
var player = await vault.Where(p => p.Name == "Hero").FirstOrDefaultAsync();
// Save
await vault.SaveAsync(newPlayer);
// Save with history
await vault.SaveAsync(player, storeHistory: true);
Historical Storage
When StoreHistory = true, the framework creates two tables:
- Regular table — stores the current state
- History table (
<name>_history) — retains past versions with timestamps
You control when to write history:
// Regular save (only updates current state)
await vault.SaveAsync(gameState);
// Save with historical snapshot
await vault.SaveAsync(gameState, storeHistory: true);
Using Vaults
Inject IVault<T> directly into your services — the framework resolves the correct database provider and keyspace automatically:
[Service]
public class PlayerService
{
private readonly IVault<PlayerVault> _players;
private readonly IVault<ItemVault> _items;
public PlayerService(IVault<PlayerVault> players, IVault<ItemVault> items)
{
_players = players;
_items = items;
}
}