---
title: Multi-database portal registration
summary: Registers keyed SQL Server or Oracle data-portal instances against an ApplicationDataAccess instance for downstream business-object use.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-07-30
library: SAIF.Application
reference_type: package
operation_id: OP-APP-05
trigger: The manager sets connection properties on ApplicationDataAccess and calls CreateSQLServerPortal or CreateOraclePortal
---

# Multi-database portal registration

`ApplicationDataAccess` is the multi-portal registration helper that an `AppManagerBase` subclass uses (typically from its `OnCreateDataLayer()` hook) to build one or more keyed `IDataPortalInfo` entries — one per logical database connection the manager needs — backed by either a SQL Server or an Oracle data portal.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-APP-05 |
| **Trigger** | Consuming code sets `DBUser`, `Database`, `Server`, and/or `BatchID` on an `ApplicationDataAccess` instance and calls `CreateSQLServerPortal(key)` or `CreateOraclePortal(key)` |
| **Inputs** | `DBUser`, `Database`, `Server`, `BatchID` connection properties; the registration `key` string; optionally a batch user ID supplied to the constructor |
| **Outputs** | A stored, keyed `IDataPortalInfo` entry retrievable through the `DPI(key)` property; the underlying `clsDataAccess` (`DAL`) instance used to add the portal |
| **Primary components** | `ApplicationDataAccess.New`, `ApplicationDataAccess.CreateSQLServerPortal`, `ApplicationDataAccess.CreateOraclePortal`, `ApplicationDataAccess.DPI`, `AppManagerBase.InjectDPI` |
| **Entry point** | `Application\SAIF.Application\ApplicationDataAccess.vb` |

## Steps

1. **Construct the data-access object.** `New(Optional inBatchUserID As String = "")` initializes a fresh `clsDataAccess.clsEnvironment` and either:
   - constructs `clsDataAccess(inBatchUserID)` and sets `m_Env.ClientID = inBatchUserID` when a non-blank batch user ID is supplied, or
   - constructs the parameterless `clsDataAccess` and sets `m_Env.ClientID = m_DAL.ClientID` from the resulting DAL when no batch user ID is given.
   - This constructor is normally invoked indirectly, through `AppManagerBase.DataLayer`'s lazy getter, which passes `SecurityPrincipal.UserName.ToString` as the batch user ID — see [Application context initialization](application-context-initialization.md).
2. **Set connection properties.** Consuming code (typically the subclass's `OnCreateDataLayer()`) sets `DBUser`, `Database`, `Server`, and optionally `BatchID` on the `ApplicationDataAccess` instance before registering any portal. These are plain auto-implemented properties with no validation.
3. **Register a SQL Server portal.** `CreateSQLServerPortal(inKey As String)`:
   1. Copies `m_DAL.ClientID` into `m_Env.ClientID`.
   2. Copies `DBUser`, `Database`, and `Server` into the environment.
   3. Sets `m_Env.DBMSType = clsDataAccess.clsEnvironment.DBMS.SqlServer`.
   4. If `BatchID.Trim.Length > 0`: overwrites `m_Env.ClientID` with `BatchID`, so a configured batch ID takes precedence over the DAL's own client ID ([BR-005](../business-rules.md#br-005-batch-id-overrides-the-data-access-client-id-for-portal-environments)).
   5. Constructs `New clsSQLDataPortal(m_Env, True)` and adds it to `m_DPILibrary` under the lower-cased key, via `DAL.AddDataPortal(portal, portal.PortalID & inKey)`.
   - On failure: this method calls `m_DPILibrary.Add(inKey.ToLower, ...)` directly rather than through the `DPI` property setter, so it does **not** perform the duplicate-key guard that the `DPI` setter enforces — a second call with the same key raises a runtime "key already exists" exception from the underlying `SortedList` instead of being silently ignored.*
4. **Register an Oracle portal.** `CreateOraclePortal(inKey As String)` follows the same sequence as step 3 but sets `m_Env.DBMSType = clsDataAccess.clsEnvironment.DBMS.Oracle`, omits the `Server` assignment (Oracle connections are identified by `Database` and `DBUser` only in this environment shape), and constructs `New clsMSOracleDataPortal(m_Env)` instead of the SQL Server portal type ([BR-005](../business-rules.md#br-005-batch-id-overrides-the-data-access-client-id-for-portal-environments)).
   - Same direct-`Add` caveat as step 3 applies — a duplicate key raises an exception rather than being ignored.*
5. **Retrieve a registered portal.** Any caller reads `DPI(key)` to get the stored `IDataPortalInfo`, which looks the key up by its lower-cased form in `m_DPILibrary`.
   - On failure: requesting a key that was never registered raises a `KeyNotFoundException` from the underlying `SortedList`, since the getter performs no existence check.*
6. **Inject a portal without going through Create\*Portal.** `AppManagerBase.InjectDPI(inKey, inDPI)` sets `DataLayer.DPI(inKey) = inDPI` directly, which — unlike steps 3–4 — routes through the `DPI` property's `Friend` setter. That setter lower-cases the key and only adds the entry when `ContainsKey` is `False`, so an injected key that already exists is silently ignored rather than replacing the prior entry or raising ([BR-004](../business-rules.md#br-004-data-portal-keys-are-registered-case-insensitively-and-only-once)).

## Process flow

The following diagram visualizes portal registration for both database types and the two different registration entry points.

```mermaid
flowchart TD
	Start([ApplicationDataAccess constructed]) --> SetProps[Set DBUser, Database,<br>Server, BatchID]
	SetProps --> Which{SQL Server or Oracle?}
	Which -->|SQL Server| SqlEnv[Copy ClientID, DBUser,<br>Database, Server; set DBMS=SqlServer]
	Which -->|Oracle| OraEnv[Copy ClientID, DBUser,<br>Database; set DBMS=Oracle]
	SqlEnv --> BatchCheck1{BatchID set?}
	OraEnv --> BatchCheck2{BatchID set?}
	BatchCheck1 -->|Yes| OverrideClient1[ClientID = BatchID]
	BatchCheck1 -->|No| KeepClient1[Keep DAL ClientID]
	BatchCheck2 -->|Yes| OverrideClient2[ClientID = BatchID]
	BatchCheck2 -->|No| KeepClient2[Keep DAL ClientID]
	OverrideClient1 --> BuildSql[New clsSQLDataPortal]
	KeepClient1 --> BuildSql
	OverrideClient2 --> BuildOra[New clsMSOracleDataPortal]
	KeepClient2 --> BuildOra
	BuildSql --> AddDirect[m_DPILibrary.Add key,<br>duplicate key throws]
	BuildOra --> AddDirect
	AddDirect --> Stored([Portal available via DPI key]))
	InjectPath([AppManagerBase.InjectDPI]) --> Setter[DPI setter: add only<br>if key not already present]
	Setter --> Stored
```

## Failure and edge-case behavior

- `CreateSQLServerPortal` and `CreateOraclePortal` add to `m_DPILibrary` directly instead of through the guarded `DPI` property setter, so registering the same key twice through either `Create*Portal` method throws, while re-injecting the same key through `InjectDPI` is silently ignored — the two registration paths are inconsistent with each other ([BR-004](../business-rules.md#br-004-data-portal-keys-are-registered-case-insensitively-and-only-once)).*
- Neither `CreateSQLServerPortal` nor `CreateOraclePortal` validates that `DBUser`, `Database`, or `Server` (for SQL Server) are non-blank before building the portal — a portal can be constructed from an empty connection environment with no explicit error from this library.*
- `CreateOraclePortal` never sets `m_Env.Server`, so any previously-set `Server` value on the shared `m_Env.clsEnvironment` instance persists across a mixed sequence of SQL Server and Oracle portal creation calls on the same `ApplicationDataAccess` instance, since `m_Env` is reused rather than recreated per call.*
- Reading `DPI(key)` for an unregistered key raises a `KeyNotFoundException`; there is no `TryGetValue`-style accessor exposed by this class.*

> **Note:** Statements marked with `*` were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts.

## Related documentation

- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Dependencies](../dependencies.md)

## Sources

- `Application\SAIF.Application\ApplicationDataAccess.vb`
- `Application\SAIF.Application\AppManagerBase.vb`
