Skip to content

Scalars Providers – Guide & Recipes

Pick a storage/client for Scalars, wire it in minutes, and avoid the foot-guns. This page is the practical “how do I use it?” cheat sheet.


See also (deep dives)


TL;DR — which provider?

Provider Best when Where data lives Creates tables? Notable
DS ScalarRepository (DHI.Services.Provider.DS) You want to consume scalars from another DS host over HTTP Remote Scalars Web API (/api/scalars/{connectionId}) N/A Built-in retries (Polly), Bearer auth, drop-in client for GroupedScalarService
PostgreSQL ScalarRepository (DHI.Services.Provider.PostgreSQL) You want a simple, self-hosted PostgreSQL backend Your PostgreSQL DB (public.scalars by default) Yes (auto-DDL) Stores current value (+ timestamp/flag), table override via ;Table=schema.table

Concepts you’ll actually touch

  • Id / FullName: hierarchical Group/Name (e.g., ProjectA/MaxLevel).
  • Groups: GetByGroup("ProjectA") is recursive (children included).
  • Value typing: set ValueTypeName as a CLR type name ("System.Double", "System.Int32", …). Values are parsed/stringified invariantly.
  • Locking: enforced by the service (ScalarService / GroupedScalarService), not the repo. Call through the service to respect Locked.

5-minute wiring

A) Consume a remote Scalars API (DS client)

using DHI.Services;
using DHI.Services.Scalars;
using DHI.Services.Provider.DS;

// Repo talks to remote host over HTTP (with retries + bearer token)
var repo = new ScalarRepository(
  "baseUrl=https://remote.host/api/scalars/prod;token=[env:SCALARS_API_TOKEN];retry=5");

// Wrap in service (group-aware)
var scalars = new GroupedScalarService<string,int>(repo);

ServiceLocator.Register(scalars, "scalars-ds"); // connectionId for your app
// REST base in your host (if you expose it) -> /api/scalars/scalars-ds/...

B) Persist scalars in PostgreSQL

using DHI.Services;
using DHI.Services.Scalars;
using DHI.Services.Provider.PostgreSQL;

// Optional table override with ;Table=schema.table
var repo = new ScalarRepository(
  "Host=localhost;Port=5432;Database=ProviderTest;Username=postgres;Password=***;Table=public.scalars");

var scalars = new GroupedScalarService<string,int>(repo);
ServiceLocator.Register(scalars, "postgresql");
// Your host (if any): /api/scalars/postgresql/...

Minimal CRUD (works with both providers)

// 1) Define metadata (no value yet)
scalars.Add(new DHI.Services.Scalars.Scalar<string,int>(
  id: "ProjectA/MaxLevel",
  name: "MaxLevel",
  valueTypeName: "System.Double",
  group: "ProjectA")
{
  Description = "Highest allowed reservoir level (m)"
});

// 2) Set the current value (+timestamp/flag)
scalars.SetData("ProjectA/MaxLevel",
  new DHI.Services.Scalars.ScalarData<int>(42.3, DateTime.UtcNow, flag: 1));

// 3) Lock to prevent further changes (service enforces it)
scalars.SetLocked("ProjectA/MaxLevel", true);

// 4) Read one / list by group
var one  = scalars.Get("ProjectA/MaxLevel").Value;
var list = scalars.GetByGroup("ProjectA").ToList();

connections.json (declarative wiring)

DS → consume another host

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
  "scalars-ds": {
    "$type": "DHI.Services.Scalars.WebApi.GroupedScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Provider.DS.ScalarRepository, DHI.Services.Provider.DS",
    "RepositoryConnectionString": "baseUrl=https://remote.host/api/scalars/prod;token=[env:SCALARS_API_TOKEN];retry=5",
    "Name": "Scalars via DS",
    "Id": "scalars-ds"
  }
}

PostgreSQL → simple database backend

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
  "postgresql": {
    "$type": "DHI.Services.Scalars.WebApi.GroupedScalarServiceConnection, DHI.Services.Scalars.WebApi",
    "RepositoryType": "DHI.Services.Provider.PostgreSQL.ScalarRepository, DHI.Services.Provider.PostgreSQL",
    "RepositoryConnectionString": "Server=localhost;Port=5432;Database=ProviderTest;User Id=postgres;Password=Solutions!",
    "LoggerType": "DHI.Services.Logging.SimpleLogger, DHI.Services",
    "LoggerConnectionString": "[AppData]scalars.log",
    "Name": "PostgreSQL provider for scalar service connection",
    "Id": "postgresql"
  }
}

Behavior notes (per provider)

DS client

  • Base URL must include the remote connectionId: /api/scalars/{connectionId}.
  • Auth: Authorization: Bearer <token> (string or [env:...]).
  • Retries: exponential backoff (retry=<n>, default 5).
  • Methods map 1:1 to Web API routes (GET/POST/PUT/DELETE).

PostgreSQL

  • Auto-creates public.scalars (and an index on datetime) if missing.
  • value stored as text, typed by valuetypename on read/write.
  • datetime is timestamp without time zone — use UTC in your app.
  • Table override: append ;Table=myschema.mytable to the connection string.

Common

  • Locking is enforced by the service; always call via ScalarService/GroupedScalarService.
  • Groups are hierarchical; GetByGroup("A/B") includes A/B/*.

Foot-guns & fixes

Symptom Likely cause Fix
401/403 (DS) Missing/invalid token or wrong audience/scope Verify token= source and remote host’s auth config
404 on Get/SetData (DS) Id not URL-encoded the same way as remote Ensure you pass the FullName and the server’s encoding matches
Value parse errors ValueTypeName doesn’t match the actual data Use correct CLR type string (System.Double, etc.)
Updates “ignored” Scalar is Locked SetLocked(id,false) before updating
No children in GetByGroup Group token mismatch / casing Use exact leading path; keep casing consistent

What providers to use

  • Pick DS when your truth already lives behind another DS Scalars Web API and you just need a client with retries/auth.
  • Pick PostgreSQL when you want a fast, self-contained backend you control, with auto-DDL and simple ops.