---
title: SAIF.Framework.Preferences business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: <owner>
last_reviewed: 2026-07-23
library: SAIF.Framework.Preferences
reference_type: package
---

# SAIF.Framework.Preferences business rules

This page captures business rules enforced by `SAIF.Framework.Preferences` when reading, writing, and normalizing preference data in [USER_PREFERENCE](database-tables.md#user_preference).

## BR-001: User IDs are normalized to lower case

| Field | Value |
| --- | --- |
| **Statement** | User IDs are normalized to lower case before querying or storing preference data. |
| **Trigger** | Any call to `GetPreferences`, `GetPreferencesForUser`, `InsertPref`, `UpdatePref`, `DeletePref`, or `DoesPrefExist`. |
| **Enforcement** | `userId.ToLower()` is passed to `SqlHelper.NewParm("userid", ...)` in each method. |
| **Outcome** | Preference lookups and writes are effectively case-insensitive on user ID. |
| **Tables** | [USER_PREFERENCE](database-tables.md#user_preference) |
| **SQL** | [SQL-002](sql/SQL-002-get-preferences.md), [SQL-003](sql/SQL-003-get-preferences-for-user.md), [SQL-004](sql/SQL-004-insert-preference.md), [SQL-005](sql/SQL-005-update-preference.md), [SQL-006](sql/SQL-006-delete-preference.md), [SQL-007](sql/SQL-007-does-pref-exist.md) |
| **Source** | SAIF.Framework.Preferences\PreferenceData.cs |

---

## BR-002: Empty preference text triggers deletion

| Field | Value |
| --- | --- |
| **Statement** | Saving an empty or whitespace-only preference value deletes the existing row rather than storing an empty value. |
| **Trigger** | `SavePreferences` is called with `preferenceText` that is empty or whitespace-only after `Trim()`. |
| **Enforcement** | `SavePreferences` branches to the `DeletePref` path. |
| **Outcome** | The preference row for that app/page/user is removed instead of persisting an empty value. |
| **Tables** | [USER_PREFERENCE](database-tables.md#user_preference) |
| **SQL** | [SQL-006](sql/SQL-006-delete-preference.md) |
| **Source** | SAIF.Framework.Preferences\PreferenceData.cs |

---

## BR-003: Preference identity is scoped by app, user, and optional page

| Field | Value |
| --- | --- |
| **Statement** | A preference is uniquely identified by application name, user ID, and an optional page name; a preference with no page name is distinct from one scoped to a specific page. |
| **Trigger** | Any read, write, or delete operation against `USER_PREFERENCE`. |
| **Enforcement** | The `whereNull` (`PAGE_NAME IS NULL`) and `wherePageName` (`PAGE_NAME = :pagename`) constant clauses are applied consistently across `GetPreferences`, `UpdatePref`, `DeletePref`, and `DoesPrefExist`. |
| **Outcome** | Application/user/page-scoped preferences do not collide with application/user-only preferences. |
| **Tables** | [USER_PREFERENCE](database-tables.md#user_preference) |
| **SQL** | [SQL-002](sql/SQL-002-get-preferences.md), [SQL-004](sql/SQL-004-insert-preference.md), [SQL-005](sql/SQL-005-update-preference.md), [SQL-006](sql/SQL-006-delete-preference.md), [SQL-007](sql/SQL-007-does-pref-exist.md) |
| **Source** | SAIF.Framework.Preferences\PreferenceData.cs |

---

## BR-004: Update is skipped when stored value is unchanged

| Field | Value |
| --- | --- |
| **Statement** | An update is a no-op when the new preference text is identical to the currently stored value. |
| **Trigger** | `UpdatePref` executes as part of `SavePreferences`. |
| **Enforcement** | The `PREFERENCE_TEXT <> :prefs` condition in the `UPDATE ... WHERE` clause. |
| **Outcome** | Redundant writes and audit-timestamp churn are avoided when the new value equals the existing value. |
| **Tables** | [USER_PREFERENCE](database-tables.md#user_preference) |
| **SQL** | [SQL-005](sql/SQL-005-update-preference.md) |
| **Source** | SAIF.Framework.Preferences\PreferenceData.cs |

---

## BR-005: Preference text is trimmed before storage

| Field | Value |
| --- | --- |
| **Statement** | Preference text is trimmed of leading/trailing whitespace before it is persisted. |
| **Trigger** | `InsertPref` or `UpdatePref` executes. |
| **Enforcement** | `preferenceText.Trim()` is passed as the `prefs` parameter. |
| **Outcome** | Leading/trailing whitespace is never persisted in `PREFERENCE_TEXT`. |
| **Tables** | [USER_PREFERENCE](database-tables.md#user_preference) |
| **SQL** | [SQL-004](sql/SQL-004-insert-preference.md), [SQL-005](sql/SQL-005-update-preference.md) |
| **Source** | SAIF.Framework.Preferences\PreferenceData.cs |

---

## BR-006: Preference item payload is JSON-encoded key/value data

| Field | Value |
| --- | --- |
| **Statement** | The `PREFERENCE_TEXT` column stores a JSON-encoded key/value dictionary, deserialized on read into `Preference.PreferenceItems`. |
| **Trigger** | `GetPreferencesForUser` deserializes `PREFERENCE_TEXT` for each returned row. |
| **Enforcement** | `serializer.Deserialize<Dictionary<string, string>>(json)` is invoked only when the trimmed JSON text has non-zero length. |
| **Outcome** | `Preference.PreferenceItems` reflects the stored key/value pairs; empty stored text yields an empty dictionary. |
| **Tables** | [USER_PREFERENCE](database-tables.md#user_preference) |
| **SQL** | [SQL-003](sql/SQL-003-get-preferences-for-user.md) |
| **Source** | SAIF.Framework.Preferences\PreferenceData.cs |

## Notes
- All six rules above are enforced entirely within `PreferenceData`; there is no separate `IBusinessCriteria`/business-object rule layer in this library.
- No security, authorization, or segregation-of-duties rules were found in this library; `userId` is used purely as a data key, not as an authorization decision point.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Database tables](database-tables.md)
- [SQL catalog](sql/index.md)
- [Configuration](configuration.md)
- [API reference](api/index.md)

## Sources
- SAIF.Framework.Preferences\PreferenceData.cs
- SAIF.Framework.Preferences\Preference.cs
