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);