TCIS.Pluggable.Persistence.Oracle 1.0.8-rc.2

This is a prerelease version of TCIS.Pluggable.Persistence.Oracle.
dotnet add package TCIS.Pluggable.Persistence.Oracle --version 1.0.8-rc.2
                    
NuGet\Install-Package TCIS.Pluggable.Persistence.Oracle -Version 1.0.8-rc.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TCIS.Pluggable.Persistence.Oracle" Version="1.0.8-rc.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TCIS.Pluggable.Persistence.Oracle" Version="1.0.8-rc.2" />
                    
Directory.Packages.props
<PackageReference Include="TCIS.Pluggable.Persistence.Oracle" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TCIS.Pluggable.Persistence.Oracle --version 1.0.8-rc.2
                    
#r "nuget: TCIS.Pluggable.Persistence.Oracle, 1.0.8-rc.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TCIS.Pluggable.Persistence.Oracle@1.0.8-rc.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TCIS.Pluggable.Persistence.Oracle&version=1.0.8-rc.2&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=TCIS.Pluggable.Persistence.Oracle&version=1.0.8-rc.2&prerelease
                    
Install as a Cake Tool

TCIS.Pluggable.Persistence.Oracle

Oracle provider for the Pluggable multi-tenant data layer. Supports all three tiers — Virtual Private Database, schema per tenant, and database per tenant.

Shared behaviour — model composition, query filters, audit stamping — lives in TCIS.Pluggable.Persistence.EntityFrameworkCore.


1. Registration

dotnet add package TCIS.Pluggable.Persistence.Oracle
builder.Services.AddPluggableOracle<TosDbContext>(commandTimeoutSeconds: 30);

Dapper only, no DbContext (a worker that reads existing tables with raw SQL):

builder.Services.AddPluggableOracleConnections(commandTimeoutSeconds: 30);
// inject IDapperContext

Both register IDbConnectionFactory per tenant and IDapperContext, and use commandTimeoutSeconds as the default timeout for every Dapper call that does not pass its own.

Call Connection string from the Tenant Store
Query* ReadConnectionString, else ConnectionString
Execute*, ExecuteCustom* ConnectionString only — never the read one

Read a scalar (SELECT COUNT(*)) with QuerySingleOrDefaultAsync<T>, not ExecuteScalarAsync: Execute* is the write path, so on a read-only tenant it is refused. Blank ReadConnectionString ("" or whitespace) counts as not set and falls back to ConnectionString.

A tenant that must only be read (for example a site running in parallel for comparison) declares only ReadConnectionString with a read-only database user: reads work, and every write is refused before it reaches the database (Oracle write connection string is missing).

Do not pass a connection string. It is resolved per request from the Tenant Store through IMultiTenantContextAccessor (the non-generic accessor, so a host declaring its own tenant type such as MyTenantInfo : TenantInfo works too).


2. Tier support

Tier Mechanism Identifier used
Tier 1 — Row level (VPD) DBMS_SESSION.SET_CONTEXT('TCIS_CTX', 'TENANT_ID', …) TenantInfo.Id (GUID)
Tier 2 — Schema per tenant ALTER SESSION SET CURRENT_SCHEMA TenantInfo.Identifier (site code)
Tier 3 — Database per tenant no-op initializer —

Tier 2 takes the schema name from TenantInfo.Identifier, not TenantInfo.Id. The identifier becomes an Oracle schema name and must pass the identifier whitelist; a GUID contains - and is always rejected.

In Oracle a schema is a user, so Tier 2 means one database user per tenant. Grant the application account the privileges to switch into each of them, and nothing more.


3. Tier 1 — application context and VPD

OracleRlsConnectionInitializer sets an application context on Open() and clears it on Close(). Your VPD policy function reads it back:

CREATE OR REPLACE FUNCTION sec.tenant_predicate(
    schema_name IN VARCHAR2, table_name IN VARCHAR2) RETURN VARCHAR2 AS
BEGIN
    RETURN 'TENANT_ID = SYS_CONTEXT(''TCIS_CTX'', ''TENANT_ID'')';
END;

The context namespace TCIS_CTX must be created with a trusted package so that only that package can set it — otherwise any session could set its own tenant id and defeat the policy:

CREATE CONTEXT TCIS_CTX USING sec.tenant_ctx_pkg;

A blank tenant id runs the cleanup command, clearing any context left over from a pooled session. The predicate then matches no row: fail-closed.


4. Tier status

Tier 3 is the simplest design here (a no-op initializer) but has no automated test coverage yet, and tenant resolution is its single point of failure — if the wrong connection string is resolved, nothing downstream will catch it. Ironically it is also the tier most likely to be chosen by a large port.

Treat a Tier 3 rollout as needing its own isolation test before go-live.


5. Pitfalls

# Pitfall Consequence
1 Using a GUID as the Tier 2 identifier Whitelist rejection — use the site code
2 Creating TCIS_CTX without a trusted package Any session can set its own tenant id and bypass VPD
3 Granting the application account EXEMPT ACCESS POLICY VPD no longer applies to it — the defence is gone
4 Assuming the EF query filter protects Dapper queries It does not; on Tier 1 only VPD stands in the way
5 Passing commandTimeout: 0 to a Dapper call Oracle waits forever — 0 overrides the registered default
6 Deploying Tier 3 without an isolation test Tenant resolution is the only thing preventing a cross-tenant connection
7 Dapper on a library version without named binding (before the release after 1.0.8-rc.1) ODP.NET binds by position: two parameters declared in a different order than in the SQL swap values and return wrong rows silently; a parameter used twice raises ORA-01008. Fixed in the library: the tenant connection wrapper turns BindByName on. On an older version set OracleConfiguration.BindByName = true before the first connection

6. A note on terminology

FORCE ROW LEVEL SECURITY and BYPASSRLS are PostgreSQL concepts and have no Oracle equivalent. The Oracle counterparts are VPD policies plus the EXEMPT ACCESS POLICY privilege — do not port a PostgreSQL checklist verbatim.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.8-rc.2 0 10/22/2026
1.0.8-rc.1 28 10/22/2026
1.0.6 77 10/20/2026
1.0.6-rc.1 40 10/20/2026
1.0.5 79 10/19/2026
1.0.4 89 10/13/2026
1.0.3 90 10/13/2026
1.0.2 82 10/12/2026
1.0.2-rc.1 48 10/12/2026
1.0.1-rc.10 61 10/8/2026
1.0.1-rc.9 51 10/8/2026
1.0.1-rc.8 61 10/8/2026
1.0.1-rc.7 54 10/8/2026
1.0.1-rc.6 59 10/8/2026
1.0.1-rc.5 60 10/7/2026
1.0.1-rc.4 55 10/7/2026
1.0.1-rc.3 55 10/7/2026
1.0.1-rc.2 61 10/5/2026
1.0.1-rc.1 64 10/5/2026
1.0.0 97 10/1/2026
Loading failed