File: System\Data\Common\DbDataSource.cs
Web Access
Project: src\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.NotSupportedOnDataSourceCommand();
 
            public override Task PrepareAsync(CancellationToken cancellationToken = default)
                => Task.FromException(ExceptionBuilder.NotSupportedOnDataSourceCommand());
 
            // 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();
            }
        }
    }
}