152 lines
8.8 KiB
Markdown
152 lines
8.8 KiB
Markdown
---
|
|
source_files:
|
|
- Common/DTS.Common.SettingsDB/UserSetting.cs
|
|
- Common/DTS.Common.SettingsDB/Setting.cs
|
|
- Common/DTS.Common.SettingsDB/GlobalSetting.cs
|
|
- Common/DTS.Common.SettingsDB/SettingsDB.cs
|
|
generated_at: "2026-04-16T11:31:14.800080+00:00"
|
|
model: "zai-org/GLM-5-FP8"
|
|
schema_version: 1
|
|
sha256: "0690ee9f2887691d"
|
|
---
|
|
|
|
# Documentation: DTS.Common.Settings
|
|
|
|
## 1. Purpose
|
|
|
|
This module provides a persistent settings management system that stores and retrieves application configuration values in a SQL database. It supports two categories of settings: **global settings** (application-wide, identified by the `"SYSTEM"` user) and **user-specific settings** (scoped to individual users). The `SettingsDB` class serves as the primary facade, implementing a thread-safe singleton pattern with in-memory caching to minimize database round-trips. Settings are serialized as strings in the database, with helper methods provided for type conversion to/from `int`, `double`, `bool`, and arrays.
|
|
|
|
---
|
|
|
|
## 2. Public Interface
|
|
|
|
### `Setting` (Abstract Base Class)
|
|
|
|
The abstract base class for all setting types.
|
|
|
|
**Properties:**
|
|
- `string PropertyId` — The string identifier for the setting.
|
|
- `PropertyTypes PropertyType` — Either `PropertyTypes.User` (1) or `PropertyTypes.Global` (2).
|
|
- `string PropertyValue` — The current value of the setting.
|
|
- `string UserId` — The user identifier; `"SYSTEM"` for global settings.
|
|
|
|
**Methods:**
|
|
- `void SetValue(string value)` — Updates the in-memory value and persists it to the database via `StoreInDB()`.
|
|
|
|
**Nested Types:**
|
|
- `enum PropertyTypes` — Values: `User = 1`, `Global = 2`.
|
|
|
|
---
|
|
|
|
### `GlobalSetting` (Class, inherits `Setting`)
|
|
|
|
Represents an application-wide setting.
|
|
|
|
**Constructors:**
|
|
- `GlobalSetting(string id, string defaultPropertyValue)` — Creates or retrieves a global setting with the given ID and default value.
|
|
|
|
**Static Methods:**
|
|
- `GlobalSetting ReadXML(System.Xml.XmlElement root)` — Parses an XML element to construct a `GlobalSetting`. Expects child elements matching `XMLFields.NAME` and `XMLFields.VALUE`.
|
|
- `GlobalSetting[] GetAllGlobalSettings()` — Retrieves all global settings from the database. Returns an empty array on failure.
|
|
|
|
**Nested Types:**
|
|
- `enum XMLFields` — Values: `NAME`, `VALUE`. Used for XML parsing.
|
|
- `class NullDefaultValueException : NullReferenceException` — Thrown when a setting doesn't exist and no default value is provided.
|
|
|
|
---
|
|
|
|
### `UserSetting` (Class, inherits `Setting`)
|
|
|
|
Represents a user-specific setting.
|
|
|
|
**Constructors:**
|
|
- `UserSetting(string id, string defaultValue, string user)` — Creates or retrieves a user-specific setting for the given user.
|
|
|
|
---
|
|
|
|
### `SettingsDB` (Static Facade Class)
|
|
|
|
The primary interface for interacting with settings. Implements a thread-safe singleton with caching.
|
|
|
|
**Static Methods:**
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `void RefreshSettings()` | Clears the in-memory settings cache. |
|
|
| `string GetUserValue(string id, string defaultValue, string user)` | Retrieves a user-specific value; creates with default if not exists. |
|
|
| `string GetGlobalValue(string id, string defaultValue, bool useCache = true)` | Retrieves a global value; creates with default if not exists. Optionally bypasses cache. |
|
|
| `void GetAllGlobalValues()` | Loads all global settings into the cache. |
|
|
| `double GetGlobalValueDouble(string id, double defaultValue)` | Retrieves a global value parsed as `double`. |
|
|
| `int GetGlobalValueInt(string id, int defaultValue)` | Retrieves a global value parsed as `int`. |
|
|
| `bool GetGlobalValueBool(string id, bool defaultValue, bool useCache = true)` | Retrieves a global value parsed as `bool`. |
|
|
| `double[] GetGlobalValueDoubleArray(string id, double[] defaultValues)` | Retrieves an array of doubles stored as separate keys (`{id}_x_Count`, `{id}_x_0`, etc.). |
|
|
| `int[] GetGlobalValueIntArray(string id, int[] defaultValues)` | Retrieves an array of ints stored as separate keys. |
|
|
| `void SetUserValue(string id, string value, string user)` | Sets a user-specific value in the database. |
|
|
| `void SetGlobalValue(string id, string value)` | Sets a global string value. |
|
|
| `void SetGlobalValueDouble(string id, double value)` | Sets a global double value. |
|
|
| `void SetGlobalValueInt(string id, int value)` | Sets a global int value. |
|
|
| `void SetGlobalValueBoolean(string id, bool value)` | Sets a global bool value. |
|
|
| `void SetGlobalValueDoubleArray(string id, double[] values)` | Stores a double array as multiple keys. |
|
|
| `void SetGlobalValueIntArray(string id, int[] values)` | Stores an int array as multiple keys. |
|
|
|
|
---
|
|
|
|
## 3. Invariants
|
|
|
|
1. **Thread Safety**: All public methods in `SettingsDB` acquire a lock on `LOCK_OBJECT` before accessing `_settingsLookup`. The singleton instance is lazily initialized under lock.
|
|
|
|
2. **Key Uniqueness**: User-specific settings use a composite key format `{id}_{user}` in the cache; global settings use `{id}` directly.
|
|
|
|
3. **Global Settings Identity**: All `GlobalSetting` instances use `UserId = "SYSTEM"` (defined as a private constant).
|
|
|
|
4. **Database Synchronization**: Calling `SetValue()` on any `Setting` instance immediately persists the value to the database via `StoreInDB()`.
|
|
|
|
5. **Auto-Creation on Missing**: When a setting is retrieved but doesn't exist in the database, it is automatically created with the provided default value and persisted.
|
|
|
|
6. **Array Storage Convention**: Arrays are stored as multiple database records:
|
|
- `{id}_x_Count` — The number of elements.
|
|
- `{id}_x_{i}` — The value at index `i`.
|
|
|
|
7. **Culture Invariance**: All numeric-to-string conversions use `CultureInfo.InvariantCulture`.
|
|
|
|
---
|
|
|
|
## 4. Dependencies
|
|
|
|
### This Module Depends On:
|
|
- `System.Data` — For `CommandType`, `SqlParameter`, `SqlDbType`, `ParameterDirection`.
|
|
- `System.Data.SqlClient` — For `SqlConnection`/`SqlCommand` operations (implied via `SqlParameter`).
|
|
- `DTS.Common.Storage` — Provides:
|
|
- `DbOperations.GetSQLCommand(bool)`
|
|
- `DbOperations.Connection.QueryDataSet(SqlCommand)`
|
|
- `DbOperationsEnum.StoredProcedure` (enum with `sp_SettingsGet`, `sp_SettingsUpdateInsert`)
|
|
- `DbOperations.Settings.UserFields.PropertyValue` (enum)
|
|
- `DTS.Common.Utilities.Logging` — Provides `APILogger.LogException(Exception)` and `APILogger.Log(string, Exception)`.
|
|
|
|
### Database Dependencies:
|
|
- Stored Procedure: `sp_SettingsGet` — Retrieves settings by `@UserId` and `@PropertyId`.
|
|
- Stored Procedure: `sp_SettingsUpdateInsert` — Inserts or updates a setting. Parameters: `@PropertyId`, `@PropertyType`, `@PropertyValue`, `@UserId`, `@new_id` (output), `@errorNumber` (output), `@errorMessage` (output).
|
|
|
|
---
|
|
|
|
## 5. Gotchas
|
|
|
|
1. **Silent Exception Swallowing in `UserSetting.GetPropertyValue`**: All exceptions are caught and silently ignored; the method falls back to the default value without logging. This can mask database connectivity issues or data corruption.
|
|
|
|
2. **Unused Error Output Parameters**: In `Setting.StoreInDB()`, the stored procedure returns `@errorNumber` and `@errorMessage`, but the code checks if `errorNumber != 0` and then does nothing (empty block). Errors from the database are silently ignored.
|
|
|
|
3. **Max Length Constraint Not Enforced in Code**: The `PropertyValue` property documentation states "there is a max length" but no validation occurs before database insertion. The stored procedure parameters specify length 255 for string values.
|
|
|
|
4. **Redundant `cmd.Connection.Dispose()`**: All database methods call `cmd.Connection.Dispose()` inside a `finally` block, but the `cmd` is already inside a `using` statement. This is redundant and potentially confusing.
|
|
|
|
5. **Backward Compatibility Hack for `ImportCreateDynamicGroups`**: `SettingsDB` contains special-case logic for migrating from `CSV_IMPORT_CREATE_DYNAMIC_GROUPS` to `IMPORT_CREATE_DYNAMIC_GROUPS`. When setting the new key, it also sets the old key for pre-Version 93 client compatibility. This is documented in comments referencing issue 36831.
|
|
|
|
6. **`GetGlobalValueBool` Parsing Logic Error**: The method contains `return !bool.TryParse(sValue, out b) ? b : b;` — both branches return `b`, making the conditional meaningless. It should likely return `defaultValue` on parse failure.
|
|
|
|
7. **`NullDefaultValueException` Only Thrown by `GlobalSetting`**: `UserSetting.GetPropertyValue` does not throw this exception; it silently falls back to the default. Only `GlobalSetting.GetPropertyValue` throws `NullDefaultValueException` when the setting doesn't exist and `defaultValue` is null.
|
|
|
|
8. **TODO Comments Indicate Incomplete Work**:
|
|
- `UserSetting.GetPropertyValue` line 42: `//TODO: handle exception properly`
|
|
- `GlobalSetting.ProcessXMLElement` line 68: `// TODO: handle exception properly`
|
|
|
|
9. **Cache Bypass Only Affects Retrieval**: `GetGlobalValue(id, defaultValue, useCache: false)` bypasses the cache when reading, but `SetGlobalValue` always updates the cache. This can lead to stale cache entries if another process modifies the database directly. |