Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow transfer of ownership of DbConnection from application to DbContext #29960

Merged
merged 2 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -646,16 +646,18 @@ public static DbConnection GetDbConnection(this DatabaseFacade databaseFacade)
/// The connection can only be set when the existing connection, if any, is not open.
/// </para>
/// <para>
/// Note that the given connection must be disposed by application code since it was not created by Entity Framework.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-connections">Connections and connection strings</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <param name="connection">The connection.</param>
public static void SetDbConnection(this DatabaseFacade databaseFacade, DbConnection? connection)
=> GetFacadeDependencies(databaseFacade).RelationalConnection.DbConnection = connection;
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal. The default value is <see langword="false"/>.
/// </param>
public static void SetDbConnection(this DatabaseFacade databaseFacade, DbConnection? connection, bool contextOwnsConnection = false)
=> GetFacadeDependencies(databaseFacade).RelationalConnection.SetDbConnection(connection, contextOwnsConnection);

/// <summary>
/// Gets the underlying connection string configured for this <see cref="DbContext" />.
Expand Down
19 changes: 19 additions & 0 deletions src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public abstract class RelationalOptionsExtension : IDbContextOptionsExtension

private string? _connectionString;
private DbConnection? _connection;
private bool _connectionOwned;
private int? _commandTimeout;
private int? _maxBatchSize;
private int? _minBatchSize;
Expand All @@ -50,6 +51,7 @@ protected RelationalOptionsExtension(RelationalOptionsExtension copyFrom)
{
_connectionString = copyFrom._connectionString;
_connection = copyFrom._connection;
_connectionOwned = copyFrom._connectionOwned;
_commandTimeout = copyFrom._commandTimeout;
_maxBatchSize = copyFrom._maxBatchSize;
_minBatchSize = copyFrom._minBatchSize;
Expand Down Expand Up @@ -101,17 +103,34 @@ public virtual RelationalOptionsExtension WithConnectionString(string? connectio
public virtual DbConnection? Connection
=> _connection;

/// <summary>
/// <see langword="true"/> if the <see cref="Connection"/> is owned by the context and should be disposed appropriately.
/// </summary>
public virtual bool ConnectionOwned
=> _connectionOwned;

/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
/// </summary>
/// <param name="connection">The option to change.</param>
/// <returns>A new instance with the option changed.</returns>
public virtual RelationalOptionsExtension WithConnection(DbConnection? connection)
=> WithConnection(connection, owned: false);

/// <summary>
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
/// </summary>
/// <param name="connection">The option to change.</param>
/// <param name="owned">If <see langword="true"/>, then the connection will become owned by the context, and will be disposed in the same way that a connection created by the context is disposed.</param>
/// <returns>A new instance with the option changed.</returns>
public virtual RelationalOptionsExtension WithConnection(DbConnection? connection, bool owned)
{
var clone = Clone();

clone._connection = connection;
clone._connectionOwned = owned;

return clone;
}
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore.Relational/Storage/IRelationalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ public interface IRelationalConnection : IRelationalTransactionManager, IDisposa
[AllowNull]
DbConnection DbConnection { get; set; }

/// <summary>
/// Sets the underlying <see cref="System.Data.Common.DbConnection" /> used to connect to the database.
/// </summary>
/// <param name="value">The connection object.</param>
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal.
/// </param>
/// <remarks>
/// <para>
/// The connection can only be changed when the existing connection, if any, is not open.
/// </para>
/// </remarks>
void SetDbConnection(DbConnection? value, bool contextOwnsConnection);

/// <summary>
/// The <see cref="DbContext" /> currently in use, or <see langword="null" /> if not known.
/// </summary>
Expand Down
28 changes: 17 additions & 11 deletions src/EFCore.Relational/Storage/RelationalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected RelationalConnection(RelationalConnectionDependencies dependencies)
if (relationalOptions.Connection != null)
{
_connection = relationalOptions.Connection;
_connectionOwned = false;
_connectionOwned = relationalOptions.ConnectionOwned;

if (_connectionString != null)
{
Expand Down Expand Up @@ -172,19 +172,25 @@ public virtual DbConnection DbConnection
}
set
{
if (!ReferenceEquals(_connection, value))
SetDbConnection(value, contextOwnsConnection: false);
}
}

/// <inheritdoc />
public virtual void SetDbConnection(DbConnection? value, bool contextOwnsConnection)
{
if (!ReferenceEquals(_connection, value))
{
if (_openedCount > 0)
{
if (_openedCount > 0)
{
throw new InvalidOperationException(RelationalStrings.CannotChangeWhenOpen);
}
throw new InvalidOperationException(RelationalStrings.CannotChangeWhenOpen);
}

Dispose();
Dispose();

_connection = value;
_connectionString = null;
_connectionOwned = false;
}
_connection = value;
_connectionString = null;
_connectionOwned = contextOwnsConnection;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,30 @@ public static class SqlServerDbContextOptionsExtensions
return optionsBuilder;
}

// Note: Decision made to use DbConnection not SqlConnection: Issue #772
/// <summary>
/// Configures the context to connect to a Microsoft SQL Server database.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and SQL Azure databases with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <param name="optionsBuilder">The builder being used to configure the context.</param>
/// <param name="connection">
/// An existing <see cref="DbConnection" /> to be used to connect to the database. If the connection is
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed. The caller owns the connection and is
/// responsible for its disposal.
/// </param>
/// <param name="sqlServerOptionsAction">An optional action to allow additional SQL Server specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static DbContextOptionsBuilder UseSqlServer(
this DbContextOptionsBuilder optionsBuilder,
DbConnection connection,
Action<SqlServerDbContextOptionsBuilder>? sqlServerOptionsAction = null)
=> UseSqlServer(optionsBuilder, connection, false, sqlServerOptionsAction);

// Note: Decision made to use DbConnection not SqlConnection: Issue #772
/// <summary>
/// Configures the context to connect to a Microsoft SQL Server database.
Expand All @@ -90,16 +114,22 @@ public static class SqlServerDbContextOptionsExtensions
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed.
/// </param>
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal.
/// </param>
/// <param name="sqlServerOptionsAction">An optional action to allow additional SQL Server specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static DbContextOptionsBuilder UseSqlServer(
this DbContextOptionsBuilder optionsBuilder,
DbConnection connection,
bool contextOwnsConnection,
Action<SqlServerDbContextOptionsBuilder>? sqlServerOptionsAction = null)
{
Check.NotNull(connection, nameof(connection));

var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection);
var extension = (SqlServerOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection, contextOwnsConnection);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);
Expand Down Expand Up @@ -170,7 +200,8 @@ public static class SqlServerDbContextOptionsExtensions
/// <param name="connection">
/// An existing <see cref="DbConnection" /> to be used to connect to the database. If the connection is
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed.
/// state then EF will open and close the connection as needed. The caller owns the connection and is
/// responsible for its disposal.
/// </param>
/// <param name="sqlServerOptionsAction">An optional action to allow additional SQL Server specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
Expand All @@ -182,6 +213,38 @@ public static class SqlServerDbContextOptionsExtensions
=> (DbContextOptionsBuilder<TContext>)UseSqlServer(
(DbContextOptionsBuilder)optionsBuilder, connection, sqlServerOptionsAction);

// Note: Decision made to use DbConnection not SqlConnection: Issue #772
/// <summary>
/// Configures the context to connect to a Microsoft SQL Server database.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlserver">Accessing SQL Server and SQL Azure databases with EF Core</see>
/// for more information and examples.
/// </remarks>
/// <typeparam name="TContext">The type of context to be configured.</typeparam>
/// <param name="optionsBuilder">The builder being used to configure the context.</param>
/// <param name="connection">
/// An existing <see cref="DbConnection" /> to be used to connect to the database. If the connection is
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed.
/// </param>
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal.
/// </param>
/// <param name="sqlServerOptionsAction">An optional action to allow additional SQL Server specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static DbContextOptionsBuilder<TContext> UseSqlServer<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
DbConnection connection,
bool contextOwnsConnection,
Action<SqlServerDbContextOptionsBuilder>? sqlServerOptionsAction = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseSqlServer(
(DbContextOptionsBuilder)optionsBuilder, connection, contextOwnsConnection, sqlServerOptionsAction);

private static SqlServerOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.Options.FindExtension<SqlServerOptionsExtension>()
?? new SqlServerOptionsExtension();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,28 @@ public static class SqliteDbContextOptionsBuilderExtensions
return optionsBuilder;
}

/// <summary>
/// Configures the context to connect to a SQLite database.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlite">Accessing SQLite databases with EF Core</see> for more information and examples.
/// </remarks>
/// <param name="optionsBuilder">The builder being used to configure the context.</param>
/// <param name="connection">
/// An existing <see cref="DbConnection" /> to be used to connect to the database. If the connection is
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed. The caller owns the connection and is
/// responsible for its disposal.
/// </param>
/// <param name="sqliteOptionsAction">An optional action to allow additional SQLite specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static DbContextOptionsBuilder UseSqlite(
this DbContextOptionsBuilder optionsBuilder,
DbConnection connection,
Action<SqliteDbContextOptionsBuilder>? sqliteOptionsAction = null)
=> UseSqlite(optionsBuilder, connection, false, sqliteOptionsAction);

/// <summary>
/// Configures the context to connect to a SQLite database.
/// </summary>
Expand All @@ -85,16 +107,22 @@ public static class SqliteDbContextOptionsBuilderExtensions
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed.
/// </param>
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal.
/// </param>
/// <param name="sqliteOptionsAction">An optional action to allow additional SQLite specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static DbContextOptionsBuilder UseSqlite(
this DbContextOptionsBuilder optionsBuilder,
DbConnection connection,
bool contextOwnsConnection,
Action<SqliteDbContextOptionsBuilder>? sqliteOptionsAction = null)
{
Check.NotNull(connection, nameof(connection));

var extension = (SqliteOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection);
var extension = (SqliteOptionsExtension)GetOrCreateExtension(optionsBuilder).WithConnection(connection, contextOwnsConnection);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);

ConfigureWarnings(optionsBuilder);
Expand Down Expand Up @@ -161,7 +189,8 @@ public static class SqliteDbContextOptionsBuilderExtensions
/// <param name="connection">
/// An existing <see cref="DbConnection" /> to be used to connect to the database. If the connection is
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed.
/// state then EF will open and close the connection as needed. The caller owns the connection and is
/// responsible for its disposal.
/// </param>
/// <param name="sqliteOptionsAction">An optional action to allow additional SQLite specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
Expand All @@ -173,6 +202,36 @@ public static class SqliteDbContextOptionsBuilderExtensions
=> (DbContextOptionsBuilder<TContext>)UseSqlite(
(DbContextOptionsBuilder)optionsBuilder, connection, sqliteOptionsAction);

/// <summary>
/// Configures the context to connect to a SQLite database.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-dbcontext-options">Using DbContextOptions</see>, and
/// <see href="https://aka.ms/efcore-docs-sqlite">Accessing SQLite databases with EF Core</see> for more information and examples.
/// </remarks>
/// <typeparam name="TContext">The type of context to be configured.</typeparam>
/// <param name="optionsBuilder">The builder being used to configure the context.</param>
/// <param name="connection">
/// An existing <see cref="DbConnection" /> to be used to connect to the database. If the connection is
/// in the open state then EF will not open or close the connection. If the connection is in the closed
/// state then EF will open and close the connection as needed.
/// </param>
/// <param name="contextOwnsConnection">
/// If <see langword="true" />, then EF will take ownership of the connection and will
/// dispose it in the same way it would dispose a connection created by EF. If <see langword="false" />, then the caller still
/// owns the connection and is responsible for its disposal.
/// </param>
/// <param name="sqliteOptionsAction">An optional action to allow additional SQLite specific configuration.</param>
/// <returns>The options builder so that further configuration can be chained.</returns>
public static DbContextOptionsBuilder<TContext> UseSqlite<TContext>(
this DbContextOptionsBuilder<TContext> optionsBuilder,
DbConnection connection,
bool contextOwnsConnection,
Action<SqliteDbContextOptionsBuilder>? sqliteOptionsAction = null)
where TContext : DbContext
=> (DbContextOptionsBuilder<TContext>)UseSqlite(
(DbContextOptionsBuilder)optionsBuilder, connection, contextOwnsConnection, sqliteOptionsAction);

private static SqliteOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder options)
=> options.Options.FindExtension<SqliteOptionsExtension>()
?? new SqliteOptionsExtension();
Expand Down
Loading