Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShay Rojansky <roji@roji.org>2022-06-06 17:10:15 +0300
committerGitHub <noreply@github.com>2022-06-06 17:10:15 +0300
commitbe4d292eaf27f3890c877b08f6bf9e2f6279e343 (patch)
treed603e138cccffc4acd90c1a44edf34fe3d13cf71
parent941c81dd9a73a9991efaf126c5e6d8b442161289 (diff)
Implement DbDataSource (#70006)dev/tommcdon/example_mdbg_test
Closes #64812
-rw-r--r--src/libraries/System.Data.Common/ref/System.Data.Common.cs19
-rw-r--r--src/libraries/System.Data.Common/src/Resources/Strings.resx2
-rw-r--r--src/libraries/System.Data.Common/src/System.Data.Common.csproj2
-rw-r--r--src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs584
-rw-r--r--src/libraries/System.Data.Common/src/System/Data/Common/DbProviderFactory.cs3
-rw-r--r--src/libraries/System.Data.Common/src/System/Data/Common/DefaultDataSource.cs32
-rw-r--r--src/libraries/System.Data.Common/src/System/Data/DataException.cs11
7 files changed, 653 insertions, 0 deletions
diff --git a/src/libraries/System.Data.Common/ref/System.Data.Common.cs b/src/libraries/System.Data.Common/ref/System.Data.Common.cs
index fc4963afd6a..7f8e500b823 100644
--- a/src/libraries/System.Data.Common/ref/System.Data.Common.cs
+++ b/src/libraries/System.Data.Common/ref/System.Data.Common.cs
@@ -2405,6 +2405,24 @@ namespace System.Data.Common
System.ComponentModel.PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties(System.Attribute[]? attributes) { throw null; }
object System.ComponentModel.ICustomTypeDescriptor.GetPropertyOwner(System.ComponentModel.PropertyDescriptor? pd) { throw null; }
}
+ public abstract class DbDataSource : IDisposable, IAsyncDisposable
+ {
+ public abstract string ConnectionString { get; }
+ protected abstract System.Data.Common.DbConnection CreateDbConnection();
+ protected virtual System.Data.Common.DbConnection OpenDbConnection() { throw null; }
+ protected virtual System.Threading.Tasks.ValueTask<System.Data.Common.DbConnection> OpenDbConnectionAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ protected virtual System.Data.Common.DbCommand CreateDbCommand(string? commandText = null) { throw null; }
+ protected virtual System.Data.Common.DbBatch CreateDbBatch() { throw null; }
+ public System.Data.Common.DbConnection CreateConnection() { throw null; }
+ public System.Data.Common.DbConnection OpenConnection() { throw null; }
+ public System.Threading.Tasks.ValueTask<System.Data.Common.DbConnection> OpenConnectionAsync(System.Threading.CancellationToken cancellationToken = default) { throw null; }
+ public System.Data.Common.DbCommand CreateCommand(string? commandText = null) { throw null; }
+ public System.Data.Common.DbBatch CreateBatch() { throw null; }
+ public void Dispose() { throw null; }
+ public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
+ protected virtual void Dispose(bool disposing) { throw null; }
+ protected virtual System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; }
+ }
public abstract partial class DbDataSourceEnumerator
{
protected DbDataSourceEnumerator() { }
@@ -2606,6 +2624,7 @@ namespace System.Data.Common
public virtual System.Data.Common.DbDataAdapter? CreateDataAdapter() { throw null; }
public virtual System.Data.Common.DbDataSourceEnumerator? CreateDataSourceEnumerator() { throw null; }
public virtual System.Data.Common.DbParameter? CreateParameter() { throw null; }
+ public virtual System.Data.Common.DbDataSource CreateDataSource(string connectionString) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed partial class DbProviderSpecificTypePropertyAttribute : System.Attribute
diff --git a/src/libraries/System.Data.Common/src/Resources/Strings.resx b/src/libraries/System.Data.Common/src/Resources/Strings.resx
index c1a4dee0c36..5199442caa4 100644
--- a/src/libraries/System.Data.Common/src/Resources/Strings.resx
+++ b/src/libraries/System.Data.Common/src/Resources/Strings.resx
@@ -156,6 +156,8 @@
<data name="Expr_InvalidTimeZoneRange" xml:space="preserve"><value>Provided range for time one exceeds total of 14 hours.</value></data>
<data name="Expr_MismatchKindandTimeSpan" xml:space="preserve"><value>Kind property of provided DateTime argument, does not match 'hours' and 'minutes' arguments.</value></data>
<data name="Expr_UnsupportedType" xml:space="preserve"><value>A DataColumn of type '{0}' does not support expression.</value></data>
+ <data name="Batch_NotSupportedOnDataSourceBatch" xml:space="preserve"><value>Connection and transaction access is not supported on batches created from DbDataSource.</value></data>
+ <data name="Command_NotSupportedOnDataSourceCommand" xml:space="preserve"><value>Connection and transaction access is not supported on commands created from DbDataSource.</value></data>
<data name="Data_EnforceConstraints" xml:space="preserve"><value>Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.</value></data>
<data name="Data_CannotModifyCollection" xml:space="preserve"><value>Collection itself is not modifiable.</value></data>
<data name="Data_CaseInsensitiveNameConflict" xml:space="preserve"><value>The given name '{0}' matches at least two names in the collection object with different cases, but does not match either of them with the same case.</value></data>
diff --git a/src/libraries/System.Data.Common/src/System.Data.Common.csproj b/src/libraries/System.Data.Common/src/System.Data.Common.csproj
index c4ac48cf19e..8c1413c2612 100644
--- a/src/libraries/System.Data.Common/src/System.Data.Common.csproj
+++ b/src/libraries/System.Data.Common/src/System.Data.Common.csproj
@@ -210,6 +210,7 @@
<Compile Include="System\Data\Common\DbDataReader.cs" />
<Compile Include="System\Data\Common\DbDataReaderExtensions.cs" />
<Compile Include="System\Data\Common\DbDataRecord.cs" />
+ <Compile Include="System\Data\Common\DbDataSource.cs" />
<Compile Include="System\Data\Common\DbDataSourceEnumerator.cs" />
<Compile Include="System\Data\Common\DbEnumerator.cs" />
<Compile Include="System\Data\Common\DbException.cs" />
@@ -224,6 +225,7 @@
<Compile Include="System\Data\Common\DBSchemaTable.cs" />
<Compile Include="System\Data\Common\DbTransaction.cs" />
<Compile Include="System\Data\Common\DecimalStorage.cs" />
+ <Compile Include="System\Data\Common\DefaultDataSource.cs" />
<Compile Include="System\Data\Common\DoubleStorage.cs" />
<Compile Include="System\Data\Common\FieldNameLookup.cs" />
<Compile Include="System\Data\Common\Groupbybehavior.cs" />
diff --git a/src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs b/src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
new file mode 100644
index 00000000000..c51c7f59507
--- /dev/null
+++ b/src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
@@ -0,0 +1,584 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+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();
+ }
+ }
+ }
+}
diff --git a/src/libraries/System.Data.Common/src/System/Data/Common/DbProviderFactory.cs b/src/libraries/System.Data.Common/src/System/Data/Common/DbProviderFactory.cs
index 7282338b540..f133e5a0cdd 100644
--- a/src/libraries/System.Data.Common/src/System/Data/Common/DbProviderFactory.cs
+++ b/src/libraries/System.Data.Common/src/System/Data/Common/DbProviderFactory.cs
@@ -66,5 +66,8 @@ namespace System.Data.Common
public virtual DbParameter? CreateParameter() => null;
public virtual DbDataSourceEnumerator? CreateDataSourceEnumerator() => null;
+
+ public virtual DbDataSource CreateDataSource(string connectionString)
+ => new DefaultDataSource(this, connectionString);
}
}
diff --git a/src/libraries/System.Data.Common/src/System/Data/Common/DefaultDataSource.cs b/src/libraries/System.Data.Common/src/System/Data/Common/DefaultDataSource.cs
new file mode 100644
index 00000000000..2829fb1b5aa
--- /dev/null
+++ b/src/libraries/System.Data.Common/src/System/Data/Common/DefaultDataSource.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Data.Common
+{
+ internal sealed class DefaultDataSource : DbDataSource
+ {
+ private readonly DbProviderFactory _dbProviderFactory;
+ private readonly string _connectionString;
+
+ internal DefaultDataSource(DbProviderFactory dbProviderFactory, string connectionString)
+ {
+ _dbProviderFactory = dbProviderFactory;
+ _connectionString = connectionString;
+ }
+
+ public override string ConnectionString => _connectionString;
+
+ protected override DbConnection CreateDbConnection()
+ {
+ var connection = _dbProviderFactory.CreateConnection();
+ if (connection is null)
+ {
+ throw new InvalidOperationException("DbProviderFactory returned a null connection");
+ }
+
+ connection.ConnectionString = _connectionString;
+
+ return connection;
+ }
+ }
+}
diff --git a/src/libraries/System.Data.Common/src/System/Data/DataException.cs b/src/libraries/System.Data.Common/src/System/Data/DataException.cs
index 7696f42c3db..ffc9b5c9682 100644
--- a/src/libraries/System.Data.Common/src/System/Data/DataException.cs
+++ b/src/libraries/System.Data.Common/src/System/Data/DataException.cs
@@ -352,6 +352,17 @@ namespace System.Data
public static Exception ArgumentContainsNull(string paramName) => _Argument(paramName, SR.Format(SR.Data_ArgumentContainsNull, paramName));
public static Exception TypeNotAllowed(Type type) => _InvalidOperation(SR.Format(SR.Data_TypeNotAllowed, type.AssemblyQualifiedName));
+ //
+ // Batch
+ //
+
+ public static Exception NotSupportedOnDataSourceBatch() => Common.ADP.NotSupported(SR.Batch_NotSupportedOnDataSourceBatch);
+
+ //
+ // Command
+ //
+
+ public static Exception NotSupportedOnDataSourceCommand() => Common.ADP.NotSupported(SR.Command_NotSupportedOnDataSourceCommand);
//
// Collections