5.5 KiB
Transactions and Disposal
DynamORM manages connections, command pools, and transaction stacks internally.
Connection and Transaction Options
DynamicDatabaseOptions controls behavior:
SingleConnectionSingleTransactionConnectionPoolingSupportSchemaSupportStoredProceduresSupportNoLockSupportTop/SupportLimitOffset/SupportFirstSkip
Typical setup:
var options =
DynamicDatabaseOptions.SingleConnection |
DynamicDatabaseOptions.SingleTransaction |
DynamicDatabaseOptions.SupportLimitOffset |
DynamicDatabaseOptions.SupportSchema;
Internal Connection Pooling
Without SingleConnection, DynamORM normally opens and closes a managed connection for each operation.
That is fine when the provider already has efficient connection pooling. It is not enough for providers that do not.
ConnectionPooling enables internal managed connection reuse:
var options =
DynamicDatabaseOptions.ConnectionPooling |
DynamicDatabaseOptions.SupportLimitOffset |
DynamicDatabaseOptions.SupportSchema;
using (var db = new DynamicDatabase(factory, connectionString, options))
{
db.ConnectionPoolingKeepOpenCount = 32;
db.ConnectionPoolingMaximumOpenCount = 128;
db.ConnectionPoolingConnectionLifetime = TimeSpan.FromHours(1);
}
Behavior:
- idle connections are reused instead of opened and closed repeatedly
- up to
ConnectionPoolingKeepOpenCountidle connections are kept open - no more than
ConnectionPoolingMaximumOpenCountmanaged connections are kept at once - if the maximum is reached, callers wait until a connection is released
- idle pooled connections older than
ConnectionPoolingConnectionLifetimeare retired even if the preferred idle count has not been exceeded
Default values:
ConnectionPoolingKeepOpenCount = 32ConnectionPoolingMaximumOpenCount = 128ConnectionPoolingConnectionLifetime = 1 hour
Transaction Usage
using (var db = new DynamicDatabase(factory, connectionString, options))
using (var conn = db.Open())
using (var tx = conn.BeginTransaction())
using (var cmd = conn.CreateCommand())
{
cmd.SetCommand("UPDATE users SET first = 'Ada' WHERE id = 1").ExecuteNonQuery();
tx.Commit();
}
Global transaction mode is also available via db.BeginTransaction().
Direct Database Transactions and Thread Ownership
db.BeginTransaction() is different from conn.BeginTransaction() on an arbitrary opened connection.
When you call db.BeginTransaction():
- DynamORM binds one managed connection to the current thread
- commands executed through that
DynamicDatabaseon the same thread reuse that connection and transaction - other threads do not join that transaction automatically
- other threads use another managed connection or wait for one to become available
This is important because it prevents unrelated work from another thread from accidentally running inside the transaction started on the original thread.
Example:
using (var tx = db.BeginTransaction())
{
db.Execute("UPDATE sample_users SET code = 'A' WHERE id = 1");
db.Execute("UPDATE sample_users SET code = 'B' WHERE id = 2");
tx.Commit();
}
All commands above run on the same transaction-bound connection because they execute on the same thread.
If another thread uses the same db instance while that transaction is active, it does not participate in this transaction unless user code explicitly routes work back to the owning thread.
Single Connection and Single Transaction Locking
SingleConnection and SingleTransaction are now guarded by internal execution serialization.
That means:
- only one command executes at a time per
DynamicDatabaseinstance when either option is enabled - if another thread tries to execute a command at the same time, it waits until the current execution finishes
- this reduces the amount of user-side locking needed for providers such as SQLite
Important limit:
- a reader keeps the execution slot until the reader is disposed
So if one thread executes ExecuteReader() and holds the reader open, another thread waits until that reader is closed or disposed before executing the next command on the same DynamicDatabase instance.
Disposal Guarantees
Current disposal behavior includes idempotent guards on key objects:
DynamicDatabaseDynamicConnectionDynamicCommandDynamicTransactionDynamicTable- builder and parser helpers
This prevents repeated cleanup from throwing or re-disposing lower-level resources.
Pooling and Rollback Behavior
Behavior validated by DynamORM.Tests/Helpers/PoolingTests.cs:
- Disposing the database invalidates active commands.
- Open transactions are rolled back during disposal when not committed.
Behavior validated by DynamORM.Tests/Helpers/ConnectionPoolingAndLockingTests.cs:
- pooled connections are reused
- pooled connections wait when the configured maximum is reached
- expired idle connections are retired
- direct database transactions are isolated to the owning thread
SingleConnectionandSingleTransactionserialize command execution across threads
Practices
- Prefer
usingblocks forDynamicDatabase, connections, commands, transactions, and builders. - Do not manually re-dispose the same object from multiple ownership paths unless
IsDisposedis checked. - Keep transaction scope short and explicit.
- Use
db.BeginTransaction()when you want DynamORM-managed transactional flow across multiple commands on the current thread. - Use
ConnectionPoolingwhen the underlying provider does not give you adequate pooling on its own.