---
title: SAIF.Framework.Preferences architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: <owner>
last_reviewed: 2026-07-23
library: SAIF.Framework.Preferences
reference_type: package
---

# SAIF.Framework.Preferences architecture

`SAIF.Framework.Preferences` is a small, self-contained data-access library that persists per-user application/page preference blobs. It consists of exactly two classes — the `Preference` DTO and the `PreferenceData` data-access class — and reads/writes a single Oracle table, `USER_PREFERENCE`, via inline SQL rather than the framework's standard business-object pattern.

## Responsibilities
- Provide a data-access surface for reading a single preference blob (`GetPreferences`), reading all preferences for a user (`GetPreferencesForUser`), and upserting/deleting a preference (`SavePreferences`).
- Represent one saved preference record as a simple DTO (`Preference`), carrying application name, page name, preference ID, and a deserialized key/value item dictionary.
- Normalize and scope preference identity by application name, user ID (lower-cased), and optional page name.
- Serialize/deserialize the stored preference payload as JSON (`PREFERENCE_TEXT`) using `JavaScriptSerializer`.

## Component boundaries
- `Preference` (`SAIF.Framework.Preferences\Preference.cs`): plain data-transfer object. Public fields `PreferenceId`, `ApplicationName`, `Pagename`, `PreferenceItems`; no behavior beyond a parameterless constructor.
- `PreferenceData` (`SAIF.Framework.Preferences\PreferenceData.cs`): the sole data-access class. Public methods `GetPreferences`, `GetPreferencesForUser`, `SavePreferences`; private helpers `InsertPref`, `UpdatePref`, `DeletePref`, `DoesPrefExist`. Constructed with an injected `IDataPortalInfo` (`DPI` field) supplied by the caller.
- External boundary — `SAIF.Framework` (built from `SAIF.Framework.Core`): supplies `IDataPortalInfo`, `SqlHelper.ResultSetQuery`, `SqlHelper.ScalarQuery<T>`, `SqlHelper.NewParm`, and `clsResultSet.DataRow`. This library has no `<ProjectReference>` to `SAIF.Framework.Core`; it references the compiled `SAIF.Framework.dll` via a `HintPath` binary reference.
- Database boundary — [USER_PREFERENCE](database-tables.md#user_preference) (Oracle), the single table this library reads and writes, keyed via the `USER_PREFERENCE_SEQ` sequence.

## Interaction model
- Host applications construct `PreferenceData` by supplying an `IDataPortalInfo` instance; this library does not resolve its own connection/portal context.
- `GetPreferences` builds a parameterized SELECT scoped by `USER_ID`/`APP_NAME`/optional `PAGE_NAME` (see [SQL-002](sql/SQL-002-get-preferences.md)), executes it via `SqlHelper.ResultSetQuery`, and returns the raw `PREFERENCE_TEXT` string (or `string.Empty` if no row matches).
- `GetPreferencesForUser` selects all rows for a `USER_ID` (see [SQL-003](sql/SQL-003-get-preferences-for-user.md)), builds one `Preference` object per row, and deserializes non-empty `PREFERENCE_TEXT` JSON into `PreferenceItems` via `JavaScriptSerializer`.
- `SavePreferences` is the single write entry point: it branches to `DeletePref` (see [SQL-006](sql/SQL-006-delete-preference.md)) when `preferenceText` is empty/whitespace, otherwise checks `DoesPrefExist` (see [SQL-007](sql/SQL-007-does-pref-exist.md)) and dispatches to `UpdatePref` (see [SQL-005](sql/SQL-005-update-preference.md)) or `InsertPref` (see [SQL-001](sql/SQL-001-insert-preference-sequence.md) and [SQL-004](sql/SQL-004-insert-preference.md)) accordingly. All three internal operations execute via `SqlHelper.ScalarQuery<int>`.
- There is no application/UI layer code in this library — it is a reusable component intended to be consumed by host applications.

## Architecture diagram
The following diagram shows the high-level component interactions for this library.

```mermaid
flowchart TD
	Caller["Host application"] --> PD["PreferenceData"]
	PD --> DTO["Preference (DTO)"]
	PD --> DPI["IDataPortalInfo<br>(injected by caller)"]
	PD --> SqlHelper["SqlHelper.ResultSetQuery /<br>SqlHelper.ScalarQuery&lt;T&gt;<br>(SAIF.Framework)"]
	SqlHelper --> DB[("USER_PREFERENCE<br>(Oracle)")]
```

## Sequence diagram
The following diagram shows the primary save (upsert/delete) request sequence.

```mermaid
sequenceDiagram
	participant Caller as Host application
	participant PD as PreferenceData
	participant SQL as SqlHelper
	participant DB as USER_PREFERENCE (Oracle)
	Caller->>PD: SavePreferences(appName, pageName, userId, preferenceText)
	alt preferenceText is empty/whitespace
		PD->>SQL: DeletePref (ScalarQuery)
		SQL->>DB: DELETE
	else preferenceText present
		PD->>SQL: DoesPrefExist (ScalarQuery)
		SQL->>DB: SELECT COUNT(*)
		DB-->>SQL: count
		alt row exists
			PD->>SQL: UpdatePref (ScalarQuery)
			SQL->>DB: UPDATE
		else no row
			PD->>SQL: InsertPref (ScalarQuery)
			SQL->>DB: INSERT (USER_PREFERENCE_SEQ.nextval)
		end
	end
	PD-->>Caller: true
```

## Design constraints
- **Deviation from standard BOF data-access pattern**: This library does not implement the standard `clsBusinessBase`/`clsBusinessCollectionBase` + `IBusinessCriteria` object-lifecycle pattern used elsewhere in the framework. There is no nested `Criteria` class and no `CreateSQL`/`RetrieveSQL`/`InsertSQL`/`UpdateSQL`/`DeleteSQL` properties. Instead, `PreferenceData` issues inline Oracle SQL directly via `SqlHelper.ResultSetQuery` and `SqlHelper.ScalarQuery<T>`. This is a deliberate architectural deviation that should be noted by developers expecting the standard business-object model.
- The library targets a single table ([USER_PREFERENCE](database-tables.md#user_preference)) with no joins or foreign-key relationships; all 7 cataloged SQL statements (see [SQL catalog](sql/index.md)) reference only this table.
- The `SAIF.Framework` dependency is resolved as a compiled binary reference (`HintPath`) rather than a `<ProjectReference>`, though the local `.sln` builds against the sibling `SAIF.Framework.Core` project's output.*
- No configuration retrieval mechanism exists in this library (no `IEnvironmentService`, `ConfigurationManager.AppSettings`, `Web.config`/`App.config`, or `DbConnectionManager` usage) — the database/portal execution context is entirely supplied by the caller through the injected `IDataPortalInfo`.
- No authentication, authorization, or segregation-of-duties logic exists in this library; `userId` is used purely as a data key for scoping `USER_PREFERENCE` rows, not as an authorization decision point.
- **Open question — possible duplicate implementation**: A separate `PreferenceData.vb` implementing similar preference persistence exists at `SAIF.Framework.Core\Preferences\PreferenceData.vb`. That file belongs to `SAIF.Framework.Core`, not `SAIF.Framework.Preferences`, and was out of scope for this analysis. Its relationship to this library's `PreferenceData` (duplicate, legacy predecessor, or independent implementation) is unconfirmed.**

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

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

## Sources
- SAIF.Framework.Preferences\Preference.cs
- SAIF.Framework.Preferences\PreferenceData.cs
- SAIF.Framework.Preferences\SAIF.Framework.Preferences.csproj
- SAIF.Framework.Core\V1\DataAccessLayer\SqlHelper.vb
- SAIF.Framework.Core\Preferences\PreferenceData.vb
