Skip to content

Commit

Permalink
Add creation and disposal to connection interception
Browse files Browse the repository at this point in the history
Fixes #23087.

Part of #626.
  • Loading branch information
ajcvickers committed May 4, 2022
1 parent 8d80ea9 commit ffc1fc9
Show file tree
Hide file tree
Showing 15 changed files with 1,497 additions and 20 deletions.
59 changes: 59 additions & 0 deletions src/EFCore.Relational/Diagnostics/ConnectionCreatedEventData.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// The <see cref="DiagnosticSource" /> event payload for <see cref="RelationalEventId.ConnectionCreated"/> events.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-diagnostics">Logging, events, and diagnostics</see> for more information and examples.
/// </remarks>
public class ConnectionCreatedEventData : DbContextEventData
{
/// <summary>
/// Constructs the event payload.
/// </summary>
/// <param name="eventDefinition">The event definition.</param>
/// <param name="messageGenerator">A delegate that generates a log message for this event.</param>
/// <param name="connection">The <see cref="DbConnection" />.</param>
/// <param name="context">The <see cref="DbContext" /> currently being used, to null if not known.</param>
/// <param name="connectionId">A correlation ID that identifies the <see cref="DbConnection" /> instance being used.</param>
/// <param name="startTime">The start time of this event.</param>
/// <param name="duration">The duration this event.</param>
public ConnectionCreatedEventData(
EventDefinitionBase eventDefinition,
Func<EventDefinitionBase, EventData, string> messageGenerator,
DbConnection connection,
DbContext? context,
Guid connectionId,
DateTimeOffset startTime,
TimeSpan duration)
: base(eventDefinition, messageGenerator, context)
{
Connection = connection;
ConnectionId = connectionId;
StartTime = startTime;
Duration = duration;
}

/// <summary>
/// The <see cref="DbConnection" />.
/// </summary>
public virtual DbConnection Connection { get; }

/// <summary>
/// A correlation ID that identifies the <see cref="DbConnection" /> instance being used.
/// </summary>
public virtual Guid ConnectionId { get; }

/// <summary>
/// The start time of this event.
/// </summary>
public virtual DateTimeOffset StartTime { get; }

/// <summary>
/// The duration this event.
/// </summary>
public virtual TimeSpan Duration { get; }
}
43 changes: 43 additions & 0 deletions src/EFCore.Relational/Diagnostics/ConnectionCreatingEventData.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// The <see cref="DiagnosticSource" /> event payload for <see cref="RelationalEventId.ConnectionCreating"/> events.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-diagnostics">Logging, events, and diagnostics</see> for more information and examples.
/// </remarks>
public class ConnectionCreatingEventData : DbContextEventData
{
/// <summary>
/// Constructs the event payload.
/// </summary>
/// <param name="eventDefinition">The event definition.</param>
/// <param name="messageGenerator">A delegate that generates a log message for this event.</param>
/// <param name="context">The <see cref="DbContext" /> currently being used, to null if not known.</param>
/// <param name="connectionId">A correlation ID that identifies the <see cref="DbConnection" /> instance being used.</param>
/// <param name="startTime">The start time of this event.</param>
public ConnectionCreatingEventData(
EventDefinitionBase eventDefinition,
Func<EventDefinitionBase, EventData, string> messageGenerator,
DbContext? context,
Guid connectionId,
DateTimeOffset startTime)
: base(eventDefinition, messageGenerator, context)
{
ConnectionId = connectionId;
StartTime = startTime;
}

/// <summary>
/// A correlation ID that identifies the <see cref="DbConnection" /> instance being used.
/// </summary>
public virtual Guid ConnectionId { get; }

/// <summary>
/// The start time of this event.
/// </summary>
public virtual DateTimeOffset StartTime { get; }
}
104 changes: 102 additions & 2 deletions src/EFCore.Relational/Diagnostics/IDbConnectionInterceptor.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
Expand Down Expand Up @@ -29,6 +31,44 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics;
/// </remarks>
public interface IDbConnectionInterceptor : IInterceptor
{
/// <summary>
/// Called just before EF creates a <see cref="DbConnection"/>. This event is not triggered if the application provides the
/// connection to use.
/// </summary>
/// <param name="eventData">Contextual information about the connection.</param>
/// <param name="result">
/// Represents the current result if one exists.
/// This value will have <see cref="InterceptionResult{DbConnection}.HasResult" /> set to <see langword="true" /> if some previous
/// interceptor suppressed execution by calling <see cref="InterceptionResult{DbConnection}.SuppressWithResult" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <returns>
/// If <see cref="InterceptionResult{DbConnection}.HasResult" /> is false, the EF will continue as normal.
/// If <see cref="InterceptionResult{DbConnection}.HasResult" /> is true, then EF will suppress the operation it
/// was about to perform and use <see cref="InterceptionResult{DbConnection}.Result" /> instead.
/// A normal implementation of this method for any interceptor that is not attempting to change the result
/// is to return the <paramref name="result" /> value passed in.
/// </returns>
InterceptionResult<DbConnection> ConnectionCreating(ConnectionCreatingEventData eventData, InterceptionResult<DbConnection> result)
=> result;

/// <summary>
/// Called just after EF creates a <see cref="DbConnection"/>. This event is not triggered if the application provides the
/// connection to use.
/// </summary>
/// <param name="eventData">Contextual information about the connection.</param>
/// <param name="result">
/// The connection that has been created.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <returns>
/// The result that EF will use.
/// A normal implementation of this method for any interceptor that is not attempting to change the result
/// is to return the <paramref name="result" /> value passed in.
/// </returns>
DbConnection ConnectionCreated(ConnectionCreatedEventData eventData, DbConnection result)
=> result;

/// <summary>
/// Called just before EF intends to call <see cref="DbConnection.Open()" />.
/// </summary>
Expand Down Expand Up @@ -98,7 +138,7 @@ Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData event
=> Task.CompletedTask;

/// <summary>
/// Called just before EF intends to call <see cref="DbConnection.CloseAsync()" />.
/// Called just before EF intends to call <see cref="DbConnection.Close()" />.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="eventData">Contextual information about the connection.</param>
Expand All @@ -119,7 +159,7 @@ InterceptionResult ConnectionClosing(DbConnection connection, ConnectionEventDat
=> result;

/// <summary>
/// Called just before EF intends to call <see cref="DbConnection.Close()" /> in an async context.
/// Called just before EF intends to call <see cref="DbConnection.CloseAsync()" /> in an async context.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="eventData">Contextual information about the connection.</param>
Expand Down Expand Up @@ -157,6 +197,66 @@ void ConnectionClosed(DbConnection connection, ConnectionEndEventData eventData)
Task ConnectionClosedAsync(DbConnection connection, ConnectionEndEventData eventData)
=> Task.CompletedTask;

/// <summary>
/// Called just before EF intends to call <see cref="Component.Dispose()" /> for the <see cref="DbConnection"/>.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="eventData">Contextual information about the connection.</param>
/// <param name="result">
/// Represents the current result if one exists.
/// This value will have <see cref="InterceptionResult.IsSuppressed" /> set to <see langword="true" /> if some previous
/// interceptor suppressed execution by calling <see cref="InterceptionResult.Suppress" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <returns>
/// If <see cref="InterceptionResult.IsSuppressed" /> is false, the EF will continue as normal.
/// If <see cref="InterceptionResult.IsSuppressed" /> 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
/// the operation is to return the <paramref name="result" /> value passed in.
/// </returns>
InterceptionResult ConnectionDisposing(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
=> result;

/// <summary>
/// Called just before EF intends to call <see cref="DbConnection.DisposeAsync()" /> in an async context.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="eventData">Contextual information about the connection.</param>
/// <param name="result">
/// Represents the current result if one exists.
/// This value will have <see cref="InterceptionResult.IsSuppressed" /> set to <see langword="true" /> if some previous
/// interceptor suppressed execution by calling <see cref="InterceptionResult.Suppress" />.
/// This value is typically used as the return value for the implementation of this method.
/// </param>
/// <returns>
/// If <see cref="InterceptionResult.IsSuppressed" /> is false, the EF will continue as normal.
/// If <see cref="InterceptionResult.IsSuppressed" /> 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
/// the operation is to return the <paramref name="result" /> value passed in.
/// </returns>
ValueTask<InterceptionResult> ConnectionDisposingAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
=> new(result);

/// <summary>
/// Called just after EF has called <see cref="Component.Dispose()" /> in an async context.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="eventData">Contextual information about the connection.</param>
void ConnectionDisposed(DbConnection connection, ConnectionEndEventData eventData)
{
}

/// <summary>
/// Called just after EF has called <see cref="DbConnection.DisposeAsync()" />.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="eventData">Contextual information about the connection.</param>
/// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns>
Task ConnectionDisposedAsync(DbConnection connection, ConnectionEndEventData eventData)
=> Task.CompletedTask;

/// <summary>
/// Called when closing of a connection has failed with an exception.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,70 @@ namespace Microsoft.EntityFrameworkCore.Diagnostics;
/// </remarks>
public interface IRelationalConnectionDiagnosticsLogger : IDiagnosticsLogger<DbLoggerCategory.Database.Connection>
{
/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionCreating" /> event.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="startTime">The time that the operation was started.</param>
/// <returns>The result of execution, which may have been modified by an interceptor.</returns>
InterceptionResult<DbConnection> ConnectionCreating(
IRelationalConnection connection,
DateTimeOffset startTime);

/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionCreated" /> event.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="startTime">The time that the operation was started.</param>
/// <param name="duration">The amount of time before the connection was created.</param>
DbConnection ConnectionCreated(
IRelationalConnection connection,
DateTimeOffset startTime,
TimeSpan duration);

/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionDisposing" /> event.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="startTime">The time that the operation was started.</param>
/// <returns>The result of execution, which may have been modified by an interceptor.</returns>
InterceptionResult ConnectionDisposing(
IRelationalConnection connection,
DateTimeOffset startTime);

/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionDisposing" /> event.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="startTime">The time that the operation was started.</param>
/// <returns>A <see cref="Task" /> representing the async operation.</returns>
ValueTask<InterceptionResult> ConnectionDisposingAsync(
IRelationalConnection connection,
DateTimeOffset startTime);

/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionDisposed" /> event.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="startTime">The time that the operation was started.</param>
/// <param name="duration">The amount of time before the connection was Disposed.</param>
void ConnectionDisposed(
IRelationalConnection connection,
DateTimeOffset startTime,
TimeSpan duration);

/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionDisposed" /> event.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="startTime">The time that the operation was started.</param>
/// <param name="duration">The amount of time before the connection was Disposed.</param>
/// <returns>A <see cref="Task" /> representing the async operation.</returns>
Task ConnectionDisposedAsync(
IRelationalConnection connection,
DateTimeOffset startTime,
TimeSpan duration);

/// <summary>
/// Logs for the <see cref="RelationalEventId.ConnectionOpening" /> event.
/// </summary>
Expand Down Expand Up @@ -146,6 +210,18 @@ Task ConnectionErrorAsync(
bool logErrorAsDebug,
CancellationToken cancellationToken = default);

/// <summary>
/// Whether <see cref="RelationalEventId.ConnectionCreating" /> or <see cref="RelationalEventId.ConnectionCreated" /> need
/// to be logged.
/// </summary>
bool ShouldLogConnectionCreate(DateTimeOffset now);

/// <summary>
/// Whether <see cref="RelationalEventId.ConnectionDisposing" /> or <see cref="RelationalEventId.ConnectionDisposed" /> need
/// to be logged.
/// </summary>
bool ShouldLogConnectionDispose(DateTimeOffset now);

/// <summary>
/// Whether <see cref="RelationalEventId.ConnectionOpening" /> or <see cref="RelationalEventId.ConnectionOpened" /> need
/// to be logged.
Expand Down
Loading

0 comments on commit ffc1fc9

Please sign in to comment.