--- name: migrating-oracle-to-postgres-data-access-code description: 'Migrates .NET/C# data access code from Oracle to PostgreSQL (Npgsql). Replaces Oracle NuGet packages, rewrites OracleConnection/OracleCommand/OracleDataReader usage, fixes DbType mappings, updates stored procedure invocation patterns, and adapts connection string configuration. Use when migrating the application code layer of a .NET project during an Oracle-to-PostgreSQL database migration.' --- # Migrating .NET Data Access Code from Oracle to PostgreSQL Migrate the C# data access layer of a single `.Postgres`-copy project from Oracle (Oracle.ManagedDataAccess) to PostgreSQL (Npgsql). Work item by item through `Reports/{ProjectName}/MigrationChecklist.md`. ## Prerequisites - The `.Postgres` project copy exists (created in Phase 5 setup). - `Reports/{ProjectName}/MigrationChecklist.md` exists and is the source of truth for what to change. - `Reports/{ProjectName}/OracleRiskAnalysis.md` exists for cross-referencing behavioral differences. ## Workflow ``` Progress: - [ ] Step 1: Replace NuGet packages - [ ] Step 2: Update connection string configuration - [ ] Step 3: Rewrite ADO.NET type references - [ ] Step 4: Fix DbType mappings - [ ] Step 5: Migrate stored procedure invocation - [ ] Step 6: Address Oracle-specific SQL and syntax - [ ] Step 7: Build and verify ``` **Step 1: Replace NuGet packages** In the `.csproj` of the `.Postgres` project: - Remove: `Oracle.ManagedDataAccess.Core`, `Oracle.EntityFrameworkCore` (and any other `Oracle.*` packages) - Add: `Npgsql` (for ADO.NET) and/or `Npgsql.EntityFrameworkCore.PostgreSQL` (for EF Core) - Keep version pinning consistent with the target .NET version; do not introduce newer package versions than what the solution already uses for similar packages. - If `System.Data` abstractions (`IDbConnection`, `IDbCommand`) are used project-wide, the surface-level code may need fewer changes — identify them first. **Step 2: Update connection string configuration** - Locate the Oracle connection string in `appsettings.json`, `appsettings.{env}.json`, `web.config`, `app.config`, or environment variable configuration. - Replace with a Npgsql-compatible connection string: `Host=localhost;Port=5432;Database=mydb;Username=myuser;Password=mypassword` - Do not hardcode credentials — use the same configuration mechanism already in use (e.g., environment variables, secrets manager, `IConfiguration`). - Update any named connection string keys only if they were Oracle-specific (e.g., `OracleConnection`). Prefer keeping the same key name to minimize application config changes. **Step 3: Rewrite ADO.NET type references** Replace Oracle-specific ADO.NET types with Npgsql equivalents: | Oracle type | Npgsql replacement | |---|---| | `OracleConnection` | `NpgsqlConnection` | | `OracleCommand` | `NpgsqlCommand` | | `OracleDataReader` | `NpgsqlDataReader` | | `OracleDataAdapter` | `NpgsqlDataAdapter` | | `OracleParameter` | `NpgsqlParameter` | | `OracleTransaction` | `NpgsqlTransaction` | | `OracleException` | `NpgsqlException` | | `OracleDbType` | `NpgsqlDbType` (from `NpgsqlTypes` namespace) | Update `using` directives accordingly (`using Oracle.ManagedDataAccess.Client` → `using Npgsql`). If the codebase uses `IDbConnection`/`IDbCommand` abstractions registered via DI, update only the DI registration and connection string — the consuming code may not need changes. **Step 4: Fix DbType and NpgsqlDbType mappings** Oracle parameter types do not map 1:1 to Npgsql. Review every `OracleParameter` (now `NpgsqlParameter`) that sets an explicit type: | Oracle type | Notes | |---|---| | `OracleDbType.Varchar2` | Use `NpgsqlDbType.Varchar` or omit (Npgsql infers from value) | | `OracleDbType.Clob` | Use `NpgsqlDbType.Text` | | `OracleDbType.Number` | Use `NpgsqlDbType.Numeric` or `NpgsqlDbType.Integer` depending on precision | | `OracleDbType.Date` | Use `NpgsqlDbType.Date` (date only) or `NpgsqlDbType.Timestamp` (if time component used) | | `OracleDbType.TimeStamp` | Use `NpgsqlDbType.Timestamp` | | `OracleDbType.RefCursor` | Use `NpgsqlDbType.Refcursor` — see Step 5 | | `OracleDbType.Char` | Use `NpgsqlDbType.Char` | For parameters where Oracle inferred the type from the value, Npgsql also infers — explicit type setting is often unnecessary and can be removed. **Step 5: Migrate stored procedure invocation** Oracle and PostgreSQL stored procedure invocation differ significantly: - **Command type**: Retain `CommandType.StoredProcedure` for function calls. For procedures that use `OUT` parameters, PostgreSQL requires `CommandType.Text` with `CALL proc_name(...)` syntax in some versions of Npgsql — verify against the target Npgsql version. - **RefCursor handling**: Oracle returns ref cursors as output parameters; PostgreSQL returns them differently: - For `RETURNS TABLE` / `RETURNS SETOF`, use `ExecuteReader()` directly — no cursor parameter needed. - For `RETURNS refcursor`, call within a transaction, read the cursor name from the output parameter, then issue `FETCH ALL IN ""`. - Remove any Oracle-specific cursor-wrapping code (e.g., `OracleRefCursor`). - **OUT parameters**: PostgreSQL stored procedures use `INOUT` or function return values. Verify parameter direction matches the migrated procedure signature. - **Sequence `NEXTVAL`**: Replace `SELECT {SEQUENCE}.NEXTVAL FROM DUAL` with `SELECT nextval('{sequence_name}')`. - **Named parameters**: Npgsql uses `@param_name`; Oracle used `:param_name`. Update all parameter name prefixes. **Step 6: Address Oracle-specific SQL and C# patterns** Review inline SQL strings and query builders for Oracle-specific constructs and replace: | Oracle construct | PostgreSQL replacement | |---|---| | `ROWNUM <= n` | `LIMIT n` | | `ROWNUM = 1` | `LIMIT 1` | | `NVL(x, y)` | `COALESCE(x, y)` | | `DECODE(expr, v1, r1, ...)` | `CASE WHEN expr = v1 THEN r1 ... END` | | `SYSDATE` / `SYSTIMESTAMP` | `NOW()` or `CURRENT_TIMESTAMP` | | `TO_CHAR(date, fmt)` | `TO_CHAR(date, fmt)` (mostly compatible; verify format strings) | | `TO_DATE(str, fmt)` | `TO_DATE(str, fmt)` (verify format strings) | | `TO_NUMBER(str)` | `CAST(str AS NUMERIC)` or `str::NUMERIC` | | `||` string concat | `||` (compatible) | | `DUAL` table | Remove `FROM DUAL`; PostgreSQL evaluates `SELECT expr` without a table | | `CONNECT BY` hierarchy | Rewrite using recursive CTEs (`WITH RECURSIVE`) | | `MERGE INTO` | Rewrite as `INSERT ... ON CONFLICT DO UPDATE` | | Empty string `''` as NULL | Oracle treats `''` as NULL; PostgreSQL does not — check comparisons and `IS NULL` guards | | `VARCHAR2` | `VARCHAR` or `TEXT` | **Step 7: Build and verify** After addressing all checklist items: 1. Run `dotnet build` on the `.Postgres` project. Fix any remaining compilation errors. 2. Verify no Oracle-specific namespaces remain: search for `Oracle.ManagedDataAccess`, `OracleConnection`, `OracleCommand`, `:param` patterns. 3. Mark completed items in `Reports/{ProjectName}/MigrationChecklist.md`. ## EF Core projects If the project uses `Oracle.EntityFrameworkCore`: - Replace the provider registration in `DbContext` configuration: `.UseOracle(...)` → `.UseNpgsql(...)` - Replace `OracleDbContextOptionsBuilder` references. - Review `OnModelCreating` for Oracle-specific configurations (e.g., `HasColumnType("NUMBER")` → `HasColumnType("numeric")`). - Sequence configuration: `modelBuilder.HasSequence("seq_name").StartsAt(1).IncrementsBy(1)` syntax is compatible; verify column defaults referencing sequences. - Do not run EF Core migrations — schema is managed externally via DDL scripts (Phase 4). ## Key Constraints - Work only within the `.Postgres` copy — never modify the original Oracle-targeting project. - Keep to existing .NET and C# versions; do not introduce newer language or runtime features. - Preserve comments and application logic; change only what is necessary for PostgreSQL compatibility. - Oracle is the source of truth — behavioral differences must be documented as bug reports, not silently altered.