Add managed connection pooling and execution locking
This commit is contained in:
@@ -29,6 +29,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System;
|
||||
|
||||
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "This is a generated file which generates all the necessary support classes.")]
|
||||
@@ -1271,14 +1272,21 @@ namespace DynamORM
|
||||
/// <returns>The number of rows affected.</returns>
|
||||
public int ExecuteNonQuery()
|
||||
{
|
||||
IDisposable scope = null;
|
||||
try
|
||||
{
|
||||
scope = _db != null ? _db.AcquireExecutionScope() : null;
|
||||
return PrepareForExecution().ExecuteNonQuery();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DynamicQueryException(ex, this);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (scope != null)
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
/// <summary>Executes the <see cref="P:System.Data.IDbCommand.CommandText"/>
|
||||
/// against the <see cref="P:System.Data.IDbCommand.Connection"/>,
|
||||
@@ -1289,12 +1297,16 @@ namespace DynamORM
|
||||
/// <returns>An <see cref="T:System.Data.IDataReader"/> object.</returns>
|
||||
public IDataReader ExecuteReader(CommandBehavior behavior)
|
||||
{
|
||||
IDisposable scope = null;
|
||||
try
|
||||
{
|
||||
return PrepareForExecution().ExecuteReader(behavior);
|
||||
scope = _db != null ? _db.AcquireExecutionScope() : null;
|
||||
return new DynamicExecutionReader(PrepareForExecution().ExecuteReader(behavior), scope);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (scope != null)
|
||||
scope.Dispose();
|
||||
throw new DynamicQueryException(ex, this);
|
||||
}
|
||||
}
|
||||
@@ -1304,12 +1316,16 @@ namespace DynamORM
|
||||
/// <returns>An <see cref="T:System.Data.IDataReader"/> object.</returns>
|
||||
public IDataReader ExecuteReader()
|
||||
{
|
||||
IDisposable scope = null;
|
||||
try
|
||||
{
|
||||
return PrepareForExecution().ExecuteReader();
|
||||
scope = _db != null ? _db.AcquireExecutionScope() : null;
|
||||
return new DynamicExecutionReader(PrepareForExecution().ExecuteReader(), scope);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (scope != null)
|
||||
scope.Dispose();
|
||||
throw new DynamicQueryException(ex, this);
|
||||
}
|
||||
}
|
||||
@@ -1319,14 +1335,21 @@ namespace DynamORM
|
||||
/// <returns>The first column of the first row in the result set.</returns>
|
||||
public object ExecuteScalar()
|
||||
{
|
||||
IDisposable scope = null;
|
||||
try
|
||||
{
|
||||
scope = _db != null ? _db.AcquireExecutionScope() : null;
|
||||
return PrepareForExecution().ExecuteScalar();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DynamicQueryException(ex, this);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (scope != null)
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
/// <summary>Gets the <see cref="T:System.Data.IDataParameterCollection"/>.</summary>
|
||||
public IDataParameterCollection Parameters
|
||||
@@ -1384,7 +1407,7 @@ namespace DynamORM
|
||||
|
||||
IsDisposed = true;
|
||||
|
||||
if (_con != null)
|
||||
if (_con != null && db.CommandsPool != null)
|
||||
{
|
||||
List<IDbCommand> pool = db.CommandsPool.TryGetValue(_con.Connection);
|
||||
|
||||
@@ -1436,7 +1459,7 @@ namespace DynamORM
|
||||
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
|
||||
internal DynamicTransaction BeginTransaction(IsolationLevel? il, object custom, Action disposed)
|
||||
{
|
||||
return new DynamicTransaction(_db, this, _singleTransaction, il, disposed, null);
|
||||
return new DynamicTransaction(_db, this, _singleTransaction, il, disposed, custom);
|
||||
}
|
||||
#region IDbConnection Members
|
||||
|
||||
@@ -1546,6 +1569,13 @@ namespace DynamORM
|
||||
/// <summary>Dynamic database is a class responsible for managing database.</summary>
|
||||
public class DynamicDatabase : IExtendedDisposable
|
||||
{
|
||||
private sealed class ConnectionEntry
|
||||
{
|
||||
public bool External { get; set; }
|
||||
public int LeaseCount { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastReleasedUtc { get; set; }
|
||||
}
|
||||
#region Internal fields and properties
|
||||
|
||||
private DbProviderFactory _provider;
|
||||
@@ -1553,6 +1583,7 @@ namespace DynamORM
|
||||
private string _connectionString;
|
||||
private bool _singleConnection;
|
||||
private bool _singleTransaction;
|
||||
private bool _connectionPooling;
|
||||
private string _leftDecorator = "\"";
|
||||
private string _rightDecorator = "\"";
|
||||
private bool _leftDecoratorIsInInvalidMembersChars = true;
|
||||
@@ -1561,7 +1592,9 @@ namespace DynamORM
|
||||
private int? _commandTimeout = null;
|
||||
private long _poolStamp = 0;
|
||||
|
||||
private DynamicConnection _tempConn = null;
|
||||
private readonly object _executionSyncRoot = new object();
|
||||
private int _executionOwnerThreadId = -1;
|
||||
private int _executionLockDepth = 0;
|
||||
|
||||
/// <summary>Provides lock object for this database instance.</summary>
|
||||
internal readonly object SyncLock = new object();
|
||||
@@ -1593,6 +1626,12 @@ namespace DynamORM
|
||||
/// <remarks>Pool should contain dynamic commands instead of native ones.</remarks>
|
||||
internal Dictionary<IDbConnection, List<IDbCommand>> CommandsPool { get; private set; }
|
||||
|
||||
/// <summary>Gets connection metadata tracked by the database instance.</summary>
|
||||
private Dictionary<IDbConnection, ConnectionEntry> ConnectionEntries { get; set; }
|
||||
|
||||
/// <summary>Gets ambient transaction-bound connections keyed by managed thread identifier.</summary>
|
||||
private Dictionary<int, IDbConnection> AmbientTransactionConnections { get; set; }
|
||||
|
||||
/// <summary>Gets schema columns cache.</summary>
|
||||
internal Dictionary<string, Dictionary<string, DynamicSchemaColumn>> Schema { get; private set; }
|
||||
|
||||
@@ -1616,6 +1655,18 @@ namespace DynamORM
|
||||
/// <summary>Gets or sets command timeout.</summary>
|
||||
public int? CommandTimeout { get { return _commandTimeout; } set { _commandTimeout = value; _poolStamp = DateTime.Now.Ticks; } }
|
||||
|
||||
/// <summary>Gets or sets the preferred number of idle open connections kept in the internal pool.</summary>
|
||||
/// <remarks>Default value is <c>32</c>. Applies only when <see cref="DynamicDatabaseOptions.ConnectionPooling"/> is enabled.</remarks>
|
||||
public int ConnectionPoolingKeepOpenCount { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the maximum number of connections that may remain managed by the internal pool at once.</summary>
|
||||
/// <remarks>Default value is <c>128</c>. When this limit is reached, callers wait for a pooled connection to be released.</remarks>
|
||||
public int ConnectionPoolingMaximumOpenCount { get; set; }
|
||||
|
||||
/// <summary>Gets or sets how long an idle pooled connection may stay open before it is retired.</summary>
|
||||
/// <remarks>Default value is one hour. Applies only when <see cref="DynamicDatabaseOptions.ConnectionPooling"/> is enabled.</remarks>
|
||||
public TimeSpan ConnectionPoolingConnectionLifetime { get; set; }
|
||||
|
||||
/// <summary>Gets the database provider.</summary>
|
||||
public DbProviderFactory Provider { get { return _provider; } }
|
||||
|
||||
@@ -1804,6 +1855,14 @@ namespace DynamORM
|
||||
IsDisposed = false;
|
||||
InitCommon(connection.ConnectionString, options);
|
||||
TransactionPool.Add(connection, new Stack<IDbTransaction>());
|
||||
CommandsPool.Add(connection, new List<IDbCommand>());
|
||||
ConnectionEntries.Add(connection, new ConnectionEntry
|
||||
{
|
||||
External = true,
|
||||
LeaseCount = 0,
|
||||
CreatedUtc = DateTime.UtcNow,
|
||||
LastReleasedUtc = DateTime.UtcNow,
|
||||
});
|
||||
|
||||
if (!_singleConnection)
|
||||
throw new InvalidOperationException("This constructor accepts only connections with DynamicDatabaseOptions.SingleConnection option.");
|
||||
@@ -1815,10 +1874,16 @@ namespace DynamORM
|
||||
|
||||
_singleConnection = (options & DynamicDatabaseOptions.SingleConnection) == DynamicDatabaseOptions.SingleConnection;
|
||||
_singleTransaction = (options & DynamicDatabaseOptions.SingleTransaction) == DynamicDatabaseOptions.SingleTransaction;
|
||||
_connectionPooling = (options & DynamicDatabaseOptions.ConnectionPooling) == DynamicDatabaseOptions.ConnectionPooling;
|
||||
DumpCommands = (options & DynamicDatabaseOptions.DumpCommands) == DynamicDatabaseOptions.DumpCommands;
|
||||
ConnectionPoolingKeepOpenCount = 32;
|
||||
ConnectionPoolingMaximumOpenCount = 128;
|
||||
ConnectionPoolingConnectionLifetime = TimeSpan.FromHours(1);
|
||||
|
||||
TransactionPool = new Dictionary<IDbConnection, Stack<IDbTransaction>>();
|
||||
CommandsPool = new Dictionary<IDbConnection, List<IDbCommand>>();
|
||||
ConnectionEntries = new Dictionary<IDbConnection, ConnectionEntry>();
|
||||
AmbientTransactionConnections = new Dictionary<int, IDbConnection>();
|
||||
Schema = new Dictionary<string, Dictionary<string, DynamicSchemaColumn>>();
|
||||
RemainingBuilders = new List<IDynamicQueryBuilder>();
|
||||
#if !DYNAMORM_OMMIT_OLDSYNTAX
|
||||
@@ -3356,7 +3421,205 @@ namespace DynamORM
|
||||
|
||||
#region Connection
|
||||
|
||||
private bool UsesExecutionSerialization
|
||||
{
|
||||
get { return _singleConnection || _singleTransaction; }
|
||||
}
|
||||
private bool UsesManagedPooling
|
||||
{
|
||||
get { return _connectionPooling && !_singleConnection; }
|
||||
}
|
||||
private IDisposable EnterExecutionScope()
|
||||
{
|
||||
if (!UsesExecutionSerialization)
|
||||
return null;
|
||||
|
||||
int currentThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
lock (_executionSyncRoot)
|
||||
{
|
||||
while (_executionLockDepth > 0 && _executionOwnerThreadId != currentThreadId)
|
||||
Monitor.Wait(_executionSyncRoot);
|
||||
|
||||
_executionOwnerThreadId = currentThreadId;
|
||||
_executionLockDepth++;
|
||||
}
|
||||
return new ExecutionScope(this);
|
||||
}
|
||||
private void ExitExecutionScope()
|
||||
{
|
||||
if (!UsesExecutionSerialization)
|
||||
return;
|
||||
|
||||
lock (_executionSyncRoot)
|
||||
{
|
||||
if (_executionLockDepth > 0)
|
||||
_executionLockDepth--;
|
||||
|
||||
if (_executionLockDepth == 0)
|
||||
{
|
||||
_executionOwnerThreadId = -1;
|
||||
Monitor.PulseAll(_executionSyncRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
private sealed class ExecutionScope : IDisposable
|
||||
{
|
||||
private DynamicDatabase _db;
|
||||
|
||||
public ExecutionScope(DynamicDatabase db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
DynamicDatabase db = _db;
|
||||
if (db == null)
|
||||
return;
|
||||
|
||||
_db = null;
|
||||
db.ExitExecutionScope();
|
||||
}
|
||||
}
|
||||
internal IDisposable AcquireExecutionScope()
|
||||
{
|
||||
return EnterExecutionScope();
|
||||
}
|
||||
private bool IsConnectionAmbientForAnotherThread(IDbConnection connection, int currentThreadId)
|
||||
{
|
||||
return AmbientTransactionConnections.Any(x => x.Key != currentThreadId && x.Value == connection);
|
||||
}
|
||||
private void AddManagedConnection(IDbConnection connection, bool external)
|
||||
{
|
||||
TransactionPool.Add(connection, new Stack<IDbTransaction>());
|
||||
CommandsPool.Add(connection, new List<IDbCommand>());
|
||||
ConnectionEntries.Add(connection, new ConnectionEntry
|
||||
{
|
||||
External = external,
|
||||
LeaseCount = 0,
|
||||
CreatedUtc = DateTime.UtcNow,
|
||||
LastReleasedUtc = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
private IDbConnection CreateManagedConnection()
|
||||
{
|
||||
IDbConnection conn = _provider.CreateConnection();
|
||||
conn.ConnectionString = _connectionString;
|
||||
conn.Open();
|
||||
AddManagedConnection(conn, false);
|
||||
return conn;
|
||||
}
|
||||
private void EnsureConnectionIsOpen(IDbConnection connection)
|
||||
{
|
||||
if (connection != null && connection.State != ConnectionState.Open)
|
||||
connection.Open();
|
||||
}
|
||||
private void TrimIdleConnectionsUnderLock()
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
int idleCount = ConnectionEntries
|
||||
.Where(x => !x.Value.External)
|
||||
.Count(x => x.Value.LeaseCount == 0 && TransactionPool[x.Key].Count == 0 && CommandsPool[x.Key].Count == 0 && !AmbientTransactionConnections.ContainsValue(x.Key));
|
||||
|
||||
List<IDbConnection> toRemove = new List<IDbConnection>();
|
||||
|
||||
foreach (KeyValuePair<IDbConnection, ConnectionEntry> item in ConnectionEntries)
|
||||
{
|
||||
if (item.Value.External || item.Value.LeaseCount > 0)
|
||||
continue;
|
||||
|
||||
if (TransactionPool[item.Key].Count > 0 || CommandsPool[item.Key].Count > 0 || AmbientTransactionConnections.ContainsValue(item.Key))
|
||||
continue;
|
||||
|
||||
bool expired = ConnectionPoolingConnectionLifetime <= TimeSpan.Zero || now - item.Value.LastReleasedUtc >= ConnectionPoolingConnectionLifetime;
|
||||
bool abovePreferred = idleCount > ConnectionPoolingKeepOpenCount;
|
||||
|
||||
if (expired || abovePreferred)
|
||||
{
|
||||
toRemove.Add(item.Key);
|
||||
idleCount--;
|
||||
}
|
||||
}
|
||||
foreach (IDbConnection connection in toRemove)
|
||||
{
|
||||
ConnectionEntries.Remove(connection);
|
||||
TransactionPool.Remove(connection);
|
||||
CommandsPool.Remove(connection);
|
||||
|
||||
if (connection.State == ConnectionState.Open)
|
||||
connection.Close();
|
||||
|
||||
connection.Dispose();
|
||||
}
|
||||
}
|
||||
private IDbConnection GetOrCreateManagedConnectionUnderLock(out bool opened)
|
||||
{
|
||||
opened = false;
|
||||
int currentThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
if (_singleConnection)
|
||||
{
|
||||
IDbConnection single = ConnectionEntries.Count == 0 ? CreateManagedConnection() : ConnectionEntries.Keys.First();
|
||||
if (single.State != ConnectionState.Open)
|
||||
{
|
||||
single.Open();
|
||||
opened = true;
|
||||
}
|
||||
ConnectionEntries[single].LeaseCount++;
|
||||
return single;
|
||||
}
|
||||
IDbConnection ambient;
|
||||
if (AmbientTransactionConnections.TryGetValue(currentThreadId, out ambient))
|
||||
{
|
||||
EnsureConnectionIsOpen(ambient);
|
||||
ConnectionEntries[ambient].LeaseCount++;
|
||||
return ambient;
|
||||
}
|
||||
if (!UsesManagedPooling)
|
||||
{
|
||||
IDbConnection conn = CreateManagedConnection();
|
||||
opened = true;
|
||||
ConnectionEntries[conn].LeaseCount++;
|
||||
return conn;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
TrimIdleConnectionsUnderLock();
|
||||
|
||||
IDbConnection pooled = ConnectionEntries
|
||||
.Where(x => !x.Value.External && x.Value.LeaseCount == 0)
|
||||
.Where(x => TransactionPool[x.Key].Count == 0 && CommandsPool[x.Key].Count == 0)
|
||||
.Where(x => !IsConnectionAmbientForAnotherThread(x.Key, currentThreadId))
|
||||
.Select(x => x.Key)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (pooled != null)
|
||||
{
|
||||
if (pooled.State != ConnectionState.Open)
|
||||
{
|
||||
pooled.Open();
|
||||
opened = true;
|
||||
}
|
||||
ConnectionEntries[pooled].LeaseCount++;
|
||||
return pooled;
|
||||
}
|
||||
if (ConnectionEntries.Count < ConnectionPoolingMaximumOpenCount)
|
||||
{
|
||||
IDbConnection conn = CreateManagedConnection();
|
||||
opened = true;
|
||||
ConnectionEntries[conn].LeaseCount++;
|
||||
return conn;
|
||||
}
|
||||
Monitor.Wait(SyncLock);
|
||||
}
|
||||
}
|
||||
/// <summary>Open managed connection.</summary>
|
||||
/// <remarks>
|
||||
/// When <see cref="DynamicDatabaseOptions.ConnectionPooling"/> is enabled, DynamORM reuses idle managed connections instead of opening and closing a new one for every operation.
|
||||
/// When a transaction is started through <see cref="BeginTransaction()"/>, <see cref="BeginTransaction(IsolationLevel)"/>, or <see cref="BeginTransaction(object)"/>,
|
||||
/// commands executed on the same thread reuse the same underlying connection until that transaction finishes.
|
||||
/// Other threads do not join that transaction; they use another managed connection or wait for one to become available.
|
||||
/// </remarks>
|
||||
/// <returns>Opened connection.</returns>
|
||||
public IDbConnection Open()
|
||||
{
|
||||
@@ -3366,32 +3629,8 @@ namespace DynamORM
|
||||
|
||||
lock (SyncLock)
|
||||
{
|
||||
if (_tempConn == null)
|
||||
{
|
||||
if (TransactionPool.Count == 0 || !_singleConnection)
|
||||
{
|
||||
conn = _provider.CreateConnection();
|
||||
conn.ConnectionString = _connectionString;
|
||||
conn.Open();
|
||||
opened = true;
|
||||
|
||||
TransactionPool.Add(conn, new Stack<IDbTransaction>());
|
||||
CommandsPool.Add(conn, new List<IDbCommand>());
|
||||
}
|
||||
else
|
||||
{
|
||||
conn = TransactionPool.Keys.First();
|
||||
|
||||
if (conn.State != ConnectionState.Open)
|
||||
{
|
||||
conn.Open();
|
||||
opened = true;
|
||||
}
|
||||
}
|
||||
ret = new DynamicConnection(this, conn, _singleTransaction);
|
||||
}
|
||||
else
|
||||
ret = _tempConn;
|
||||
conn = GetOrCreateManagedConnectionUnderLock(out opened);
|
||||
ret = new DynamicConnection(this, conn, _singleTransaction);
|
||||
}
|
||||
if (opened)
|
||||
ExecuteInitCommands(ret);
|
||||
@@ -3405,40 +3644,46 @@ namespace DynamORM
|
||||
if (connection == null)
|
||||
return;
|
||||
|
||||
if (!_singleConnection && connection != null && TransactionPool.ContainsKey(connection))
|
||||
lock (SyncLock)
|
||||
{
|
||||
// Close all commands
|
||||
if (CommandsPool.ContainsKey(connection))
|
||||
{
|
||||
List<IDbCommand> tmp = CommandsPool[connection].ToList();
|
||||
tmp.ForEach(cmd => cmd.Dispose());
|
||||
if (IsDisposed || ConnectionEntries == null || TransactionPool == null || CommandsPool == null)
|
||||
return;
|
||||
|
||||
CommandsPool[connection].Clear();
|
||||
}
|
||||
// Rollback remaining transactions
|
||||
while (TransactionPool[connection].Count > 0)
|
||||
ConnectionEntry entry;
|
||||
if (!ConnectionEntries.TryGetValue(connection, out entry))
|
||||
return;
|
||||
|
||||
if (entry.LeaseCount > 0)
|
||||
entry.LeaseCount--;
|
||||
|
||||
bool hasAmbientOwner = AmbientTransactionConnections.ContainsValue(connection);
|
||||
bool hasTransactions = TransactionPool.ContainsKey(connection) && TransactionPool[connection].Count > 0;
|
||||
bool hasCommands = CommandsPool.ContainsKey(connection) && CommandsPool[connection].Count > 0;
|
||||
|
||||
if (_singleConnection || hasAmbientOwner || hasTransactions || hasCommands || entry.LeaseCount > 0)
|
||||
{
|
||||
IDbTransaction trans = TransactionPool[connection].Pop();
|
||||
trans.Rollback();
|
||||
trans.Dispose();
|
||||
Monitor.PulseAll(SyncLock);
|
||||
return;
|
||||
}
|
||||
// Close connection
|
||||
entry.LastReleasedUtc = DateTime.UtcNow;
|
||||
|
||||
if (UsesManagedPooling && !entry.External)
|
||||
{
|
||||
TrimIdleConnectionsUnderLock();
|
||||
Monitor.PulseAll(SyncLock);
|
||||
return;
|
||||
}
|
||||
ConnectionEntries.Remove(connection);
|
||||
TransactionPool.Remove(connection);
|
||||
CommandsPool.Remove(connection);
|
||||
|
||||
if (connection.State == ConnectionState.Open)
|
||||
connection.Close();
|
||||
|
||||
// remove from pools
|
||||
lock (SyncLock)
|
||||
{
|
||||
TransactionPool.Remove(connection);
|
||||
CommandsPool.Remove(connection);
|
||||
}
|
||||
// Set stamp
|
||||
_poolStamp = DateTime.Now.Ticks;
|
||||
|
||||
// Dispose the corpse
|
||||
connection.Dispose();
|
||||
connection = null;
|
||||
Monitor.PulseAll(SyncLock);
|
||||
}
|
||||
connection.Dispose();
|
||||
}
|
||||
/// <summary>Gets or sets contains commands executed when connection is opened.</summary>
|
||||
public List<string> InitCommands { get; set; }
|
||||
@@ -3457,60 +3702,59 @@ namespace DynamORM
|
||||
#region Transaction
|
||||
|
||||
/// <summary>Begins a global database transaction.</summary>
|
||||
/// <remarks>Using this method connection is set to single open
|
||||
/// connection until all transactions are finished.</remarks>
|
||||
/// <remarks>
|
||||
/// Using this method binds one managed connection to the current thread until all direct database transactions on that thread are finished.
|
||||
/// Commands executed through the same <see cref="DynamicDatabase"/> instance on that thread reuse that transaction-bound connection.
|
||||
/// Other threads do not participate in that transaction and use another managed connection or wait for one to become available.
|
||||
/// </remarks>
|
||||
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
|
||||
public IDbTransaction BeginTransaction()
|
||||
{
|
||||
_tempConn = Open() as DynamicConnection;
|
||||
|
||||
return _tempConn.BeginTransaction(null, null, () =>
|
||||
{
|
||||
Stack<IDbTransaction> t = TransactionPool.TryGetValue(_tempConn.Connection);
|
||||
|
||||
if (t == null | t.Count == 0)
|
||||
{
|
||||
_tempConn.Dispose();
|
||||
_tempConn = null;
|
||||
}
|
||||
});
|
||||
return BeginAmbientTransaction(null, null);
|
||||
}
|
||||
/// <summary>Begins a database transaction with the specified
|
||||
/// <see cref="System.Data.IsolationLevel"/> value.</summary>
|
||||
/// <remarks>Thread ownership and connection binding follow the same rules as <see cref="BeginTransaction()"/>.</remarks>
|
||||
/// <param name="il">One of the <see cref="System.Data.IsolationLevel"/> values.</param>
|
||||
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
|
||||
public IDbTransaction BeginTransaction(IsolationLevel il)
|
||||
{
|
||||
_tempConn = Open() as DynamicConnection;
|
||||
|
||||
return _tempConn.BeginTransaction(il, null, () =>
|
||||
{
|
||||
Stack<IDbTransaction> t = TransactionPool.TryGetValue(_tempConn.Connection);
|
||||
|
||||
if (t == null | t.Count == 0)
|
||||
{
|
||||
_tempConn.Dispose();
|
||||
_tempConn = null;
|
||||
}
|
||||
});
|
||||
return BeginAmbientTransaction(il, null);
|
||||
}
|
||||
/// <summary>Begins a database transaction with the specified
|
||||
/// <see cref="System.Data.IsolationLevel"/> value.</summary>
|
||||
/// custom provider argument.</summary>
|
||||
/// <remarks>Thread ownership and connection binding follow the same rules as <see cref="BeginTransaction()"/>.</remarks>
|
||||
/// <param name="custom">Custom parameter describing transaction options.</param>
|
||||
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
|
||||
public IDbTransaction BeginTransaction(object custom)
|
||||
{
|
||||
_tempConn = Open() as DynamicConnection;
|
||||
return BeginAmbientTransaction(null, custom);
|
||||
}
|
||||
private IDbTransaction BeginAmbientTransaction(IsolationLevel? il, object custom)
|
||||
{
|
||||
DynamicConnection connection = Open() as DynamicConnection;
|
||||
int threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
|
||||
return _tempConn.BeginTransaction(null, custom, () =>
|
||||
lock (SyncLock)
|
||||
AmbientTransactionConnections[threadId] = connection.Connection;
|
||||
|
||||
return connection.BeginTransaction(il, custom, () =>
|
||||
{
|
||||
Stack<IDbTransaction> t = TransactionPool.TryGetValue(_tempConn.Connection);
|
||||
bool releaseConnection = false;
|
||||
|
||||
if (t == null | t.Count == 0)
|
||||
lock (SyncLock)
|
||||
{
|
||||
_tempConn.Dispose();
|
||||
_tempConn = null;
|
||||
Stack<IDbTransaction> t = TransactionPool.TryGetValue(connection.Connection);
|
||||
|
||||
if (t == null || t.Count == 0)
|
||||
{
|
||||
AmbientTransactionConnections.Remove(threadId);
|
||||
releaseConnection = true;
|
||||
Monitor.PulseAll(SyncLock);
|
||||
}
|
||||
}
|
||||
if (releaseConnection)
|
||||
connection.Dispose();
|
||||
});
|
||||
}
|
||||
#endregion Transaction
|
||||
@@ -3567,10 +3811,14 @@ namespace DynamORM
|
||||
{
|
||||
TransactionPool.Clear();
|
||||
CommandsPool.Clear();
|
||||
ConnectionEntries.Clear();
|
||||
AmbientTransactionConnections.Clear();
|
||||
RemainingBuilders.Clear();
|
||||
|
||||
TransactionPool = null;
|
||||
CommandsPool = null;
|
||||
ConnectionEntries = null;
|
||||
AmbientTransactionConnections = null;
|
||||
RemainingBuilders = null;
|
||||
}
|
||||
ClearSchema();
|
||||
@@ -3578,7 +3826,6 @@ namespace DynamORM
|
||||
_proc.Dispose();
|
||||
|
||||
_proc = null;
|
||||
_tempConn = null;
|
||||
IsDisposed = true;
|
||||
}
|
||||
/// <summary>Gets a value indicating whether this instance is disposed.</summary>
|
||||
@@ -3595,11 +3842,17 @@ namespace DynamORM
|
||||
None = 0x00000000,
|
||||
|
||||
/// <summary>Only single persistent database connection.</summary>
|
||||
/// <remarks>Command execution is serialized inside one <see cref="DynamicDatabase"/> instance when this option is enabled.</remarks>
|
||||
SingleConnection = 0x00000001,
|
||||
|
||||
/// <summary>Only one transaction.</summary>
|
||||
/// <remarks>Command execution is serialized inside one <see cref="DynamicDatabase"/> instance when this option is enabled.</remarks>
|
||||
SingleTransaction = 0x00000002,
|
||||
|
||||
/// <summary>Use internal connection pooling when connections are not kept as a single shared connection.</summary>
|
||||
/// <remarks>Pooling reuses idle managed connections and is configured through <see cref="DynamicDatabase.ConnectionPoolingKeepOpenCount"/>, <see cref="DynamicDatabase.ConnectionPoolingMaximumOpenCount"/>, and <see cref="DynamicDatabase.ConnectionPoolingConnectionLifetime"/>.</remarks>
|
||||
ConnectionPooling = 0x00000004,
|
||||
|
||||
/// <summary>Database supports top syntax (SELECT TOP x ... FROM ...).</summary>
|
||||
SupportTop = 0x00000080,
|
||||
|
||||
@@ -6902,6 +7155,11 @@ namespace DynamORM
|
||||
/// <summary>Commits the database transaction.</summary>
|
||||
public void Commit()
|
||||
{
|
||||
if (_db == null)
|
||||
{
|
||||
_isOperational = false;
|
||||
return;
|
||||
}
|
||||
lock (_db.SyncLock)
|
||||
{
|
||||
if (_isOperational)
|
||||
@@ -6929,6 +7187,11 @@ namespace DynamORM
|
||||
/// <summary>Rolls back a transaction from a pending state.</summary>
|
||||
public void Rollback()
|
||||
{
|
||||
if (_db == null)
|
||||
{
|
||||
_isOperational = false;
|
||||
return;
|
||||
}
|
||||
lock (_db.SyncLock)
|
||||
{
|
||||
if (_isOperational)
|
||||
@@ -14612,6 +14875,70 @@ namespace DynamORM
|
||||
return resultTable;
|
||||
}
|
||||
}
|
||||
internal sealed class DynamicExecutionReader : IDataReader
|
||||
{
|
||||
private IDataReader _reader;
|
||||
private IDisposable _scope;
|
||||
|
||||
public DynamicExecutionReader(IDataReader reader, IDisposable scope)
|
||||
{
|
||||
_reader = reader;
|
||||
_scope = scope;
|
||||
}
|
||||
public object this[string name] { get { return _reader[name]; } }
|
||||
public object this[int i] { get { return _reader[i]; } }
|
||||
public int Depth { get { return _reader.Depth; } }
|
||||
public bool IsClosed { get { return _reader.IsClosed; } }
|
||||
public int RecordsAffected { get { return _reader.RecordsAffected; } }
|
||||
public int FieldCount { get { return _reader.FieldCount; } }
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_reader != null)
|
||||
_reader.Close();
|
||||
|
||||
Dispose();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
IDataReader reader = _reader;
|
||||
IDisposable scope = _scope;
|
||||
|
||||
_reader = null;
|
||||
_scope = null;
|
||||
|
||||
if (reader != null)
|
||||
reader.Dispose();
|
||||
|
||||
if (scope != null)
|
||||
scope.Dispose();
|
||||
}
|
||||
public bool GetBoolean(int i) { return _reader.GetBoolean(i); }
|
||||
public byte GetByte(int i) { return _reader.GetByte(i); }
|
||||
public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) { return _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); }
|
||||
public char GetChar(int i) { return _reader.GetChar(i); }
|
||||
public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) { return _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); }
|
||||
public IDataReader GetData(int i) { return _reader.GetData(i); }
|
||||
public string GetDataTypeName(int i) { return _reader.GetDataTypeName(i); }
|
||||
public DateTime GetDateTime(int i) { return _reader.GetDateTime(i); }
|
||||
public decimal GetDecimal(int i) { return _reader.GetDecimal(i); }
|
||||
public double GetDouble(int i) { return _reader.GetDouble(i); }
|
||||
public Type GetFieldType(int i) { return _reader.GetFieldType(i); }
|
||||
public float GetFloat(int i) { return _reader.GetFloat(i); }
|
||||
public Guid GetGuid(int i) { return _reader.GetGuid(i); }
|
||||
public short GetInt16(int i) { return _reader.GetInt16(i); }
|
||||
public int GetInt32(int i) { return _reader.GetInt32(i); }
|
||||
public long GetInt64(int i) { return _reader.GetInt64(i); }
|
||||
public string GetName(int i) { return _reader.GetName(i); }
|
||||
public int GetOrdinal(string name) { return _reader.GetOrdinal(name); }
|
||||
public DataTable GetSchemaTable() { return _reader.GetSchemaTable(); }
|
||||
public string GetString(int i) { return _reader.GetString(i); }
|
||||
public object GetValue(int i) { return _reader.GetValue(i); }
|
||||
public int GetValues(object[] values) { return _reader.GetValues(values); }
|
||||
public bool IsDBNull(int i) { return _reader.IsDBNull(i); }
|
||||
public bool NextResult() { return _reader.NextResult(); }
|
||||
public bool Read() { return _reader.Read(); }
|
||||
}
|
||||
internal sealed class DynamicProcedureDescriptor
|
||||
{
|
||||
public Type ProcedureType { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user