Error when using batching on ODP.NET version 4.122.23.1

Posts   
 
    
wlhaslet
User
Posts: 2
Joined: 25-Sep-2025
# Posted on: 25-Sep-2025 18:16:53   

Hello LLBLGen team,

We're running into an error when using a batch size of 2 or higher with Oracle Data Provider version 4.122.23.1. Batching with this version of ODP.NET creates an actionQuery with various null properties (including Command.CommandText) that results in a null reference exception when the command text is fetched. Using a batch size of 0 or 1 prevents the error, and using ODP.NET version 4.122.21.1 also prevents the error. Trying to use the ORM profiler resulted in the same error at get_CommandText, so the error causing queries aren't logged. ODP.NET package versions 23.8.0 and 23.9.1 both cause the error.

Could the source of this issue be an incompatibility of our version of LLBLGen (5.8.3) with the latest ODP.NET?

Below are the exception message, stack trace, and version numbers. Thanks for your help!

The Import has been terminated at 09/25/2025 13:01:04 UTC.
 Reason: ProductEmployeeImportManager_ProcessUserChunk ThreadNumber: 1 The following exception occurred at System.String get_CommandText():
Object reference not set to an instance of an object.
Stack Trace:
   at Oracle.ManagedDataAccess.Client.OracleCommand.get_CommandText()
   at Grb.Platform.Framework.Business.Lower.DatabaseCommon.DataAccessAdapterUtility.GetCommandText(IActionQuery actionQuery) in C:\Source\Production\Platform\Framework\Business.Lower\DatabaseCommon\DataAccessAdapterUtility.cs:line 113
   at Grb.Platform.Framework.Business.Lower.DatabaseCommon.DataAccessAdapterUtility.OnSaveEntityComplete(IActionQuery saveQuery, IEntity2 entityToSave, RecoveryStrategyBase activeRecoveryStrategy) in C:\Source\Production\Platform\Framework\Business.Lower\DatabaseCommon\DataAccessAdapterUtility.cs:line 52
   at Grb.Platform.Framework.Business.Lower.Oracle.DatabaseSpecific.DataAccessAdapter.OnSaveEntityComplete(IActionQuery saveQuery, IEntity2 entityToSave) in C:\Source\Production\Platform\Framework\Business.Lower\Oracle (ODP.NET)\DatabaseSpecific\CustomDataAccessAdapterCode.cs:line 45
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.<PersistQueue>b__57_1(IActionQuery q, EntityBase2 e)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.PerformPostPersistenceQueryExecuted(PackedActionQuery packedQuery, Int32 amountSaved)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.PerformPostActionQueryWork(Int32 resultActionQuery, PackedActionQuery packedQuery)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.ExecuteElements(List`1 elementsToRun)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.Execute(ActionQueueElement`1 actionQueueElement, IActionQuery query, Type typeOfNextElement)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.PersistQueue(List`1 queueToPersist, Boolean insertActions)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.SaveEntity(IEntity2 entityToSave, Boolean refetchAfterSave, IPredicateExpression updateRestriction, Boolean recurse)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.<>c__DisplayClass19_0.<SaveEntity>b__0()
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.ExecuteWithActiveRecoveryStrategy[T](Func`1 toExecute)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.SaveEntity(IEntity2 entityToSave, Boolean refetchAfterSave, IPredicateExpression updateRestriction, Boolean recurse)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.SaveEntity(IEntity2 entityToSave, Boolean refetchAfterSave, Boolean recurse)
   at Grb.Platform.Business.HR.BusinessManager.SetEmployeeEntity_SaveChildren(EmpGeneralEntity employeeEntity, IDataAccessAdapter externalAdapter, Boolean reFetchAfterSave) in C:\Source\Production\Platform\Business\HR\BusinessManager.vb:line 11123

The error occuring on a retry:

The following exception occurred at System.String get_CommandText():
Object reference not set to an instance of an object.

Stack Trace:
   at Oracle.ManagedDataAccess.Client.OracleCommand.get_CommandText()
   at Grb.Platform.Framework.Business.Lower.DatabaseCommon.DataAccessAdapterUtility.GetCommandText(IActionQuery actionQuery) in C:\Source\Production\Platform\Framework\Business.Lower\DatabaseCommon\DataAccessAdapterUtility.cs:line 113
   at Grb.Platform.Framework.Business.Lower.DatabaseCommon.DataAccessAdapterUtility.OnSaveEntityComplete(IActionQuery saveQuery, IEntity2 entityToSave, RecoveryStrategyBase activeRecoveryStrategy) in C:\Source\Production\Platform\Framework\Business.Lower\DatabaseCommon\DataAccessAdapterUtility.cs:line 52
   at Grb.Platform.Framework.Business.Lower.Oracle.DatabaseSpecific.DataAccessAdapter.OnSaveEntityComplete(IActionQuery saveQuery, IEntity2 entityToSave) in C:\Source\Production\Platform\Framework\Business.Lower\Oracle (ODP.NET)\DatabaseSpecific\CustomDataAccessAdapterCode.cs:line 45
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.<PersistQueue>b__57_1(IActionQuery q, EntityBase2 e)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.PerformPostPersistenceQueryExecuted(PackedActionQuery packedQuery, Int32 amountSaved)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.PerformPostActionQueryWork(Int32 resultActionQuery, PackedActionQuery packedQuery)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.ExecuteElements(List`1 elementsToRun)
   at SD.LLBLGen.Pro.ORMSupportClasses.ActionQueryController.Execute(ActionQueueElement`1 actionQueueElement, IActionQuery query, Type typeOfNextElement)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.PersistQueue(List`1 queueToPersist, Boolean insertActions)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterCore.SaveEntity(IEntity2 entityToSave, Boolean refetchAfterSave, IPredicateExpression updateRestriction, Boolean recurse)
   at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.<>c__DisplayClass19_0.<SaveEntity>b__0()
   at SD.LLBLGen.Pro.ORMSupportClasses.RecoveryStrategyBase.Execute[TResult](Func`1 toExecute)

LLBLGen Pro version + buildnr: 5.8.3

Runtime library version: 5.8.3

.NET version: .NET Framework 4.7.2

Database Version: Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production

Data Provider: Oracle.ManagedDataAccess version 4.122.23.1, packages versions 23.8.0 and 23.9.1

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39972
Joined: 17-Aug-2003
# Posted on: 26-Sep-2025 09:56:40   

I can't reproduce it on .net 6 with 23.9.1 on oracle 12c, using LLBLGen Pro v5.12

(Queries from ormprofiler) Inserts:

BEGIN
    SELECT SCOTT.SEQ_DEP.NEXTVAL
    INTO   NULL /* :p2 */
    FROM   DUAL;

    INSERT INTO SCOTT.DEPT
                (DNAME,
                 DEPTNO,
                 LOC)
    VALUES      ('NewDep' /* :p1 */,
                 NULL /* :p2 */,
                 'foo' /* :p3 */);

    SELECT SCOTT.SEQ_DEP.NEXTVAL
    INTO   NULL /* :p5 */
    FROM   DUAL;

    INSERT INTO SCOTT.DEPT
                (DNAME,
                 DEPTNO,
                 LOC)
    VALUES      ('NewDep2' /* :p4 */,
                 NULL /* :p5 */,
                 'Bar' /* :p6 */);

    SELECT SCOTT.SEQ_DEP.NEXTVAL
    INTO   NULL /* :p8 */
    FROM   DUAL;

    INSERT INTO SCOTT.DEPT
                (DNAME,
                 DEPTNO,
                 LOC)
    VALUES      ('NewDep3' /* :p7 */,
                 NULL /* :p8 */,
                 'Bar2' /* :p9 */);
END;

updates:

BEGIN
    0 /* :pLLBLROWCOUNT */ := 0;

    UPDATE SCOTT.DEPT
    SET    DNAME = 'NewDepx' /* :p1 */
    WHERE  (SCOTT.DEPT.DEPTNO = 44 /* :p2 */);

    0 /* :pLLBLROWCOUNT */ := 0 /* :pLLBLROWCOUNT */ + SQL%ROWCOUNT;

    UPDATE SCOTT.DEPT
    SET    DNAME = 'NewDep2x' /* :p3 */
    WHERE  (SCOTT.DEPT.DEPTNO = 45 /* :p4 */);

    0 /* :pLLBLROWCOUNT */ := 0 /* :pLLBLROWCOUNT */ + SQL%ROWCOUNT;

    UPDATE SCOTT.DEPT
    SET    DNAME = 'NewDep3x' /* :p5 */
    WHERE  (SCOTT.DEPT.DEPTNO = 46 /* :p6 */);

    0 /* :pLLBLROWCOUNT */ := 0 /* :pLLBLROWCOUNT */ + SQL%ROWCOUNT;
END;

As the crash happens in your code, please check if something else is wrong, and if the queries are ran without you obtaining the query from it. (you can see that for instance by enabling tracing).

The main idea is that the IActionQuery instances are packed in a packed query and then, after the batch has run, they're passed to the OnSaveEntityComplete method. As the queries on oracle are disposed, it might be they're no longer available, but that's just a wild guess. I couldn't find an issue being fixed related to this after 5.8.3 so I don't think that's it either.

Frans Bouma | Lead developer LLBLGen Pro
wlhaslet
User
Posts: 2
Joined: 25-Sep-2025
# Posted on: 15-Oct-2025 16:21:01   

Thank you for your previous response. I've had a chance to look into the issue a bit more, and found the cause to be that the IActionQuery passed into OnSaveEntityComplete has already been disposed. The dispose appears to happen in CreatePackedQueryFromCollectedElements. Is this what you refered to when you said "the queries on oracle are disposed"?

The IActionQuery passed to OnSaveEntityComplete is also disposed when using the 2021 version of ODP.NET, but the properties of Command like CommandText are still available. When using the 2023 version of ODP.NET, CommandText and other properties are null. Is this the expected behavior, and the IActionQuery passed to OnSaveEntityComplete can't have it's CommandText and other Command properties read on the latest ODP.NET when using batching?

Thanks for your help!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39972
Joined: 17-Aug-2003
# Posted on: 16-Oct-2025 09:21:21   

As the command has been completed, it's indeed no longer available at that stage. it's sadly a side effect of ODP.NET which requires these early disposes. What is your use case for this requirement? As there are other ways to obtain the query

Frans Bouma | Lead developer LLBLGen Pro