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
ValueTypeNameas 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 respectLocked.
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 ondatetime) if missing. valuestored as text, typed byvaluetypenameon read/write.datetimeis timestamp without time zone — use UTC in your app.- Table override: append
;Table=myschema.mytableto the connection string.
Common
- Locking is enforced by the service; always call via
ScalarService/GroupedScalarService. - Groups are hierarchical;
GetByGroup("A/B")includesA/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.