diff --git a/src/EFCore.Relational/Diagnostics/ConnectionCreatedEventData.cs b/src/EFCore.Relational/Diagnostics/ConnectionCreatedEventData.cs
new file mode 100644
index 00000000000..b2c954f7bde
--- /dev/null
+++ b/src/EFCore.Relational/Diagnostics/ConnectionCreatedEventData.cs
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Diagnostics;
+
+///
+/// The event payload for events.
+///
+///
+/// See Logging, events, and diagnostics for more information and examples.
+///
+public class ConnectionCreatedEventData : DbContextEventData
+{
+ ///
+ /// Constructs the event payload.
+ ///
+ /// The event definition.
+ /// A delegate that generates a log message for this event.
+ /// The .
+ /// The currently being used, to null if not known.
+ /// A correlation ID that identifies the instance being used.
+ /// The start time of this event.
+ /// The duration this event.
+ public ConnectionCreatedEventData(
+ EventDefinitionBase eventDefinition,
+ Func messageGenerator,
+ DbConnection connection,
+ DbContext? context,
+ Guid connectionId,
+ DateTimeOffset startTime,
+ TimeSpan duration)
+ : base(eventDefinition, messageGenerator, context)
+ {
+ Connection = connection;
+ ConnectionId = connectionId;
+ StartTime = startTime;
+ Duration = duration;
+ }
+
+ ///
+ /// The .
+ ///
+ public virtual DbConnection Connection { get; }
+
+ ///
+ /// A correlation ID that identifies the instance being used.
+ ///
+ public virtual Guid ConnectionId { get; }
+
+ ///
+ /// The start time of this event.
+ ///
+ public virtual DateTimeOffset StartTime { get; }
+
+ ///
+ /// The duration of this event.
+ ///
+ public virtual TimeSpan Duration { get; }
+}
diff --git a/src/EFCore.Relational/Diagnostics/ConnectionCreatingEventData.cs b/src/EFCore.Relational/Diagnostics/ConnectionCreatingEventData.cs
new file mode 100644
index 00000000000..685beb61c3d
--- /dev/null
+++ b/src/EFCore.Relational/Diagnostics/ConnectionCreatingEventData.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Diagnostics;
+
+///
+/// The event payload for events.
+///
+///
+/// See Logging, events, and diagnostics for more information and examples.
+///
+public class ConnectionCreatingEventData : DbContextEventData
+{
+ ///
+ /// Constructs the event payload.
+ ///
+ /// The event definition.
+ /// A delegate that generates a log message for this event.
+ /// The currently being used, to null if not known.
+ /// A correlation ID that identifies the instance being used.
+ /// The start time of this event.
+ public ConnectionCreatingEventData(
+ EventDefinitionBase eventDefinition,
+ Func messageGenerator,
+ DbContext? context,
+ Guid connectionId,
+ DateTimeOffset startTime)
+ : base(eventDefinition, messageGenerator, context)
+ {
+ ConnectionId = connectionId;
+ StartTime = startTime;
+ }
+
+ ///
+ /// A correlation ID that identifies the instance being used.
+ ///
+ public virtual Guid ConnectionId { get; }
+
+ ///
+ /// The start time of this event.
+ ///
+ public virtual DateTimeOffset StartTime { get; }
+}
diff --git a/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs
index 87a992d6f10..70110d23f3a 100644
--- a/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs
+++ b/src/EFCore.Relational/Diagnostics/IDbCommandInterceptor.cs
@@ -43,7 +43,7 @@ public interface IDbCommandInterceptor : IInterceptor
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
InterceptionResult CommandCreating(CommandCorrelatedEventData eventData, InterceptionResult result)
@@ -63,7 +63,7 @@ InterceptionResult CommandCreating(CommandCorrelatedEventData eventDa
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result)
@@ -78,7 +78,7 @@ DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result)
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
DbCommand CommandInitialized(CommandEndEventData eventData, DbCommand result)
@@ -99,7 +99,7 @@ DbCommand CommandInitialized(CommandEndEventData eventData, DbCommand result)
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
InterceptionResult ReaderExecuting(DbCommand command,CommandEventData eventData, InterceptionResult result)
@@ -120,7 +120,7 @@ InterceptionResult ReaderExecuting(DbCommand command,CommandEventD
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
InterceptionResult ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result)
@@ -141,7 +141,7 @@ InterceptionResult ScalarExecuting(DbCommand command, CommandEventData e
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
InterceptionResult NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult result)
@@ -163,7 +163,7 @@ InterceptionResult NonQueryExecuting(DbCommand command, CommandEventData ev
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -190,7 +190,7 @@ ValueTask> ReaderExecutingAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -217,7 +217,7 @@ ValueTask> ScalarExecutingAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -243,7 +243,7 @@ ValueTask> NonQueryExecutingAsync(
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
@@ -264,7 +264,7 @@ DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventDat
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result)
@@ -285,7 +285,7 @@ DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventDat
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
@@ -307,7 +307,7 @@ int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int
/// A to observe while waiting for the task to complete.
///
/// A providing the result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -334,7 +334,7 @@ ValueTask ReaderExecutedAsync(
/// A to observe while waiting for the task to complete.
///
/// A providing the result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -361,7 +361,7 @@ ValueTask ReaderExecutedAsync(
/// A to observe while waiting for the task to complete.
///
/// A providing the result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -427,7 +427,7 @@ Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, Canc
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult DataReaderDisposing(DbCommand command, DataReaderDisposingEventData eventData, InterceptionResult result)
diff --git a/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs
index a036cc633d6..cc153d9dbc0 100644
--- a/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs
+++ b/src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.ComponentModel;
+
namespace Microsoft.EntityFrameworkCore.Diagnostics;
///
@@ -29,6 +31,44 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics;
///
public interface IDbConnectionInterceptor : IInterceptor
{
+ ///
+ /// Called just before EF creates a . This event is not triggered if the application provides the
+ /// connection to use.
+ ///
+ /// Contextual information about the connection.
+ ///
+ /// Represents the current result if one exists.
+ /// This value will have set to if some previous
+ /// interceptor suppressed execution by calling .
+ /// This value is typically used as the return value for the implementation of this method.
+ ///
+ ///
+ /// If is , then EF will continue as normal.
+ /// If is , then EF will suppress the operation it
+ /// was about to perform and use instead.
+ /// An implementation of this method for any interceptor that is not attempting to change the result
+ /// should return the value passed in.
+ ///
+ InterceptionResult ConnectionCreating(ConnectionCreatingEventData eventData, InterceptionResult result)
+ => result;
+
+ ///
+ /// Called just after EF creates a . This event is not triggered if the application provides the
+ /// connection to use.
+ ///
+ /// Contextual information about the connection.
+ ///
+ /// The connection that has been created.
+ /// This value is typically used as the return value for the implementation of this method.
+ ///
+ ///
+ /// The result that EF will use.
+ /// An implementation of this method for any interceptor that is not attempting to change the result
+ /// is to return the value passed in.
+ ///
+ DbConnection ConnectionCreated(ConnectionCreatedEventData eventData, DbConnection result)
+ => result;
+
///
/// Called just before EF intends to call .
///
@@ -44,7 +84,7 @@ public interface IDbConnectionInterceptor : IInterceptor
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
@@ -66,7 +106,7 @@ InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventDat
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
/// If the is canceled.
@@ -98,7 +138,7 @@ Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData event
=> Task.CompletedTask;
///
- /// Called just before EF intends to call .
+ /// Called just before EF intends to call .
///
/// The connection.
/// Contextual information about the connection.
@@ -112,14 +152,14 @@ Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData event
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult ConnectionClosing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
=> result;
///
- /// Called just before EF intends to call in an async context.
+ /// Called just before EF intends to call in an async context.
///
/// The connection.
/// Contextual information about the connection.
@@ -133,7 +173,7 @@ InterceptionResult ConnectionClosing(DbConnection connection, ConnectionEventDat
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
ValueTask ConnectionClosingAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
@@ -157,6 +197,66 @@ void ConnectionClosed(DbConnection connection, ConnectionEndEventData eventData)
Task ConnectionClosedAsync(DbConnection connection, ConnectionEndEventData eventData)
=> Task.CompletedTask;
+ ///
+ /// Called just before EF intends to call for the .
+ ///
+ /// The connection.
+ /// Contextual information about the connection.
+ ///
+ /// Represents the current result if one exists.
+ /// This value will have set to if some previous
+ /// interceptor suppressed execution by calling .
+ /// This value is typically used as the return value for the implementation of this method.
+ ///
+ ///
+ /// If is , the EF will continue as normal.
+ /// If is , then EF will suppress the operation
+ /// it was about to perform.
+ /// An implementation of this method for any interceptor that is not attempting to suppress
+ /// the operation is to return the value passed in.
+ ///
+ InterceptionResult ConnectionDisposing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
+ => result;
+
+ ///
+ /// Called just before EF intends to call in an async context.
+ ///
+ /// The connection.
+ /// Contextual information about the connection.
+ ///
+ /// Represents the current result if one exists.
+ /// This value will have set to if some previous
+ /// interceptor suppressed execution by calling .
+ /// This value is typically used as the return value for the implementation of this method.
+ ///
+ ///
+ /// If is false, the EF will continue as normal.
+ /// If is true, then EF will suppress the operation
+ /// it was about to perform.
+ /// An implementation of this method for any interceptor that is not attempting to suppress
+ /// the operation is to return the value passed in.
+ ///
+ ValueTask ConnectionDisposingAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
+ => new(result);
+
+ ///
+ /// Called just after EF has called in an async context.
+ ///
+ /// The connection.
+ /// Contextual information about the connection.
+ void ConnectionDisposed(DbConnection connection, ConnectionEndEventData eventData)
+ {
+ }
+
+ ///
+ /// Called just after EF has called .
+ ///
+ /// The connection.
+ /// Contextual information about the connection.
+ /// A representing the asynchronous operation.
+ Task ConnectionDisposedAsync(DbConnection connection, ConnectionEndEventData eventData)
+ => Task.CompletedTask;
+
///
/// Called when closing of a connection has failed with an exception.
///
diff --git a/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs b/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs
index dc579ba8f6e..c34afbc0811 100644
--- a/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs
+++ b/src/EFCore.Relational/Diagnostics/IDbTransactionInterceptor.cs
@@ -46,7 +46,7 @@ public interface IDbTransactionInterceptor : IInterceptor
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
InterceptionResult TransactionStarting(
@@ -70,7 +70,7 @@ InterceptionResult TransactionStarting(
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
DbTransaction TransactionStarted(DbConnection connection, TransactionEndEventData eventData, DbTransaction result)
@@ -93,7 +93,7 @@ DbTransaction TransactionStarted(DbConnection connection, TransactionEndEventDat
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -121,7 +121,7 @@ ValueTask> TransactionStartingAsync(
/// A to observe while waiting for the task to complete.
///
/// A providing the result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -143,7 +143,7 @@ ValueTask TransactionStartedAsync(
///
///
/// The value that will be used as the effective value passed to
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
DbTransaction TransactionUsed(DbConnection connection, TransactionEventData eventData, DbTransaction result)
@@ -162,7 +162,7 @@ DbTransaction TransactionUsed(DbConnection connection, TransactionEventData even
///
/// A containing the value that will be used as the effective value passed
/// to
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in, often using
///
/// If the is canceled.
@@ -188,7 +188,7 @@ ValueTask TransactionUsedAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult TransactionCommitting(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result)
@@ -220,7 +220,7 @@ void TransactionCommitted(DbTransaction transaction, TransactionEndEventData eve
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
/// If the is canceled.
@@ -260,7 +260,7 @@ Task TransactionCommittedAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult TransactionRollingBack(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result)
@@ -292,7 +292,7 @@ void TransactionRolledBack(DbTransaction transaction, TransactionEndEventData ev
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
/// If the is canceled.
@@ -332,7 +332,7 @@ Task TransactionRolledBackAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult CreatingSavepoint(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result)
@@ -363,7 +363,7 @@ void CreatedSavepoint(DbTransaction transaction, TransactionEventData eventData)
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
/// If the is canceled.
@@ -403,7 +403,7 @@ Task CreatedSavepointAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult RollingBackToSavepoint(
@@ -437,7 +437,7 @@ void RolledBackToSavepoint(DbTransaction transaction, TransactionEventData event
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
/// If the is canceled.
@@ -477,7 +477,7 @@ Task RolledBackToSavepointAsync(
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
InterceptionResult ReleasingSavepoint(DbTransaction transaction, TransactionEventData eventData, InterceptionResult result)
@@ -508,7 +508,7 @@ void ReleasedSavepoint(DbTransaction transaction, TransactionEventData eventData
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation
/// it was about to perform.
- /// A normal implementation of this method for any interceptor that is not attempting to suppress
+ /// An implementation of this method for any interceptor that is not attempting to suppress
/// the operation is to return the value passed in.
///
/// If the is canceled.
diff --git a/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs b/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs
index d6544a96610..a30d6370b17 100644
--- a/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs
+++ b/src/EFCore.Relational/Diagnostics/IRelationalConnectionDiagnosticsLogger.cs
@@ -20,6 +20,70 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics;
///
public interface IRelationalConnectionDiagnosticsLogger : IDiagnosticsLogger
{
+ ///
+ /// Logs for the event.
+ ///
+ /// The connection.
+ /// The time that the operation was started.
+ /// The result of execution, which may have been modified by an interceptor.
+ InterceptionResult ConnectionCreating(
+ IRelationalConnection connection,
+ DateTimeOffset startTime);
+
+ ///
+ /// Logs for the event.
+ ///
+ /// The connection.
+ /// The time that the operation was started.
+ /// The amount of time it took to create the connection.
+ DbConnection ConnectionCreated(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration);
+
+ ///
+ /// Logs for the event.
+ ///
+ /// The connection.
+ /// The time that the operation was started.
+ /// The result of execution, which may have been modified by an interceptor.
+ InterceptionResult ConnectionDisposing(
+ IRelationalConnection connection,
+ DateTimeOffset startTime);
+
+ ///
+ /// Logs for the event.
+ ///
+ /// The connection.
+ /// The time that the operation was started.
+ /// A representing the async operation.
+ ValueTask ConnectionDisposingAsync(
+ IRelationalConnection connection,
+ DateTimeOffset startTime);
+
+ ///
+ /// Logs for the event.
+ ///
+ /// The connection.
+ /// The time that the operation was started.
+ /// The amount of time it took to dispose the connection.
+ void ConnectionDisposed(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration);
+
+ ///
+ /// Logs for the event.
+ ///
+ /// The connection.
+ /// The time that the operation was started.
+ /// The amount of time it took to dispose the connection.
+ /// A representing the async operation.
+ Task ConnectionDisposedAsync(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration);
+
///
/// Logs for the event.
///
@@ -146,6 +210,18 @@ Task ConnectionErrorAsync(
bool logErrorAsDebug,
CancellationToken cancellationToken = default);
+ ///
+ /// Whether or need
+ /// to be logged.
+ ///
+ bool ShouldLogConnectionCreate(DateTimeOffset now);
+
+ ///
+ /// Whether or need
+ /// to be logged.
+ ///
+ bool ShouldLogConnectionDispose(DateTimeOffset now);
+
///
/// Whether or need
/// to be logged.
diff --git a/src/EFCore.Relational/Diagnostics/Internal/DbConnectionInterceptorAggregator.cs b/src/EFCore.Relational/Diagnostics/Internal/DbConnectionInterceptorAggregator.cs
index a4f1f265744..84a68c786ba 100644
--- a/src/EFCore.Relational/Diagnostics/Internal/DbConnectionInterceptorAggregator.cs
+++ b/src/EFCore.Relational/Diagnostics/Internal/DbConnectionInterceptorAggregator.cs
@@ -29,6 +29,30 @@ public CompositeDbConnectionInterceptor(IEnumerable in
_interceptors = interceptors.ToArray();
}
+ public InterceptionResult ConnectionCreating(
+ ConnectionCreatingEventData eventData,
+ InterceptionResult result)
+ {
+ for (var i = 0; i < _interceptors.Length; i++)
+ {
+ result = _interceptors[i].ConnectionCreating(eventData, result);
+ }
+
+ return result;
+ }
+
+ public DbConnection ConnectionCreated(
+ ConnectionCreatedEventData eventData,
+ DbConnection result)
+ {
+ for (var i = 0; i < _interceptors.Length; i++)
+ {
+ result = _interceptors[i].ConnectionCreated(eventData, result);
+ }
+
+ return result;
+ }
+
public InterceptionResult ConnectionOpening(
DbConnection connection,
ConnectionEventData eventData,
@@ -127,6 +151,54 @@ await _interceptors[i].ConnectionClosedAsync(connection, eventData)
}
}
+ public InterceptionResult ConnectionDisposing(
+ DbConnection connection,
+ ConnectionEventData eventData,
+ InterceptionResult result)
+ {
+ for (var i = 0; i < _interceptors.Length; i++)
+ {
+ result = _interceptors[i].ConnectionDisposing(connection, eventData, result);
+ }
+
+ return result;
+ }
+
+ public async ValueTask ConnectionDisposingAsync(
+ DbConnection connection,
+ ConnectionEventData eventData,
+ InterceptionResult result)
+ {
+ for (var i = 0; i < _interceptors.Length; i++)
+ {
+ result = await _interceptors[i].ConnectionDisposingAsync(connection, eventData, result)
+ .ConfigureAwait(false);
+ }
+
+ return result;
+ }
+
+ public void ConnectionDisposed(
+ DbConnection connection,
+ ConnectionEndEventData eventData)
+ {
+ for (var i = 0; i < _interceptors.Length; i++)
+ {
+ _interceptors[i].ConnectionDisposed(connection, eventData);
+ }
+ }
+
+ public async Task ConnectionDisposedAsync(
+ DbConnection connection,
+ ConnectionEndEventData eventData)
+ {
+ for (var i = 0; i < _interceptors.Length; i++)
+ {
+ await _interceptors[i].ConnectionDisposedAsync(connection, eventData)
+ .ConfigureAwait(false);
+ }
+ }
+
public void ConnectionFailed(
DbConnection connection,
ConnectionErrorEventData eventData)
diff --git a/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs b/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs
index 1e73dfe579a..f261d19024b 100644
--- a/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs
+++ b/src/EFCore.Relational/Diagnostics/Internal/RelationalConnectionDiagnosticsLogger.cs
@@ -14,6 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics.Internal;
public class RelationalConnectionDiagnosticsLogger
: DiagnosticsLogger, IRelationalConnectionDiagnosticsLogger
{
+ private DateTimeOffset _suppressCreateExpiration;
+ private DateTimeOffset _suppressDisposeExpiration;
private DateTimeOffset _suppressOpenExpiration;
private DateTimeOffset _suppressCloseExpiration;
@@ -423,7 +425,7 @@ public virtual void ConnectionClosed(
{
_suppressCloseExpiration = default;
- definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource);
+ definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource, (int)duration.TotalMilliseconds);
}
if (NeedsEventData(
@@ -461,7 +463,7 @@ public virtual Task ConnectionClosedAsync(
{
_suppressCloseExpiration = default;
- definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource);
+ definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource, (int)duration.TotalMilliseconds);
}
if (NeedsEventData(
@@ -492,7 +494,7 @@ private ConnectionEndEventData BroadcastCollectionClosed(
DateTimeOffset startTime,
TimeSpan duration,
bool async,
- EventDefinition definition,
+ EventDefinition definition,
bool diagnosticSourceEnabled,
bool simpleLogEnabled)
{
@@ -512,16 +514,257 @@ private ConnectionEndEventData BroadcastCollectionClosed(
static string ConnectionClosed(EventDefinitionBase definition, EventData payload)
{
- var d = (EventDefinition)definition;
+ var d = (EventDefinition)definition;
var p = (ConnectionEndEventData)payload;
return d.GenerateMessage(
p.Connection.Database,
- p.Connection.DataSource);
+ p.Connection.DataSource,
+ (int)p.Duration.TotalMilliseconds);
}
}
#endregion ConnectionClosed
+ #region ConnectionDisposing
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InterceptionResult ConnectionDisposing(
+ IRelationalConnection connection,
+ DateTimeOffset startTime)
+ {
+ _suppressDisposeExpiration = startTime + _loggingCacheTime;
+
+ var definition = RelationalResources.LogConnectionDisposing(this);
+
+ if (ShouldLog(definition))
+ {
+ _suppressDisposeExpiration = default;
+
+ definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource);
+ }
+
+ if (NeedsEventData(
+ definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ _suppressDisposeExpiration = default;
+
+ var eventData = BroadcastConnectionDisposing(
+ connection,
+ startTime,
+ async: false,
+ definition,
+ diagnosticSourceEnabled,
+ simpleLogEnabled);
+
+ if (interceptor != null)
+ {
+ return interceptor.ConnectionDisposing(connection.DbConnection, eventData, default);
+ }
+ }
+
+ return default;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual ValueTask ConnectionDisposingAsync(
+ IRelationalConnection connection,
+ DateTimeOffset startTime)
+ {
+ _suppressDisposeExpiration = startTime + _loggingCacheTime;
+
+ var definition = RelationalResources.LogConnectionDisposing(this);
+
+ if (ShouldLog(definition))
+ {
+ _suppressDisposeExpiration = default;
+
+ definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource);
+ }
+
+ if (NeedsEventData(
+ definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ _suppressDisposeExpiration = default;
+
+ var eventData = BroadcastConnectionDisposing(
+ connection,
+ startTime,
+ async: true,
+ definition,
+ diagnosticSourceEnabled,
+ simpleLogEnabled);
+
+ if (interceptor != null)
+ {
+ return interceptor.ConnectionDisposingAsync(connection.DbConnection, eventData, default);
+ }
+ }
+
+ return default;
+ }
+
+ private ConnectionEventData BroadcastConnectionDisposing(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ bool async,
+ EventDefinition definition,
+ bool diagnosticSourceEnabled,
+ bool simpleLogEnabled)
+ {
+ var eventData = new ConnectionEventData(
+ definition,
+ ConnectionDisposing,
+ connection.DbConnection,
+ connection.Context,
+ connection.ConnectionId,
+ async,
+ startTime);
+
+ DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
+
+ return eventData;
+
+ static string ConnectionDisposing(EventDefinitionBase definition, EventData payload)
+ {
+ var d = (EventDefinition)definition;
+ var p = (ConnectionEventData)payload;
+ return d.GenerateMessage(
+ p.Connection.Database,
+ p.Connection.DataSource);
+ }
+ }
+
+ #endregion ConnectionDisposing
+
+ #region ConnectionDisposed
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual void ConnectionDisposed(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration)
+ {
+ var definition = RelationalResources.LogConnectionDisposed(this);
+
+ if (ShouldLog(definition))
+ {
+ _suppressDisposeExpiration = default;
+
+ definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource, (int)duration.TotalMilliseconds);
+ }
+
+ if (NeedsEventData(
+ definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ _suppressDisposeExpiration = default;
+
+ var eventData = BroadcastCollectionDisposed(
+ connection,
+ startTime,
+ duration,
+ false,
+ definition,
+ diagnosticSourceEnabled,
+ simpleLogEnabled);
+
+ interceptor?.ConnectionDisposed(connection.DbConnection, eventData);
+ }
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual Task ConnectionDisposedAsync(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration)
+ {
+ var definition = RelationalResources.LogConnectionDisposed(this);
+
+ if (ShouldLog(definition))
+ {
+ _suppressDisposeExpiration = default;
+
+ definition.Log(this, connection.DbConnection.Database, connection.DbConnection.DataSource, (int)duration.TotalMilliseconds);
+ }
+
+ if (NeedsEventData(
+ definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ _suppressDisposeExpiration = default;
+
+ var eventData = BroadcastCollectionDisposed(
+ connection,
+ startTime,
+ duration,
+ async: true,
+ definition,
+ diagnosticSourceEnabled,
+ simpleLogEnabled);
+
+ if (interceptor != null)
+ {
+ return interceptor.ConnectionDisposedAsync(connection.DbConnection, eventData);
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private ConnectionEndEventData BroadcastCollectionDisposed(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration,
+ bool async,
+ EventDefinition definition,
+ bool diagnosticSourceEnabled,
+ bool simpleLogEnabled)
+ {
+ var eventData = new ConnectionEndEventData(
+ definition,
+ ConnectionDisposed,
+ connection.DbConnection,
+ connection.Context,
+ connection.ConnectionId,
+ async,
+ startTime,
+ duration);
+
+ DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
+
+ return eventData;
+
+ static string ConnectionDisposed(EventDefinitionBase definition, EventData payload)
+ {
+ var d = (EventDefinition)definition;
+ var p = (ConnectionEndEventData)payload;
+ return d.GenerateMessage(
+ p.Connection.Database,
+ p.Connection.DataSource,
+ (int)p.Duration.TotalMilliseconds);
+ }
+ }
+
+ #endregion ConnectionDisposed
+
#region ConnectionError
///
@@ -649,8 +892,175 @@ private void LogConnectionError(
#endregion ConnectionError
+ #region ConnectionCreating
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual InterceptionResult ConnectionCreating(
+ IRelationalConnection connection,
+ DateTimeOffset startTime)
+ {
+ _suppressCreateExpiration = startTime + _loggingCacheTime;
+
+ var definition = RelationalResources.LogConnectionCreating(this);
+
+ if (ShouldLog(definition))
+ {
+ _suppressCreateExpiration = default;
+
+ definition.Log(this);
+ }
+
+ if (NeedsEventData(
+ definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ _suppressCreateExpiration = default;
+
+ var eventData = BroadcastConnectionCreating(
+ connection.Context,
+ connection.ConnectionId,
+ startTime,
+ definition,
+ diagnosticSourceEnabled,
+ simpleLogEnabled);
+
+ if (interceptor != null)
+ {
+ return interceptor.ConnectionCreating(eventData, default);
+ }
+ }
+
+ return default;
+ }
+
+ private ConnectionCreatingEventData BroadcastConnectionCreating(
+ DbContext? context,
+ Guid connectionId,
+ DateTimeOffset startTime,
+ EventDefinition definition,
+ bool diagnosticSourceEnabled,
+ bool simpleLogEnabled)
+ {
+ var eventData = new ConnectionCreatingEventData(
+ definition,
+ ConnectionCreating,
+ context,
+ connectionId,
+ startTime);
+
+ DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
+
+ return eventData;
+
+ static string ConnectionCreating(EventDefinitionBase definition, EventData payload)
+ => ((EventDefinition)definition).GenerateMessage();
+ }
+
+ #endregion ConnectionCreating
+
+ #region ConnectionCreated
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual DbConnection ConnectionCreated(
+ IRelationalConnection connection,
+ DateTimeOffset startTime,
+ TimeSpan duration)
+ {
+ var definition = RelationalResources.LogConnectionCreated(this);
+
+ if (ShouldLog(definition))
+ {
+ _suppressCreateExpiration = default;
+
+ definition.Log(this, (int)duration.TotalMilliseconds);
+ }
+
+ if (NeedsEventData(
+ definition, out var interceptor, out var diagnosticSourceEnabled, out var simpleLogEnabled))
+ {
+ _suppressCreateExpiration = default;
+
+ var eventData = BroadcastConnectionCreated(
+ connection.DbConnection,
+ connection.Context,
+ connection.ConnectionId,
+ startTime,
+ duration,
+ definition,
+ diagnosticSourceEnabled,
+ simpleLogEnabled);
+
+ if (interceptor != null)
+ {
+ return interceptor.ConnectionCreated(eventData, connection.DbConnection);
+ }
+ }
+
+ return connection.DbConnection;
+ }
+
+ private ConnectionCreatedEventData BroadcastConnectionCreated(
+ DbConnection connection,
+ DbContext? context,
+ Guid connectionId,
+ DateTimeOffset startTime,
+ TimeSpan duration,
+ EventDefinition definition,
+ bool diagnosticSourceEnabled,
+ bool simpleLogEnabled)
+ {
+ var eventData = new ConnectionCreatedEventData(
+ definition,
+ ConnectionCreated,
+ connection,
+ context,
+ connectionId,
+ startTime,
+ duration);
+
+ DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
+
+ return eventData;
+
+ static string ConnectionCreated(EventDefinitionBase definition, EventData payload)
+ {
+ var d = (EventDefinition)definition;
+ var p = (ConnectionCreatedEventData)payload;
+ return d.GenerateMessage((int)p.Duration.TotalMilliseconds);
+ }
+ }
+
+ #endregion ConnectionCreated
+
#region ShouldLog checks
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool ShouldLogConnectionCreate(DateTimeOffset now)
+ => now > _suppressCreateExpiration;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual bool ShouldLogConnectionDispose(DateTimeOffset now)
+ => now > _suppressDisposeExpiration;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
index 848873f2e68..614e687e199 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
@@ -29,6 +29,10 @@ private enum Id
ConnectionClosing,
ConnectionClosed,
ConnectionError,
+ ConnectionCreating,
+ ConnectionCreated,
+ ConnectionDisposing,
+ ConnectionDisposed,
// Command events
CommandExecuting = CoreEventId.RelationalBaseId + 100,
@@ -158,6 +162,34 @@ private static EventId MakeConnectionId(Id id)
///
public static readonly EventId ConnectionClosed = MakeConnectionId(Id.ConnectionClosed);
+ ///
+ /// A database connection is going to be disposed. This event is only triggered when Entity Framework is responsible for
+ /// disposing the connection.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId ConnectionDisposing = MakeConnectionId(Id.ConnectionDisposing);
+
+ ///
+ /// A database connection has been disposed. This event is only triggered when Entity Framework is responsible for
+ /// disposing the connection.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId ConnectionDisposed = MakeConnectionId(Id.ConnectionDisposed);
+
///
/// A error occurred while opening or using a database connection.
///
@@ -171,6 +203,32 @@ private static EventId MakeConnectionId(Id id)
///
public static readonly EventId ConnectionError = MakeConnectionId(Id.ConnectionError);
+ ///
+ /// A is about to be created by EF.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId ConnectionCreating = MakeConnectionId(Id.ConnectionCreating);
+
+ ///
+ /// A has been created by EF.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId ConnectionCreated = MakeConnectionId(Id.ConnectionCreated);
+
private static readonly string _sqlPrefix = DbLoggerCategory.Database.Command.Name + ".";
private static EventId MakeCommandId(Id id)
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
index 0050d796af2..d77e753cadf 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
@@ -43,6 +43,42 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogBoolWithDefaultWarning;
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogConnectionCreating;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogConnectionCreated;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogConnectionDisposing;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogConnectionDisposed;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 74fe34cf166..d0a42ec5282 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -1666,9 +1666,9 @@ public static EventDefinition LogBoolWithDefaultWarning(IDiagnos
}
///
- /// Closed connection to database '{database}' on server '{server}'.
+ /// Closed connection to database '{database}' on server '{server}' ({elapsed}ms).
///
- public static EventDefinition LogClosedConnection(IDiagnosticsLogger logger)
+ public static EventDefinition LogClosedConnection(IDiagnosticsLogger logger)
{
var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogClosedConnection;
if (definition == null)
@@ -1676,18 +1676,18 @@ public static EventDefinition LogClosedConnection(IDiagnosticsLo
definition = NonCapturingLazyInitializer.EnsureInitialized(
ref ((RelationalLoggingDefinitions)logger.Definitions).LogClosedConnection,
logger,
- static logger => new EventDefinition(
+ static logger => new EventDefinition(
logger.Options,
RelationalEventId.ConnectionClosed,
LogLevel.Debug,
"RelationalEventId.ConnectionClosed",
- level => LoggerMessage.Define(
+ level => LoggerMessage.Define(
level,
RelationalEventId.ConnectionClosed,
_resourceManager.GetString("LogClosedConnection")!)));
}
- return (EventDefinition)definition;
+ return (EventDefinition)definition;
}
///
@@ -1915,6 +1915,106 @@ public static EventDefinition LogCommittingTransaction(IDiagnosticsLogger logger
return (EventDefinition)definition;
}
+ ///
+ /// Created DbConnection ({elapsed}ms).
+ ///
+ public static EventDefinition LogConnectionCreated(IDiagnosticsLogger logger)
+ {
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionCreated;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionCreated,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ RelationalEventId.ConnectionCreated,
+ LogLevel.Debug,
+ "RelationalEventId.ConnectionCreated",
+ level => LoggerMessage.Define(
+ level,
+ RelationalEventId.ConnectionCreated,
+ _resourceManager.GetString("LogConnectionCreated")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
+ ///
+ /// Creating DbConnection.
+ ///
+ public static EventDefinition LogConnectionCreating(IDiagnosticsLogger logger)
+ {
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionCreating;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionCreating,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ RelationalEventId.ConnectionCreating,
+ LogLevel.Debug,
+ "RelationalEventId.ConnectionCreating",
+ level => LoggerMessage.Define(
+ level,
+ RelationalEventId.ConnectionCreating,
+ _resourceManager.GetString("LogConnectionCreating")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
+ ///
+ /// Disposed connection to database '{database}' on server '{server}' ({elapsed}ms).
+ ///
+ public static EventDefinition LogConnectionDisposed(IDiagnosticsLogger logger)
+ {
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionDisposed;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionDisposed,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ RelationalEventId.ConnectionDisposed,
+ LogLevel.Debug,
+ "RelationalEventId.ConnectionDisposed",
+ level => LoggerMessage.Define(
+ level,
+ RelationalEventId.ConnectionDisposed,
+ _resourceManager.GetString("LogConnectionDisposed")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
+ ///
+ /// Disposing connection to database '{database}' on server '{server}'.
+ ///
+ public static EventDefinition LogConnectionDisposing(IDiagnosticsLogger logger)
+ {
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionDisposing;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogConnectionDisposing,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ RelationalEventId.ConnectionDisposing,
+ LogLevel.Debug,
+ "RelationalEventId.ConnectionDisposing",
+ level => LoggerMessage.Define(
+ level,
+ RelationalEventId.ConnectionDisposing,
+ _resourceManager.GetString("LogConnectionDisposing")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// An error occurred using the connection to database '{database}' on server '{server}'.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index e3f03543a00..7e0d6fa7516 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -480,8 +480,8 @@
Warning RelationalEventId.BoolWithDefaultWarning string string
- Closed connection to database '{database}' on server '{server}'.
- Debug RelationalEventId.ConnectionClosed string string
+ Closed connection to database '{database}' on server '{server}' ({elapsed}ms).
+ Debug RelationalEventId.ConnectionClosed string string int
Closing connection to database '{database}' on server '{server}'.
@@ -519,6 +519,22 @@
Committing transaction.
Debug RelationalEventId.TransactionCommitting
+
+ Created DbConnection. ({elapsed}ms).
+ Debug RelationalEventId.ConnectionCreated int
+
+
+ Creating DbConnection.
+ Debug RelationalEventId.ConnectionCreating
+
+
+ Disposed connection to database '{database}' on server '{server}' ({elapsed}ms).
+ Debug RelationalEventId.ConnectionDisposed string string int
+
+
+ Disposing connection to database '{database}' on server '{server}'.
+ Debug RelationalEventId.ConnectionDisposing string string
+
An error occurred using the connection to database '{database}' on server '{server}'.
Error RelationalEventId.ConnectionError string string
@@ -900,4 +916,4 @@
'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.
-
\ No newline at end of file
+
diff --git a/src/EFCore.Relational/Storage/RelationalConnection.cs b/src/EFCore.Relational/Storage/RelationalConnection.cs
index 934c4654716..e0e1260cb3e 100644
--- a/src/EFCore.Relational/Storage/RelationalConnection.cs
+++ b/src/EFCore.Relational/Storage/RelationalConnection.cs
@@ -141,7 +141,35 @@ protected virtual string GetValidatedConnectionString()
[AllowNull]
public virtual DbConnection DbConnection
{
- get => _connection ??= CreateDbConnection();
+ get
+ {
+ if (_connection != null)
+ {
+ return _connection;
+ }
+
+ var logger = Dependencies.ConnectionLogger;
+ var startTime = DateTimeOffset.UtcNow;
+
+ if (logger.ShouldLogConnectionCreate(startTime))
+ {
+ _stopwatch.Restart();
+
+ var interceptionResult = logger.ConnectionCreating(this, startTime);
+
+ _connection = interceptionResult.HasResult
+ ? interceptionResult.Result
+ : CreateDbConnection();
+
+ _connection = logger.ConnectionCreated(this, startTime, _stopwatch.Elapsed);
+ }
+ else
+ {
+ _connection = CreateDbConnection();
+ }
+
+ return _connection;
+ }
set
{
if (!ReferenceEquals(_connection, value))
@@ -852,14 +880,14 @@ public virtual bool Close()
{
_stopwatch.Restart();
- var interceptionResult = Dependencies.ConnectionLogger.ConnectionClosing(this, startTime);
+ var interceptionResult = logger.ConnectionClosing(this, startTime);
if (!interceptionResult.IsSuppressed)
{
CloseDbConnection();
}
- Dependencies.ConnectionLogger.ConnectionClosed(this, startTime, _stopwatch.Elapsed);
+ logger.ConnectionClosed(this, startTime, _stopwatch.Elapsed);
}
else
{
@@ -870,7 +898,7 @@ public virtual bool Close()
}
catch (Exception e)
{
- Dependencies.ConnectionLogger.ConnectionError(this, e, startTime, _stopwatch.Elapsed, false);
+ logger.ConnectionError(this, e, startTime, _stopwatch.Elapsed, false);
throw;
}
@@ -920,7 +948,7 @@ public virtual async Task CloseAsync()
{
_stopwatch.Restart();
- var interceptionResult = await Dependencies.ConnectionLogger.ConnectionClosingAsync(this, startTime)
+ var interceptionResult = await logger.ConnectionClosingAsync(this, startTime)
.ConfigureAwait(false);
if (!interceptionResult.IsSuppressed)
@@ -928,7 +956,7 @@ public virtual async Task CloseAsync()
await CloseDbConnectionAsync().ConfigureAwait(false);
}
- await Dependencies.ConnectionLogger.ConnectionClosedAsync(this, startTime, _stopwatch.Elapsed)
+ await logger.ConnectionClosedAsync(this, startTime, _stopwatch.Elapsed)
.ConfigureAwait(false);
}
else
@@ -940,7 +968,7 @@ await Dependencies.ConnectionLogger.ConnectionClosedAsync(this, startTime, _stop
}
catch (Exception e)
{
- await Dependencies.ConnectionLogger.ConnectionErrorAsync(
+ await logger.ConnectionErrorAsync(
this,
e,
startTime,
@@ -1053,12 +1081,54 @@ protected virtual async ValueTask ResetStateAsync(bool disposeDbConnection)
/// providers to make a different call instead.
///
protected virtual void DisposeDbConnection()
- => DbConnection.Dispose();
+ {
+ var logger = Dependencies.ConnectionLogger;
+ var startTime = DateTimeOffset.UtcNow;
+
+ if (logger.ShouldLogConnectionDispose(startTime))
+ {
+ _stopwatch.Restart();
+
+ var interceptionResult = logger.ConnectionDisposing(this, startTime);
+
+ if (!interceptionResult.IsSuppressed)
+ {
+ DbConnection.Dispose();
+ }
+
+ logger.ConnectionDisposed(this, startTime, _stopwatch.Elapsed);
+ }
+ else
+ {
+ DbConnection.Dispose();
+ }
+ }
///
/// Template method that by default calls but can be overridden by
/// providers to make a different call instead.
///
- protected virtual ValueTask DisposeDbConnectionAsync()
- => DbConnection.DisposeAsync();
+ protected virtual async ValueTask DisposeDbConnectionAsync()
+ {
+ var logger = Dependencies.ConnectionLogger;
+ var startTime = DateTimeOffset.UtcNow;
+
+ if (logger.ShouldLogConnectionDispose(startTime))
+ {
+ _stopwatch.Restart();
+
+ var interceptionResult = await logger.ConnectionDisposingAsync(this, startTime).ConfigureAwait(false);
+
+ if (!interceptionResult.IsSuppressed)
+ {
+ await DbConnection.DisposeAsync().ConfigureAwait(false);
+ }
+
+ await logger.ConnectionDisposedAsync(this, startTime, _stopwatch.Elapsed).ConfigureAwait(false);
+ }
+ else
+ {
+ await DbConnection.DisposeAsync().ConfigureAwait(false);
+ }
+ }
}
diff --git a/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs b/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs
index cf9215e8e89..41228332c54 100644
--- a/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs
+++ b/src/EFCore/Diagnostics/ISaveChangesInterceptor.cs
@@ -43,7 +43,7 @@ public interface ISaveChangesInterceptor : IInterceptor
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result)
@@ -63,7 +63,7 @@ InterceptionResult SavingChanges(DbContextEventData eventData, Interception
///
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
int SavedChanges(SaveChangesCompletedEventData eventData, int result)
@@ -92,7 +92,7 @@ void SaveChangesFailed(DbContextErrorEventData eventData)
/// If is false, the EF will continue as normal.
/// If is true, then EF will suppress the operation it
/// was about to perform and use instead.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
/// If the is canceled.
@@ -117,7 +117,7 @@ ValueTask> SavingChangesAsync(
/// A to observe while waiting for the task to complete.
///
/// The result that EF will use.
- /// A normal implementation of this method for any interceptor that is not attempting to change the result
+ /// An implementation of this method for any interceptor that is not attempting to change the result
/// is to return the value passed in.
///
/// If the is canceled.
diff --git a/test/EFCore.Relational.Specification.Tests/ConnectionInterceptionTestBase.cs b/test/EFCore.Relational.Specification.Tests/ConnectionInterceptionTestBase.cs
index 6814af67f90..6e842f214f4 100644
--- a/test/EFCore.Relational.Specification.Tests/ConnectionInterceptionTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/ConnectionInterceptionTestBase.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#nullable enable
+
using System.Data;
namespace Microsoft.EntityFrameworkCore;
@@ -209,6 +211,419 @@ public virtual async Task Intercept_connection_that_throws_on_open(bool async)
AssertErrorOnOpen(context, interceptor, async);
}
+ [ConditionalTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public virtual async Task Intercept_connection_creation_passively(bool async)
+ {
+ var interceptor = new ConnectionCreationInterceptor();
+ var context = new ConnectionStringContext(ConfigureProvider);
+ var connectionDisposed = false;
+ context.Interceptors.Add(interceptor);
+
+ _ = context.Model;
+
+ Assert.False(interceptor.CreatingCalled);
+ Assert.False(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+
+ var connection = context.Database.GetDbConnection();
+ connection.Disposed += (_, __) => connectionDisposed = true;
+
+ Assert.True(interceptor.CreatingCalled);
+ Assert.True(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+ Assert.Same(context, interceptor.Context);
+ Assert.Same(connection, interceptor.Connection);
+
+ if (async)
+ {
+ await context.DisposeAsync();
+ }
+ else
+ {
+ context.Dispose();
+ }
+
+ Assert.True(interceptor.DisposingCalled);
+ Assert.True(interceptor.DisposedCalled);
+ Assert.Equal(async, interceptor.AsyncCalled);
+ Assert.NotEqual(async, interceptor.SyncCalled);
+ Assert.True(connectionDisposed);
+ }
+
+ [ConditionalTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public virtual async Task Intercept_connection_to_override_creation(bool async)
+ {
+ using var tempContext = new ConnectionStringContext(ConfigureProvider);
+ var replacementConnection = tempContext.Database.GetDbConnection();
+ var connectionDisposed = false;
+ replacementConnection.Disposed += (_, __) => connectionDisposed = true;
+
+ var interceptor = new ConnectionCreationOverrideInterceptor(replacementConnection);
+ var context = new ConnectionStringContext(ConfigureProvider);
+ context.Interceptors.Add(interceptor);
+
+ _ = context.Model;
+
+ Assert.False(interceptor.CreatingCalled);
+ Assert.False(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+
+ var connection = context.Database.GetDbConnection();
+ Assert.Same(replacementConnection, connection);
+
+ Assert.True(interceptor.CreatingCalled);
+ Assert.True(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+ Assert.Same(context, interceptor.Context);
+ Assert.Same(connection, interceptor.Connection);
+
+ if (async)
+ {
+ await context.DisposeAsync();
+ }
+ else
+ {
+ context.Dispose();
+ }
+
+ Assert.True(interceptor.DisposingCalled);
+ Assert.True(interceptor.DisposedCalled);
+ Assert.Equal(async, interceptor.AsyncCalled);
+ Assert.NotEqual(async, interceptor.SyncCalled);
+ Assert.True(connectionDisposed);
+ }
+
+ [ConditionalTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public virtual async Task Intercept_connection_to_override_connection_after_creation(bool async)
+ {
+ using var tempContext = new ConnectionStringContext(ConfigureProvider);
+ var replacementConnection = tempContext.Database.GetDbConnection();
+ var connectionDisposed = false;
+ replacementConnection.Disposed += (_, __) => connectionDisposed = true;
+
+ var interceptor = new ConnectionCreationReplaceInterceptor(replacementConnection);
+ var context = new ConnectionStringContext(ConfigureProvider);
+ context.Interceptors.Add(interceptor);
+
+ _ = context.Model;
+
+ Assert.False(interceptor.CreatingCalled);
+ Assert.False(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+
+ var connection = context.Database.GetDbConnection();
+ Assert.Same(replacementConnection, connection);
+
+ Assert.True(interceptor.CreatingCalled);
+ Assert.True(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+ Assert.Same(context, interceptor.Context);
+ Assert.Same(connection, interceptor.Connection);
+
+ if (async)
+ {
+ await context.DisposeAsync();
+ }
+ else
+ {
+ context.Dispose();
+ }
+
+ Assert.True(interceptor.DisposingCalled);
+ Assert.True(interceptor.DisposedCalled);
+ Assert.Equal(async, interceptor.AsyncCalled);
+ Assert.NotEqual(async, interceptor.SyncCalled);
+ Assert.True(connectionDisposed);
+ }
+
+ [ConditionalTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public virtual async Task Intercept_connection_to_suppress_dispose(bool async)
+ {
+ var interceptor = new ConnectionCreationNoDisposeInterceptor();
+ var context = new ConnectionStringContext(ConfigureProvider);
+ var connectionDisposed = false;
+ context.Interceptors.Add(interceptor);
+
+ _ = context.Model;
+
+ Assert.False(interceptor.CreatingCalled);
+ Assert.False(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+
+ var connection = context.Database.GetDbConnection();
+ connection.Disposed += (_, __) => connectionDisposed = true;
+
+ Assert.True(interceptor.CreatingCalled);
+ Assert.True(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+ Assert.Same(context, interceptor.Context);
+ Assert.Same(connection, interceptor.Connection);
+
+ if (async)
+ {
+ await context.DisposeAsync();
+ }
+ else
+ {
+ context.Dispose();
+ }
+
+ Assert.True(interceptor.DisposingCalled);
+ Assert.True(interceptor.DisposedCalled);
+ Assert.Equal(async, interceptor.AsyncCalled);
+ Assert.NotEqual(async, interceptor.SyncCalled);
+ Assert.False(connectionDisposed);
+ }
+
+ [ConditionalTheory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public virtual async Task Intercept_connection_creation_with_multiple_interceptors(bool async)
+ {
+ using var tempContext1 = new ConnectionStringContext(ConfigureProvider);
+ var replacementConnection1 = tempContext1.Database.GetDbConnection();
+ var connectionDisposed1 = false;
+ replacementConnection1.Disposed += (_, __) => connectionDisposed1 = true;
+
+ using var tempContext2 = new ConnectionStringContext(ConfigureProvider);
+ var replacementConnection2 = tempContext2.Database.GetDbConnection();
+ var connectionDisposed2 = false;
+ replacementConnection2.Disposed += (_, __) => connectionDisposed2 = true;
+
+ var interceptors = new[]
+ {
+ new ConnectionCreationInterceptor(),
+ new ConnectionCreationOverrideInterceptor(replacementConnection1),
+ new ConnectionCreationInterceptor(),
+ new ConnectionCreationOverrideInterceptor(replacementConnection2)
+ };
+
+ var context = new ConnectionStringContext(ConfigureProvider);
+ context.Interceptors.AddRange(interceptors);
+
+ var connection = context.Database.GetDbConnection();
+ Assert.Same(replacementConnection2, connection);
+
+ foreach (var interceptor in interceptors)
+ {
+ Assert.True(interceptor.CreatingCalled);
+ Assert.True(interceptor.CreatedCalled);
+ Assert.False(interceptor.DisposingCalled);
+ Assert.False(interceptor.DisposedCalled);
+ Assert.Same(context, interceptor.Context);
+ }
+
+ if (async)
+ {
+ await context.DisposeAsync();
+ }
+ else
+ {
+ context.Dispose();
+ }
+
+ foreach (var interceptor in interceptors)
+ {
+ Assert.True(interceptor.DisposingCalled);
+ Assert.True(interceptor.DisposedCalled);
+ Assert.Equal(async, interceptor.AsyncCalled);
+ Assert.NotEqual(async, interceptor.SyncCalled);
+ }
+
+ Assert.False(connectionDisposed1);
+ Assert.True(connectionDisposed2);
+ }
+
+ protected abstract DbContextOptionsBuilder ConfigureProvider(DbContextOptionsBuilder optionsBuilder);
+
+ protected class ConnectionCreationInterceptor : IDbConnectionInterceptor
+ {
+ public DbContext? Context { get; set; }
+ public DbConnection? Connection { get; set; }
+ public Guid ConnectionId { get; set; }
+ public DateTimeOffset StartTime { get; set; }
+ public bool CreatingCalled { get; set; }
+ public bool CreatedCalled { get; set; }
+ public bool AsyncCalled { get; set; }
+ public bool SyncCalled { get; set; }
+ public bool DisposingCalled { get; set; }
+ public bool DisposedCalled { get; set; }
+
+ public virtual InterceptionResult ConnectionCreating(
+ ConnectionCreatingEventData eventData,
+ InterceptionResult result)
+ {
+ Assert.NotNull(eventData.Context);
+ Assert.NotEqual(default, eventData.ConnectionId);
+ Assert.NotEqual(default, eventData.StartTime);
+
+ Context = eventData.Context;
+ ConnectionId = eventData.ConnectionId;
+ StartTime = eventData.StartTime;
+ CreatingCalled = true;
+
+ return result;
+ }
+
+ public virtual DbConnection ConnectionCreated(ConnectionCreatedEventData eventData, DbConnection result)
+ {
+ Assert.Same(Context, eventData.Context);
+ Assert.Equal(ConnectionId, eventData.ConnectionId);
+ Assert.Equal(StartTime, eventData.StartTime);
+ Assert.NotNull(result);
+
+ Connection = result;
+ CreatedCalled = true;
+
+ return result;
+ }
+
+ public virtual InterceptionResult ConnectionDisposing(
+ DbConnection connection,
+ ConnectionEventData eventData,
+ InterceptionResult result)
+ {
+ Assert.False(eventData.IsAsync);
+ SyncCalled = true;
+ AssertDisposing(eventData);
+
+ return result;
+ }
+
+ public virtual ValueTask ConnectionDisposingAsync(
+ DbConnection connection,
+ ConnectionEventData eventData,
+ InterceptionResult result)
+ {
+ Assert.True(eventData.IsAsync);
+ AsyncCalled = true;
+ AssertDisposing(eventData);
+
+ return new ValueTask(result);
+ }
+
+ public virtual void ConnectionDisposed(
+ DbConnection connection,
+ ConnectionEndEventData eventData)
+ {
+ Assert.False(eventData.IsAsync);
+ SyncCalled = true;
+ AssertDisposed(eventData);
+ }
+
+ public virtual Task ConnectionDisposedAsync(
+ DbConnection connection,
+ ConnectionEndEventData eventData)
+ {
+ Assert.True(eventData.IsAsync);
+ AsyncCalled = true;
+ AssertDisposed(eventData);
+
+ return Task.CompletedTask;
+ }
+
+ protected virtual void AssertDisposing(ConnectionEventData eventData)
+ {
+ Assert.Same(Context, eventData.Context);
+ Assert.Equal(ConnectionId, eventData.ConnectionId);
+
+ DisposingCalled = true;
+ }
+
+ protected virtual void AssertDisposed(ConnectionEndEventData eventData)
+ {
+ Assert.Same(Context, eventData.Context);
+ Assert.Equal(ConnectionId, eventData.ConnectionId);
+
+ DisposedCalled = true;
+ }
+ }
+
+ protected class ConnectionCreationOverrideInterceptor : ConnectionCreationInterceptor
+ {
+ private readonly DbConnection _replacementConnection;
+
+ public ConnectionCreationOverrideInterceptor(DbConnection replacementConnection)
+ {
+ _replacementConnection = replacementConnection;
+ }
+
+ public override InterceptionResult ConnectionCreating(
+ ConnectionCreatingEventData eventData,
+ InterceptionResult result)
+ {
+ base.ConnectionCreating(eventData, result);
+
+ return InterceptionResult.SuppressWithResult(_replacementConnection);
+ }
+ }
+
+ protected class ConnectionCreationReplaceInterceptor : ConnectionCreationInterceptor
+ {
+ private readonly DbConnection _replacementConnection;
+
+ public ConnectionCreationReplaceInterceptor(DbConnection replacementConnection)
+ {
+ _replacementConnection = replacementConnection;
+ }
+
+ public override DbConnection ConnectionCreated(ConnectionCreatedEventData eventData, DbConnection result)
+ {
+ base.ConnectionCreated(eventData, result);
+
+ Connection = _replacementConnection;
+ return _replacementConnection;
+ }
+ }
+
+ protected class ConnectionCreationNoDisposeInterceptor : ConnectionCreationInterceptor
+ {
+ public override InterceptionResult ConnectionDisposing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
+ {
+ base.ConnectionDisposing(connection, eventData, result);
+
+ return InterceptionResult.Suppress();
+ }
+
+ public override async ValueTask ConnectionDisposingAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
+ {
+ await base.ConnectionDisposingAsync(connection, eventData, result);
+
+ return InterceptionResult.Suppress();
+ }
+ }
+
+ protected class ConnectionStringContext : DbContext
+ {
+ private readonly Func _configureProvider;
+
+ public ConnectionStringContext(Func configureProvider)
+ {
+ _configureProvider = configureProvider;
+ }
+
+ public List Interceptors { get; } = new List();
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ => _configureProvider(optionsBuilder).AddInterceptors(Interceptors);
+ }
+
protected abstract BadUniverseContext CreateBadUniverse(DbContextOptionsBuilder optionsBuilder);
protected class BadUniverseContext : UniverseContext
@@ -259,8 +674,8 @@ public override async ValueTask ConnectionOpeningAsync(
protected class ConnectionInterceptor : IDbConnectionInterceptor
{
- public DbContext Context { get; set; }
- public Exception Exception { get; set; }
+ public DbContext? Context { get; set; }
+ public Exception? Exception { get; set; }
public DbContextId ContextId { get; set; }
public Guid ConnectionId { get; set; }
public bool AsyncCalled { get; set; }
@@ -401,7 +816,7 @@ protected virtual void AssertOpening(ConnectionEventData eventData)
{
Assert.NotNull(eventData.Context);
Assert.NotEqual(default, eventData.ConnectionId);
- Assert.NotEqual(default, eventData.Context.ContextId);
+ Assert.NotEqual(default, eventData.Context!.ContextId);
Context = eventData.Context;
ContextId = Context.ContextId;
@@ -413,7 +828,7 @@ protected virtual void AssertOpened(ConnectionEndEventData eventData)
{
Assert.Same(Context, eventData.Context);
Assert.Equal(ConnectionId, eventData.ConnectionId);
- Assert.Equal(ContextId, eventData.Context.ContextId);
+ Assert.Equal(ContextId, eventData.Context!.ContextId);
OpenedCalled = true;
}
@@ -422,7 +837,7 @@ protected virtual void AssertClosing(ConnectionEventData eventData)
{
Assert.NotNull(eventData.Context);
Assert.NotEqual(default, eventData.ConnectionId);
- Assert.NotEqual(default, eventData.Context.ContextId);
+ Assert.NotEqual(default, eventData.Context!.ContextId);
Context = eventData.Context;
ContextId = Context.ContextId;
@@ -434,7 +849,7 @@ protected virtual void AssertClosed(ConnectionEndEventData eventData)
{
Assert.Same(Context, eventData.Context);
Assert.Equal(ConnectionId, eventData.ConnectionId);
- Assert.Equal(ContextId, eventData.Context.ContextId);
+ Assert.Equal(ContextId, eventData.Context!.ContextId);
ClosedCalled = true;
}
@@ -443,7 +858,7 @@ protected virtual void AssertFailed(ConnectionErrorEventData eventData)
{
Assert.Same(Context, eventData.Context);
Assert.Equal(ConnectionId, eventData.ConnectionId);
- Assert.Equal(ContextId, eventData.Context.ContextId);
+ Assert.Equal(ContextId, eventData.Context!.ContextId);
Assert.NotNull(eventData.Exception);
Exception = eventData.Exception;
diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs
index 2116ad5afed..63763e91ff7 100644
--- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs
+++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs
@@ -223,14 +223,24 @@ public override
typeof(DbConnectionInterceptor).GetMethod(nameof(DbConnectionInterceptor.ConnectionClosedAsync)),
typeof(IDbConnectionInterceptor).GetMethod(nameof(IDbConnectionInterceptor.ConnectionClosingAsync)),
typeof(IDbConnectionInterceptor).GetMethod(nameof(IDbConnectionInterceptor.ConnectionClosedAsync)),
+ typeof(IDbConnectionInterceptor).GetMethod(nameof(IDbConnectionInterceptor.ConnectionDisposingAsync)),
+ typeof(IDbConnectionInterceptor).GetMethod(nameof(IDbConnectionInterceptor.ConnectionDisposedAsync)),
typeof(IRelationalConnectionDiagnosticsLogger).GetMethod(
nameof(IRelationalConnectionDiagnosticsLogger.ConnectionClosingAsync)),
typeof(IRelationalConnectionDiagnosticsLogger).GetMethod(
nameof(IRelationalConnectionDiagnosticsLogger.ConnectionClosedAsync)),
+ typeof(IRelationalConnectionDiagnosticsLogger).GetMethod(
+ nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposingAsync)),
+ typeof(IRelationalConnectionDiagnosticsLogger).GetMethod(
+ nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposedAsync)),
typeof(RelationalConnectionDiagnosticsLogger).GetMethod(
nameof(IRelationalConnectionDiagnosticsLogger.ConnectionClosingAsync)),
typeof(RelationalConnectionDiagnosticsLogger).GetMethod(
- nameof(IRelationalConnectionDiagnosticsLogger.ConnectionClosedAsync))
+ nameof(IRelationalConnectionDiagnosticsLogger.ConnectionClosedAsync)),
+ typeof(RelationalConnectionDiagnosticsLogger).GetMethod(
+ nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposingAsync)),
+ typeof(RelationalConnectionDiagnosticsLogger).GetMethod(
+ nameof(IRelationalConnectionDiagnosticsLogger.ConnectionDisposedAsync))
};
public List> RelationalMetadataMethods { get; } = new();
diff --git a/test/EFCore.SqlServer.FunctionalTests/ConnectionInterceptionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ConnectionInterceptionSqlServerTest.cs
index 52b164395ce..1014ea6e610 100644
--- a/test/EFCore.SqlServer.FunctionalTests/ConnectionInterceptionSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/ConnectionInterceptionSqlServerTest.cs
@@ -1,7 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#nullable enable
+
using System.Data;
+using System.Diagnostics.CodeAnalysis;
namespace Microsoft.EntityFrameworkCore;
@@ -26,11 +29,15 @@ protected override IServiceCollection InjectInterceptors(
=> base.InjectInterceptors(serviceCollection.AddEntityFrameworkSqlServer(), injectedInterceptors);
}
+ protected override DbContextOptionsBuilder ConfigureProvider(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.UseSqlServer();
+
protected override BadUniverseContext CreateBadUniverse(DbContextOptionsBuilder optionsBuilder)
=> new(optionsBuilder.UseSqlServer(new FakeDbConnection()).Options);
public class FakeDbConnection : DbConnection
{
+ [AllowNull]
public override string ConnectionString { get; set; }
public override string Database
diff --git a/test/EFCore.Sqlite.FunctionalTests/ConnectionInterceptionSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/ConnectionInterceptionSqliteTest.cs
index a308d7b2aa0..a7c8acf6884 100644
--- a/test/EFCore.Sqlite.FunctionalTests/ConnectionInterceptionSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/ConnectionInterceptionSqliteTest.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#nullable enable
+
namespace Microsoft.EntityFrameworkCore;
public abstract class ConnectionInterceptionSqliteTestBase : ConnectionInterceptionTestBase
@@ -10,6 +12,9 @@ protected ConnectionInterceptionSqliteTestBase(InterceptionSqliteFixtureBase fix
{
}
+ protected override DbContextOptionsBuilder ConfigureProvider(DbContextOptionsBuilder optionsBuilder)
+ => optionsBuilder.UseSqlite();
+
protected override BadUniverseContext CreateBadUniverse(DbContextOptionsBuilder optionsBuilder)
=> new(optionsBuilder.UseSqlite("Data Source=file:data.db?mode=invalidmode").Options);