Add managed connection pooling and execution locking

This commit is contained in:
2026-02-27 17:28:50 +01:00
parent e43d7863ec
commit aedb97e879
12 changed files with 1554 additions and 348 deletions

View File

@@ -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; }