| File: System\Data\Common\DbDataSource.cs | Web Access |
| Project: src\runtime\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; namespace System.Data.Common { public abstract class DbDataSource : IDisposable, IAsyncDisposable { public abstract string ConnectionString { get; } protected abstract DbConnection CreateDbConnection(); protected virtual DbConnection OpenDbConnection() { var connection = CreateDbConnection(); try { connection.Open(); return connection; } catch { connection.Dispose(); throw; } } protected virtual async ValueTask<DbConnection> OpenDbConnectionAsync(CancellationToken cancellationToken = default) { var connection = CreateDbConnection(); try { await connection.OpenAsync(cancellationToken).ConfigureAwait(false); return connection; } catch { await connection.DisposeAsync().ConfigureAwait(false); throw; } } protected virtual DbCommand CreateDbCommand(string? commandText = null) { var command = CreateDbConnection().CreateCommand(); command.CommandText = commandText; return new DbCommandWrapper(command); } protected virtual DbBatch CreateDbBatch() => new DbBatchWrapper(CreateDbConnection().CreateBatch()); public DbConnection CreateConnection() => CreateDbConnection(); public DbConnection OpenConnection() => OpenDbConnection(); public ValueTask<DbConnection> OpenConnectionAsync(CancellationToken cancellationToken = default) => OpenDbConnectionAsync(cancellationToken); public DbCommand CreateCommand(string? commandText = null) => CreateDbCommand(commandText); public DbBatch CreateBatch() => CreateDbBatch(); public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } public async ValueTask DisposeAsync() { await DisposeAsyncCore().ConfigureAwait(false); Dispose(disposing: false); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { } protected virtual ValueTask DisposeAsyncCore() => default; private sealed class DbCommandWrapper : DbCommand { private readonly DbCommand _wrappedCommand; private readonly DbConnection _connection; internal DbCommandWrapper(DbCommand wrappedCommand) { Debug.Assert(wrappedCommand.Connection is not null); _wrappedCommand = wrappedCommand; _connection = wrappedCommand.Connection; } public override int ExecuteNonQuery() { _connection.Open(); try { return _wrappedCommand.ExecuteNonQuery(); } finally { try { _connection.Close(); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up. // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken) { await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { return await _wrappedCommand.ExecuteNonQueryAsync(cancellationToken) .ConfigureAwait(false); } finally { try { await _connection.CloseAsync().ConfigureAwait(false); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } public override object? ExecuteScalar() { _connection.Open(); try { return _wrappedCommand.ExecuteScalar(); } finally { try { _connection.Close(); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } public override async Task<object?> ExecuteScalarAsync(CancellationToken cancellationToken) { await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { return await _wrappedCommand.ExecuteScalarAsync(cancellationToken) .ConfigureAwait(false); } finally { try { await _connection.CloseAsync().ConfigureAwait(false); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) { _connection.Open(); try { return _wrappedCommand.ExecuteReader(behavior | CommandBehavior.CloseConnection); } catch { try { _connection.Close(); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up } throw; } } protected override async Task<DbDataReader> ExecuteDbDataReaderAsync( CommandBehavior behavior, CancellationToken cancellationToken) { await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { return await _wrappedCommand.ExecuteReaderAsync( behavior | CommandBehavior.CloseConnection, cancellationToken) .ConfigureAwait(false); } catch { try { await _connection.CloseAsync().ConfigureAwait(false); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up } throw; } } protected override DbParameter CreateDbParameter() => _wrappedCommand.CreateParameter(); public override void Cancel() => _wrappedCommand.Cancel(); [AllowNull] public override string CommandText { get => _wrappedCommand.CommandText; set => _wrappedCommand.CommandText = value; } public override int CommandTimeout { get => _wrappedCommand.CommandTimeout; set => _wrappedCommand.CommandTimeout = value; } public override CommandType CommandType { get => _wrappedCommand.CommandType; set => _wrappedCommand.CommandType = value; } protected override DbParameterCollection DbParameterCollection => _wrappedCommand.Parameters; public override bool DesignTimeVisible { get => _wrappedCommand.DesignTimeVisible; set => _wrappedCommand.DesignTimeVisible = value; } public override UpdateRowSource UpdatedRowSource { get => _wrappedCommand.UpdatedRowSource; set => _wrappedCommand.UpdatedRowSource = value; } protected override void Dispose(bool disposing) { if (disposing) { var connection = _wrappedCommand.Connection; _wrappedCommand.Dispose(); connection!.Dispose(); } } public override async ValueTask DisposeAsync() { var connection = _wrappedCommand.Connection; await _wrappedCommand.DisposeAsync().ConfigureAwait(false); await connection!.DisposeAsync().ConfigureAwait(false); } // In most case, preparation doesn't make sense on a connectionless command since prepared statements are // usually bound to specific physical connections. // When prepared statements are global (not bound to a specific connection), providers would need to // provide their own connection-less implementation anyway (i.e. interacting with the originating // DbDataSource), so they'd have to override this in any case. public override void Prepare() => throw ExceptionBuilder.NotSupportedOnDataSourceCommand(); public override Task PrepareAsync(CancellationToken cancellationToken = default) => Task.FromException(ExceptionBuilder.NotSupportedOnDataSourceCommand()); // The below are incompatible with commands executed directly against DbDataSource, since no DbConnection // is involved at the user API level and the DbCommandWrapper owns the DbConnection. protected override DbConnection? DbConnection { get => throw ExceptionBuilder.NotSupportedOnDataSourceCommand(); set => throw ExceptionBuilder.NotSupportedOnDataSourceCommand(); } protected override DbTransaction? DbTransaction { get => throw ExceptionBuilder.NotSupportedOnDataSourceCommand(); set => throw ExceptionBuilder.NotSupportedOnDataSourceCommand(); } } private sealed class DbBatchWrapper : DbBatch { private readonly DbBatch _wrappedBatch; private readonly DbConnection _connection; internal DbBatchWrapper(DbBatch wrappedBatch) { Debug.Assert(wrappedBatch.Connection is not null); _wrappedBatch = wrappedBatch; _connection = wrappedBatch.Connection; } public override int ExecuteNonQuery() { _connection.Open(); try { return _wrappedBatch.ExecuteNonQuery(); } finally { try { _connection.Close(); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } public override async Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken) { await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { return await _wrappedBatch.ExecuteNonQueryAsync(cancellationToken) .ConfigureAwait(false); } finally { try { await _connection.CloseAsync().ConfigureAwait(false); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } public override object? ExecuteScalar() { _connection.Open(); try { return _wrappedBatch.ExecuteScalar(); } finally { try { _connection.Close(); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } public override async Task<object?> ExecuteScalarAsync(CancellationToken cancellationToken) { await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { return await _wrappedBatch.ExecuteScalarAsync(cancellationToken) .ConfigureAwait(false); } finally { try { await _connection.CloseAsync().ConfigureAwait(false); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up // Also, refrain from bubbling up the close exception even if there's no original exception, // since it's not relevant to the user - execution did complete successfully, and the connection // close is just an internal detail that shouldn't cause user code to fail. } } } protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) { _connection.Open(); try { return _wrappedBatch.ExecuteReader(behavior | CommandBehavior.CloseConnection); } catch { try { _connection.Close(); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up } throw; } } protected override async Task<DbDataReader> ExecuteDbDataReaderAsync( CommandBehavior behavior, CancellationToken cancellationToken) { await _connection.OpenAsync(cancellationToken).ConfigureAwait(false); try { return await _wrappedBatch.ExecuteReaderAsync( behavior | CommandBehavior.CloseConnection, cancellationToken) .ConfigureAwait(false); } catch { try { await _connection.CloseAsync().ConfigureAwait(false); } catch (Exception e) { ExceptionBuilder.TraceExceptionWithoutRethrow(e); // Swallow to allow the original exception to bubble up } throw; } } protected override DbBatchCommand CreateDbBatchCommand() => throw new NotImplementedException(); public override void Cancel() => _wrappedBatch.Cancel(); protected override DbBatchCommandCollection DbBatchCommands => _wrappedBatch.BatchCommands; public override int Timeout { get => _wrappedBatch.Timeout; set => _wrappedBatch.Timeout = value; } public override void Dispose() { var connection = _wrappedBatch.Connection; _wrappedBatch.Dispose(); connection!.Dispose(); } public override async ValueTask DisposeAsync() { var connection = _wrappedBatch.Connection; await _wrappedBatch.DisposeAsync().ConfigureAwait(false); await connection!.DisposeAsync().ConfigureAwait(false); } // In most case, preparation doesn't make sense on a connectionless command since prepared statements are // usually bound to specific physical connections. // When prepared statements are global (not bound to a specific connection), providers would need to // provide their own connection-less implementation anyway (i.e. interacting with the originating // DbDataSource), so they'd have to override this in any case. public override void Prepare() => throw ExceptionBuilder.NotSupportedOnDataSourceBatch(); public override Task PrepareAsync(CancellationToken cancellationToken = default) => Task.FromException(ExceptionBuilder.NotSupportedOnDataSourceBatch()); // The below are incompatible with batches executed directly against DbDataSource, since no DbConnection // is involved at the user API level and the DbBatchWrapper owns the DbConnection. protected override DbConnection? DbConnection { get => throw ExceptionBuilder.NotSupportedOnDataSourceBatch(); set => throw ExceptionBuilder.NotSupportedOnDataSourceBatch(); } protected override DbTransaction? DbTransaction { get => throw ExceptionBuilder.NotSupportedOnDataSourceBatch(); set => throw ExceptionBuilder.NotSupportedOnDataSourceBatch(); } } } }