---
title: SAIF.Framework.Preferences operations and processes
summary: Operation catalog and step-level workflow hub for preference retrieval and persistence.
status: draft
owner: SAIF Team
last_reviewed: 2026-07-30
library: SAIF.Framework.Preferences
reference_type: package
---

# SAIF.Framework.Preferences operations and processes

`SAIF.Framework.Preferences` exposes a small, focused surface for reading and writing user preference data: two read operations and one save entry point that internally routes to delete, update, or insert. This page is the operation catalog and cross-operation hub for the library; each operation's detailed, step-by-step logic lives in its own page under `operations/`.

## Operation catalog
| Operation | Intent | Trigger | Inputs | Outputs | Rules | Tables | SQL |
| --- | --- | --- | --- | --- | --- | --- | --- |
| [Retrieve single preference blob](operations/retrieve-single-preference-blob.md) | Read one stored preference payload for an application/user/page scope. | Caller invokes `PreferenceData.GetPreferences`. | `appName`, `pageName`, `userId` | `string` payload or `string.Empty` | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-003](business-rules.md#br-003-preference-identity-is-scoped-by-app-user-and-optional-page) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-002](sql/SQL-002-get-preferences.md) |
| [Retrieve all preferences for a user](operations/retrieve-all-preferences-for-user.md) | Load every stored preference row for one user and hydrate DTOs. | Caller invokes `PreferenceData.GetPreferencesForUser`. | `userId` | `List<Preference>` | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-006](business-rules.md#br-006-preference-item-payload-is-json-encoded-keyvalue-data) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-003](sql/SQL-003-get-preferences-for-user.md) |
| [Save preference](operations/save-upsert-delete-preference.md) | Route a save request to delete, update, or insert behavior. | Caller invokes `PreferenceData.SavePreferences`. | `appName`, `pageName`, `userId`, `preferenceText` | `bool` or exception | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-002](business-rules.md#br-002-empty-preference-text-triggers-deletion), [BR-003](business-rules.md#br-003-preference-identity-is-scoped-by-app-user-and-optional-page), [BR-004](business-rules.md#br-004-update-is-skipped-when-stored-value-is-unchanged), [BR-005](business-rules.md#br-005-preference-text-is-trimmed-before-storage) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-001](sql/SQL-001-insert-preference-sequence.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) |
| [Insert preference row](operations/insert-preference-row.md) | Create a new preference row with inline sequence-generated identity. | `SavePreferences` calls `InsertPref` after `DoesPrefExist` returns `false`. | `appName`, `pageName`, `userId`, `preferenceText` | `true` after the insert request is issued | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-003](business-rules.md#br-003-preference-identity-is-scoped-by-app-user-and-optional-page), [BR-005](business-rules.md#br-005-preference-text-is-trimmed-before-storage) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-001](sql/SQL-001-insert-preference-sequence.md), [SQL-004](sql/SQL-004-insert-preference.md) |
| [Update preference row](operations/update-preference-row.md) | Replace stored preference text and audit columns when the value changed. | `SavePreferences` calls `UpdatePref` after `DoesPrefExist` returns `true`. | `appName`, `pageName`, `userId`, `preferenceText` | `true` after the update request is issued | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-003](business-rules.md#br-003-preference-identity-is-scoped-by-app-user-and-optional-page), [BR-004](business-rules.md#br-004-update-is-skipped-when-stored-value-is-unchanged), [BR-005](business-rules.md#br-005-preference-text-is-trimmed-before-storage) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-005](sql/SQL-005-update-preference.md) |
| [Delete preference row](operations/delete-preference-row.md) | Remove the scoped preference row. | `SavePreferences` calls `DeletePref` for blank preference text. | `appName`, `pageName`, `userId`, `preferenceText` | `true` after the delete request is issued | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-003](business-rules.md#br-003-preference-identity-is-scoped-by-app-user-and-optional-page) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-006](sql/SQL-006-delete-preference.md) |
| [Check preference existence](operations/check-preference-existence.md) | Count matching rows before choosing update versus insert. | `SavePreferences` calls `DoesPrefExist`. | `appName`, `pageName`, `userId` | `bool` | [BR-001](business-rules.md#br-001-user-ids-are-normalized-to-lower-case), [BR-003](business-rules.md#br-003-preference-identity-is-scoped-by-app-user-and-optional-page) | [USER_PREFERENCE](database-tables.md#user_preference) | [SQL-007](sql/SQL-007-does-pref-exist.md) |

<!-- Legacy anchors preserved for existing cross-references from unchanged pages. -->
<a id="retrieve-single-preference-blob"></a>
<a id="retrieve-all-preferences-for-a-user"></a>
<a id="save-upsertdelete-preference"></a>
<a id="insert-preference-row"></a>
<a id="update-preference-row"></a>
<a id="delete-preference-row"></a>
<a id="check-preference-existence"></a>

## Process flow diagram
The diagram below traces a caller request from entry point through the read/save split and, on the save path, through the delete/update/insert branch that `SavePreferences` selects.

```mermaid
flowchart TD
	Start([Caller request]) --> Choice{Read or save?}
	Choice -->|Read one| GetOne[Retrieve single preference blob]
	Choice -->|Read all| GetAll[Retrieve all preferences for a user]
	Choice -->|Save| Save[Save preference]
	Save --> Empty{Trimmed text empty?}
	Empty -->|Yes| Delete[Delete preference row]
	Empty -->|No| Exists[Check preference existence]
	Exists -->|Yes| Update[Update preference row]
	Exists -->|No| Insert[Insert preference row]
	GetOne --> EndRead([Return payload])
	GetAll --> EndList([Return preference list])
	Delete --> EndSave([Return true])
	Update --> EndSave
	Insert --> EndSave
```

## Inherited operations and processes

Every SQL statement in this library ultimately executes through one shared dependency operation. That operation originates outside this library and is embedded, not reimplemented, wherever it is used.

### From `SqlHelper` / `IDataPortalInfo` (`SAIF.Framework.Core`)

| Operation | Intent | Provided by | Origin | Embedded in |
| --- | --- | --- | --- | --- |
| [Data portal request execution](../saif-framework-core/operations-processes.md#data-portal-request-execution) | Route inline SQL plus bind parameters through the configured portal and return a result. | `SqlHelper.ResultSetQuery`, `SqlHelper.ScalarQuery<T>`, `IDataPortalInfo` | `SAIF.Framework.Core` | [Retrieve single preference blob](operations/retrieve-single-preference-blob.md), [Retrieve all preferences for a user](operations/retrieve-all-preferences-for-user.md), [Insert preference row](operations/insert-preference-row.md), [Update preference row](operations/update-preference-row.md), [Delete preference row](operations/delete-preference-row.md), [Check preference existence](operations/check-preference-existence.md) |

> **Origin**: These operations are defined in `SAIF.Framework.Core` / `SqlHelper` and `IDataPortalInfo` and inherited or delegated by this component. Their step-by-step logic is embedded inline (with `> Origin` callouts) in the consuming operation's detail page.

## Database interactions

Every operation in this library ultimately touches the same single table. The table below summarizes which operations read from or write to it; per-step SQL and table citations live in each operation's detail page under `operations/`.

| Operation | SQL | Tables | Type |
| --- | --- | --- | --- |
| [Retrieve single preference blob](operations/retrieve-single-preference-blob.md) | [SQL-002](sql/SQL-002-get-preferences.md) | [USER_PREFERENCE](database-tables.md#user_preference) | Read |
| [Retrieve all preferences for a user](operations/retrieve-all-preferences-for-user.md) | [SQL-003](sql/SQL-003-get-preferences-for-user.md) | [USER_PREFERENCE](database-tables.md#user_preference) | Read |
| [Save preference](operations/save-upsert-delete-preference.md) | [SQL-001](sql/SQL-001-insert-preference-sequence.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) | [USER_PREFERENCE](database-tables.md#user_preference) | Read / Write |
| [Insert preference row](operations/insert-preference-row.md) | [SQL-001](sql/SQL-001-insert-preference-sequence.md), [SQL-004](sql/SQL-004-insert-preference.md) | [USER_PREFERENCE](database-tables.md#user_preference) | Write |
| [Update preference row](operations/update-preference-row.md) | [SQL-005](sql/SQL-005-update-preference.md) | [USER_PREFERENCE](database-tables.md#user_preference) | Write |
| [Delete preference row](operations/delete-preference-row.md) | [SQL-006](sql/SQL-006-delete-preference.md) | [USER_PREFERENCE](database-tables.md#user_preference) | Write |
| [Check preference existence](operations/check-preference-existence.md) | [SQL-007](sql/SQL-007-does-pref-exist.md) | [USER_PREFERENCE](database-tables.md#user_preference) | Read |

## Failure and edge-case behavior
- `GetPreferences` returns `string.Empty` when no row matches the supplied scope, rather than returning `null` or throwing.
- `GetPreferencesForUser` leaves `Preference.PreferenceItems` as an empty dictionary when `PREFERENCE_TEXT` trims to an empty string.
- `SavePreferences` calls `preferenceText.Trim()` before any null check; a `null` `preferenceText` raises `NullReferenceException` before delete/update/insert routing begins.
- `UpdatePref` and `DeletePref` ignore the affected-row count and still return `true`, including no-op updates or deletes that match zero rows.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.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\PreferenceData.cs
- SAIF.Framework.Preferences\Preference.cs
- SAIF.Framework.Core\V1\DataAccessLayer\SqlHelper.vb
- SAIF.Framework.Core\V1\DataAccessLayer\clsResultSet.vb
