@@ -7,11 +7,13 @@ When migrating from Oracle to PostgreSQL, a critical difference exists in how **
77## The Core Difference
88
99** Oracle Behavior:**
10+
1011- Refcursor output parameters automatically expose their result set to the data reader
1112- Client code can immediately access result columns
1213- No additional commands needed
1314
1415** PostgreSQL Behavior:**
16+
1517- Refcursor output parameters return a ** cursor name** (e.g., ` "<unnamed portal 1>" ` )
1618- The cursor remains open in the database session
1719- Client must execute ` FETCH ALL FROM "<cursor_name>" ` to retrieve actual data
@@ -27,10 +29,10 @@ System.IndexOutOfRangeException: Field not found in row: <column_name>
2729
2830This occurs because the data reader contains only the refcursor parameter itself, not the actual query results.
2931
30-
3132## Database Stored Procedure Pattern
3233
3334### Oracle Stored Procedure
35+
3436``` sql
3537CREATE OR REPLACE PROCEDURE get_users(
3638 p_department_id IN NUMBER ,
4648```
4749
4850### PostgreSQL Stored Procedure
51+
4952``` sql
5053CREATE OR REPLACE PROCEDURE get_users(
5154 p_department_id IN INTEGER ,
52- cur_result INOUT refcursor
55+ cur_result OUT refcursor
5356)
5457LANGUAGE plpgsql
5558AS $$
6366$$;
6467```
6568
66- ** Key Difference:** PostgreSQL returns the cursor itself as an ` INOUT ` parameter, not the result set.
67-
68-
69+ ** Key Difference:** PostgreSQL returns the cursor itself as an ` OUT ` parameter, not the result set.
6970
7071## Client Code Solution (C#)
7172
7273### Problematic Oracle-Style Code
74+
7375This code works with Oracle but fails with PostgreSQL:
7476
7577``` csharp
@@ -260,8 +262,6 @@ public IEnumerable<User> GetUsers(int departmentId)
260262}
261263```
262264
263-
264-
265265## Transactional Context
266266
267267When working within transactions, ensure the ` FETCH ` command uses the same transaction:
@@ -312,11 +312,10 @@ public IEnumerable<User> GetUsersInTransaction(
312312}
313313```
314314
315-
316-
317315## Debugging Tips
318316
319317### Before Fix - What You'll See
318+
320319When inspecting the data reader after executing a refcursor procedure without proper unwrapping:
321320
322321``` csharp
@@ -330,6 +329,7 @@ var userId = reader.GetInt32(reader.GetOrdinal("user_id"));
330329```
331330
332331### After Fix - What You Should See
332+
333333After properly fetching from the cursor:
334334
335335``` csharp
@@ -343,8 +343,6 @@ Console.WriteLine($"Field 2: {reader.GetName(2)}"); // Output: "email"
343343var userId = reader .GetInt32 (reader .GetOrdinal (" user_id" )); // Works correctly
344344```
345345
346-
347-
348346## Comparison Table: Oracle vs. PostgreSQL Refcursor Handling
349347
350348| Aspect | Oracle (ODP.NET) | PostgreSQL (Npgsql) |
@@ -356,8 +354,6 @@ var userId = reader.GetInt32(reader.GetOrdinal("user_id")); // Works correctly
356354| ** Cursor Lifetime** | Managed automatically by driver | Remains open; must manage explicitly |
357355| ** Transaction Handling** | Transparent | ` FETCH ` must use same transaction context |
358356
359-
360-
361357## Best Practices
362358
3633591 . ** Centralize refcursor handling** - Create a generic helper method to avoid duplicating unwrapping logic across your codebase
0 commit comments