The purpose of this topic is to list frequently asked questions (FAQs) related to developing for Aptify.
-
What is the usage pattern for Aptify Framework database transaction methods?
-
What happens when multiple requests to begin a transaction are in a single thread?
- What method is the way to assign database writes to different transactions?
- After a transaction has been committed, any additional database operations result in exceptions. Is this the correct behavior?
-
If a transaction is started but not committed or rolled back, what happens to it?
What is the usage pattern for Aptify Framework database transaction methods?
Can you clarify how transaction-related methods (BeginTransaction, CommitTransaction, etc.) are intended to be used with the Aptify Framework? Given that they are named very similar to their SQL counterparts, can it be assumed that they are also similar in function, so in the simplest case, the pattern could be:
Private Sub MyMethod() Try DataAction.BeginTransaction() <execute code involving multiple database writes> DataAction.CommitTransaction() Catch ex As Exception If DataAction.InTransaction() Then DataAction.RollbackTransaction() End If End Try End Sub |
Is this usage pattern correct, and would this guarantee that the database writes either all succeed, or all fail, assuming there are no outer transactions?
Answer
When you call BeginTransaction, it returns a string representing the ID of the Transaction. This is used by Aptify Data Services Layer to manage the Transaction. You should capture that ID and pass it in to all operations that should participate in the Transaction. When you do this, you are assured that Aptify includes the operation in the same Transaction. When you do not do this, you are relying on Aptify Default Transaction behavior, which is legacy behavior that is still supported. The Default Transaction is set when a transaction is started in Aptify and there is no Default Transaction in place. The TransactionID is stored in the UserCredentials object in the DefaultTransactionID property. When a Data Services Layer call is made and no TransactionID is provided (or it is provided as an empty string), the Data Services Layer will check the UserCredentials DefaultTransactionID property and use it if one is available. In a single user, single threaded environment, this is a helpful function, especially for developers that do not understand transaction management. This allows operations to participate in the default transaction so that it is not blocked by previous operations on the open transaction. In a multi-user and/or multi-threaded environment, this is almost always undesired behavior. This function remains because there is a configuration attribute that will turn off the Default Transaction behavior, Aptify.Framework.DataServices.SQLDataServices.DataAction.CanUseDefaultTransaction. Set it to False and the Data Services Layer will not execute operations under the Default Transaction. It will only process transactions based on the TransactionID parameter passed in. The problem with this is that many configurations and even some core code has been written to work with the Default Transaction behavior and will fail without it. Aptify has made an effort to improve our core code to explicitly pass TransactionID whenever a transaction is started and until it has ended. That is the best practice for all Transaction code. Save the TransactionID and pass it through ever method that accepts the TransactionID parameter.
What happens when multiple requests to begin a transaction are in a single thread?
What happens when multiple request are in a single thread, as show below?
DataAction.BeginTransaction() ... DataAction.BeginTransaction() |
Are multiple transactions returned? If so, are they nested or in parallel?
- If nested, how does the Aptify framework handle the commit/rollback behavior? SQL Server doesn't directly support committing or rolling back inner transactions in a chain of nested transactions.
- If in parallel, is it possible to commit/rollback the transactions separately, using the transaction ID?
Answer
When you call BeginTransaction for a new Transaction, Aptify issues a command to SQL Server to begin a database transaction. The ADO.NET connection to the database tied to that transaction is kept and used by all Data Services Layer operations on that server. It also sets an internal counter to 1. When BeginTransaction is called again for a TransactionID that is already active, the internal counter is incremented by one. When you call CommitTransaction, the internal counter is decremented until it reaches 0. When the internal counter is 0, Aptify issues a command to SQL Server to commit the database transaction. When RollbackTransaction is called, Aptify issues the rollback transaction command to the SQL Server database, regardless of the value of the internal counter. RollbackTransaction terminates the transaction with a database transaction rollback. When you start a transaction with a BeginTransaction call, you must ensure that your code will finalize the transaction on all code paths. For each an every call to BeginTransaction, there must be a corresponding call to CommitTransaction on the success path. On the fail path, there should be a RollbackTransaction if the transaction should not be committed to the database. Note, this is a single transaction. It is possible to create a separate, parallel transaction that is not dependant on any other active transaction. This is done by calling the BeginTransaction and setting the NewTransaction boolean parameter to True. This will return a new TransactionID and you will pass that to all other Database operations that you want to execute on that TransactionID. This is rarely done and great care should be taken to properly manage all the active transactions through finalization.
What method is the way to assign database writes to different transactions?
Is the GEObject.Save(AllowGUI, ErrorString, TransactionID) method the way to assign database writes to different transactions? If parallel transactions are not supported, what is this Save method used for?
Answer
Anything that accepts a TransactionID uses that value to pass through to all Data Services Layer calls within that method (or methods it calls). If TransactionID is not provided or is an empty string, Aptify will use the Default Transaction, if it is active. Best practice is to pass in the TransactionID if you've created or have been passed a TransactionID.
After a transaction has been committed, any additional database operations result in exceptions. Is this the correct behavior?
After a transaction has been committed, even database read operations like instantiating a GE object can cause an exception with the message: "The transaction [id=xx] is no longer active." Is this the correct behavior? Would another transaction have to be started to perform any additional database operations?
Answer
Once a transaction has been finalized, the TransactionID value is no longer valid and should not be used. If you are seeing this with core Aptify code, it's possible that the Default Transaction behavior is causing problems in a multi-threaded environment. In that case, where the same UserCredentials object is shared by multiple threads, there are opportunities for the DefaultTransactionID property to be retrieved by one thread while another thread finalizes the transaction before the original thread submits the Data Services Layer call with the TransactionID that is no longer valid. In that case, report it to Aptify Technical Support so that it can be investigated.
If a transaction is started but not committed or rolled back, what happens to it?
If a transaction is started, but not committed or rolledback, what happens to it?
Answer
If you have a hanging transaction, you have a bad situation. With the open transaction and Default Transaction behavior, a single user, say in the Windows Desktop client, may continue to operate in what appears to be a normal manner. All of the operations will occur on the transaction and any locks created by that transaction will not block operations on that same transaction. It will appear that records are created and updated. All the while, more and more locks are accumulating. Other users will be blocked and the system may become unusable for all other users. The User with the open transaction then closes Aptify and the connection with the open transaction is killed. SQL Server now rolls back the transaction and all data modifications in that transaction are eliminated. The locks are removed and the system returns to normal usage. The original User logs in and wonders where all the work that they did has gone. There's no record of it in the database because the transaction rolled back. A nefarious error that underscores the importance of finalizing the transaction on all code paths.
Comments
Please sign in to leave a comment.