DHI.Services.MCLite for Places — Internal Developer Guide¶
This page documents the MCLite repository implementation for the Places domain. It explains how the provider maps Place and Indicator entities to the MCLite schema, how to wire it into a service or the Web API, and what database objects must exist up-front.
Related docs: MCLite Providers · MCLite for GIS
What this provider is¶
DHI.Services.Provider.MCLite.PlaceRepository is an implementation of IPlaceRepository<string> that persists Places and Indicators into an MCLite database (PostgreSQL, SQL Server or SQLite via the shared Db abstraction). Unlike our PostgreSQL provider, MCLite does not create tables for you—the schema must pre-exist.
Key capabilities:
- Create/update/delete places and persist indicators (time series–based only; scalar indicators are skipped)
- De-duplicate and normalize palette styles and time intervals at the place collection level
- Query by group, including non-recursive lookups and group options
- Read back a place and reconstruct its indicators (including expanding per-collection time intervals)
Warning
Because palettes and intervals are normalized, a single indicator definition may produce multiple materialized indicators at read time (one per interval). Names are automatically suffixed (-1, -2, …) to avoid collisions.
Connection string & workspace¶
Instantiate the provider with an MCLite connection string:
var repo = new DHI.Services.Provider.MCLite.PlaceRepository(
"database=mc2014.2;workspace=workspace2"
);
Supported parameters (see MCLite Providers for full list):
database(required) — DB name (or SQLite file path)workspace(required) — logical workspace; resolved to a schema name viamaster.workspace- Optional:
host(defaultlocalhost),port(Pg default5432),username(defaultdss_admin),password(defaultsecretdss_admin),dbflavour=PostgreSQL|SqlServer|SQLite,cachemode=All|...
Workspace resolution requirement The provider resolves your effective schema with:
SELECT schema_name FROM master.workspace WHERE name = @Name
Make sure the master catalog exists and contains an entry mapping workspace → schema_name.
Required database schema¶
These table names/fields are referenced by the provider through
Names.*constants. If you are using a standard MCLite database, you already have these. Otherwise, create them accordingly. Column types below are provided for PostgreSQL; adapt to SQL Server/SQLite as needed.
1) Place collections & places¶
{schema}.place_collection
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | |
group_id |
uuid NULL | Parent collection group (nullable root) |
name |
text | Collection name |
place_collection_attribute_key |
text | Feature attribute key used by places |
feature_class_id |
uuid FK | → {schema}.feature_class(id) (GIS) |
version |
uuid | Row version (GUID) |
{schema}.place
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | |
name |
text | Friendly name (defaults to feature attribute) |
attribute_value |
text | Feature attribute value |
feature_id |
uuid FK | → GIS feature row |
place_collection_id |
uuid FK | → {schema}.place_collection(id) |
category |
text | Optional (taken from metadata key containing “category”) |
version |
uuid | Row version |
GIS dependency: the provider uses
FeatureRepositoryto validate features. See MCLite for GIS for the GIS schema and configuration.
2) Indicators, styles, thresholds, time intervals¶
{schema}.place_indicator
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | |
name |
text | Indicator type (e.g., Rainfall) |
aggregation_type |
int | AggregationType.Value |
time_series_type |
int | Reserved (set 0) |
time_series_id_type |
int | Reserved (set 0) |
data_type |
int | DataSourceType enum value (TimeSeries only; scalars are skipped) |
quantile |
double | Optional |
time_series_id |
text | DataSource.EntityId |
place_id |
uuid FK | → {schema}.place(id) |
connection_info |
text | DataSource.ConnectionId (e.g., csv) |
version |
uuid | Row version |
{schema}.place_indicator_style (palette colors, de-duplicated per place collection)
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | |
name |
text | Palette band label (e.g., 0, 10, Low) |
color |
text | HTML color (e.g., #FF0000) |
place_collection_id |
uuid FK | → {schema}.place_collection(id) |
version |
uuid |
{schema}.place_indicator_threshold (palette thresholds per indicator)
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | |
threshold_index |
int | 1-based order of thresholds |
threshold_value |
double | Palette threshold value |
indicator_style_id |
uuid FK | → {schema}.place_indicator_style(id) |
place_indicator_id |
uuid FK | → {schema}.place_indicator(id) |
version |
uuid |
{schema}.place_time_interval (distinct intervals per place collection)
| Column | Type | Notes |
|---|---|---|
id |
uuid PK | |
name |
text | e.g., Interval(1) |
interval_type |
int | TimeIntervalType enum |
start_time |
double? | Required for non-All |
end_time |
double? | Required for non-All |
place_collection_id |
uuid FK | → {schema}.place_collection(id) |
version |
uuid |
3) Shared metadata tables used by the provider¶
{schema}.entity_type → looked up by name (e.g., Place)
{schema}.entity_description → id (entity id), entity_type_id
{schema}.metadata → id, entity_description_id, metadata_values (stored as key:value;key:value;…)
The provider automatically inserts a row into
entity_descriptionandmetadataafter a successful place insert.
Data mapping: domain → MCLite¶
| Domain | MCLite column(s) | Notes |
|---|---|---|
Place.Id (FullName) |
place.id |
Guid generated by repo; returned in reads |
Place.Name |
place.name |
Defaults to FeatureId.AttributeValue if empty |
Place.Group |
derived from collection | place_collection row + group hierarchy |
Place.FeatureId |
place.feature_id, place_collection.feature_class_id, place_collection.place_collection_attribute_key, place.attribute_value |
Validated via GIS FeatureRepository |
Place.Metadata |
metadata.metadata_values |
Stored flattened as key:value; |
Indicator.Key (type) |
place_indicator.name |
We treat “type” as “name” |
Indicator.DataSource |
place_indicator.connection_info, place_indicator.time_series_id, place_indicator.data_type |
Scalars are skipped on write |
Indicator.AggregationType.Value |
place_indicator.aggregation_type |
|
Indicator.Quantile |
place_indicator.quantile |
nullable |
Indicator.StyleCode |
place_indicator_style + place_indicator_threshold |
Styles deduped per collection |
Indicator.TimeInterval |
place_time_interval |
Intervals deduped per collection; indicators are expanded at read time per interval |
Behavior & query semantics¶
- Add(place): inside a transaction, the repo:
- Ensures
place_collectionexists (creates the group path if needed), - Inserts the
place, - Inserts non-scalar indicators,
- Upserts place-level time intervals and styles,
- Links thresholds,
- Commits, then writes entity description + metadata.
- Ensures
- Update(place): remove then re-add (per existing group & name).
- Get(id or "Group/Name"): materializes the place; reconstructs indicators by joining:
place_indicator×place_time_interval× thresholds/styles (expands one per interval).
- GetByGroup(group):
groupcan be a place collection group path (recursive)- or a collection path when suffixed with
;nonrecursive.
- ContainsGroup(group): accepts tree options embedded in
group(e.g.;nonrecursive,;groupsonly). - Scalars: indicators with
DataSourceType.Scalarare ignored on write.
Wiring it up¶
You can wire the MCLite provider either manually (DI/ServiceLocator) or through the Connections module (config-driven).
A) Manual registration (ServiceLocator.Register)¶
// GIS (MCLite) — required by Places
ServiceLocator.Register(
new DHI.Services.GIS.GisService<string>(
new DHI.Services.Provider.MCLite.FeatureRepository("database=mc2014.2;workspace=workspace2")
),
"feature-mclite");
// Time series — pick your implementation; CSV shown for simplicity
ServiceLocator.Register(
new DHI.Services.TimeSeries.DiscreteTimeSeriesService<string,double>(
new DHI.Services.TimeSeries.CSV.TimeSeriesRepository("[AppData]".Resolve())
),
"csv");
// Compose Places with MCLite repo
var ts = new Dictionary<string, IDiscreteTimeSeriesService<string,double>> {
["csv"] = Services.Get<IDiscreteTimeSeriesService<string,double>>("csv")
};
var scalars = new Dictionary<string, IScalarService<string,int>>(); // optional
var gis = Services.Get<IGisService<string>>("feature-mclite");
ServiceLocator.Register(
new DHI.Services.Places.PlaceService(
new DHI.Services.Provider.MCLite.PlaceRepository("database=mc2014.2;workspace=workspace2"),
ts, scalars, gis),
"places-mclite" // ← your connectionId
);
// Now available via Web API as /api/places/places-mclite/...
You must have the GIS FeatureRepository configured against the same MCLite workspace. See MCLite for GIS.
B) Config-driven (Connections module)¶
{
"$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
"csv": {
"$type": "DHI.Services.TimeSeries.WebApi.DiscreteTimeSeriesServiceConnection, DHI.Services.TimeSeries.WebApi",
"ConnectionString": "[AppData]",
"RepositoryType": "DHI.Services.TimeSeries.CSV.TimeSeriesRepository, DHI.Services.TimeSeries",
"Name": "CSV time series service connection",
"Id": "csv"
},
"feature-mclite": {
"$type": "DHI.Services.GIS.WebApi.GroupedUpdatableGisServiceConnection, DHI.Services.GIS.WebApi",
"ConnectionString": "database=mc2014.2;workspace=workspace2",
"RepositoryType": "DHI.Services.Provider.MCLite.FeatureRepository, DHI.Services.Provider.MCLite",
"Name": "MCLite GIS service connection",
"Id": "feature-mclite"
},
"places-mclite": {
"$type": "DHI.Services.Places.WebApi.PlaceServiceConnection, DHI.Services.Places.WebApi",
"ConnectionString": "database=mc2014.2;workspace=workspace2",
"RepositoryType": "DHI.Services.Provider.MCLite.PlaceRepository, DHI.Services.Provider.MCLite",
"GisServiceConnectionId": "feature-mclite",
"TimeSeriesServiceConnectionIds": [ "csv" ],
"Name": "Place service connection",
"Id": "places-mclite"
}
}
Once loaded, your Web API exposes /api/places/places-mclite/....
Quickstart: seed data (from JSON or code)¶
You can migrate from a JSON definition like the sample places.json by reading it into Place/Indicator objects and calling placeService.Add(place). The provider will:
- Create the place collection based on
FullName(/Group/.../Collection/Name) - Validate feature existence via
FeatureRepository(throws if not found) - Insert place and indicators (non-scalar)
- Persist palette thresholds and time intervals at the collection level
- Save metadata (flattened)
Tip: if the same indicator should exist across multiple intervals, specify a single indicator with a
TimeInterval. Reads will expand it for each interval registered at the collection.
Operational notes¶
- Transactions:
Addand bulkAdd/Updaterun within DB transactions per call. - Deletes:
Updateremoves the old place then adds the new one. - Performance:
GetAll()issues a handful of queries; preferGetByGroup()for targeted reads. - Groups: pass
"...;nonrecursive"toGetByGroupto restrict to the exact collection path. - Case & lengths: indicator names/types and time series IDs are stored as
text; ensure any custom constraints in your DB match expected lengths. - Scalars: skipped (by design) in
_InsertPlaceIndicatorInfo. If you require scalars, extend the schema and mapper.
Troubleshooting¶
- “No feature is found.” — The
(FeatureCollectionId, AttributeKey, AttributeValue)triple didn’t match any GIS feature. Verify your GIS connection (MCLite for GIS) and attribute names/values. - Intervals duplicated — The repo dedupes by
(Type, Start, End)per collection. Confirm your input values (double equality) are identical. - Colors missing — Styles are keyed by
"{BandText};{HtmlColor}". Ensure yourStyleCodeyields palettes with both parts.
Minimal DDL (PostgreSQL example)¶
Use if you’re standing up a dedicated workspace schema from scratch. This is intentionally minimal—most projects rely on the standard MCLite DB which already ships all tables.
-- master catalog
CREATE SCHEMA IF NOT EXISTS master;
CREATE TABLE IF NOT EXISTS master.workspace (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text UNIQUE NOT NULL,
schema_name text NOT NULL
);
-- your workspace schema, e.g. "workspace2"
CREATE SCHEMA IF NOT EXISTS workspace2;
-- place collections
CREATE TABLE workspace2.place_collection (
id uuid PRIMARY KEY,
group_id uuid NULL,
name text NOT NULL,
place_collection_attribute_key text,
feature_class_id uuid NOT NULL,
version uuid NOT NULL
);
-- places
CREATE TABLE workspace2.place (
id uuid PRIMARY KEY,
name text NOT NULL,
attribute_value text,
feature_id uuid NOT NULL,
place_collection_id uuid NOT NULL REFERENCES workspace2.place_collection(id),
category text,
version uuid NOT NULL
);
-- indicators
CREATE TABLE workspace2.place_indicator (
id uuid PRIMARY KEY,
name text NOT NULL,
aggregation_type int NOT NULL,
time_series_type int NOT NULL,
time_series_id_type int NOT NULL,
data_type int NOT NULL,
quantile double precision,
time_series_id text,
place_id uuid NOT NULL REFERENCES workspace2.place(id),
connection_info text,
version uuid NOT NULL
);
-- time intervals
CREATE TABLE workspace2.place_time_interval (
id uuid PRIMARY KEY,
name text NOT NULL,
interval_type int NOT NULL,
start_time double precision,
end_time double precision,
place_collection_id uuid NOT NULL REFERENCES workspace2.place_collection(id),
version uuid NOT NULL
);
-- styles
CREATE TABLE workspace2.place_indicator_style (
id uuid PRIMARY KEY,
name text NOT NULL,
color text NOT NULL,
place_collection_id uuid NOT NULL REFERENCES workspace2.place_collection(id),
version uuid NOT NULL
);
-- thresholds
CREATE TABLE workspace2.place_indicator_threshold (
id uuid PRIMARY KEY,
threshold_index int NOT NULL,
threshold_value double precision NOT NULL,
place_indicator_style_id uuid NOT NULL REFERENCES workspace2.place_indicator_style(id),
place_indicator_id uuid NOT NULL REFERENCES workspace2.place_indicator(id),
version uuid NOT NULL
);
-- entity type/description + metadata (minimal)
CREATE TABLE workspace2.entity_type (
id uuid PRIMARY KEY,
name text UNIQUE NOT NULL
);
CREATE TABLE workspace2.entity_description (
id uuid PRIMARY KEY, -- equals the entity's id
entity_type_id uuid NOT NULL REFERENCES workspace2.entity_type(id)
);
CREATE TABLE workspace2.metadata (
id uuid PRIMARY KEY,
entity_description_id uuid NOT NULL REFERENCES workspace2.entity_description(id),
metadata_values text
);
Don’t forget to insert a
master.workspacerow mapping your logicalworkspace(e.g.workspace2) to the correctschema_name(e.g.workspace2).
Web API pairing¶
Once registered (manually or via connections), the Places Web API exposes the MCLite-backed service at:
/api/places/{connectionId}/...
All endpoints (CRUD on places/indicators, features with status colors, thresholds, palette PNG, etc.) are the same as for other providers. See the Web API guide for details.