Skip to content

Commit

Permalink
Add Add correlation ID for DbContext like we have for DbCommand a…
Browse files Browse the repository at this point in the history
…nd `DbConnection`

Based on more feedback, now one ID and a lease number.

Fixes #16201

Better test coverage for interceptors and Diagnostics registered at the same time

Fixes #16352
  • Loading branch information
ajcvickers committed Jul 2, 2019
1 parent 4fc35a0 commit c55ee41
Show file tree
Hide file tree
Showing 14 changed files with 977 additions and 311 deletions.
41 changes: 30 additions & 11 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public class DbContext :
private bool _initializing;
private bool _disposed;

private readonly Guid _contextId = Guid.NewGuid();
private int _lease;

/// <summary>
/// <para>
/// Initializes a new instance of the <see cref="DbContext" /> class. The
Expand Down Expand Up @@ -137,6 +140,18 @@ public virtual IModel Model
[DebuggerStepThrough] get => DbContextDependencies.Model;
}

/// <summary>
/// <para>
/// A unique identifier for the context instance and pool lease, if any.
/// </para>
/// <para>
/// This identifier is primarily intended as a correlation ID for logging and debugging such
/// that it is easy to identify that multiple events are using the same or different context instances.
/// </para>
/// </summary>
public virtual DbContextId ContextId
=> new DbContextId(_contextId, 0);

/// <summary>
/// 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
Expand Down Expand Up @@ -207,7 +222,8 @@ public virtual IModel Model
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
IDiagnosticsLogger<DbLoggerCategory.Infrastructure> IDbContextDependencies.InfrastructureLogger => DbContextDependencies.InfrastructureLogger;
IDiagnosticsLogger<DbLoggerCategory.Infrastructure> IDbContextDependencies.InfrastructureLogger
=> DbContextDependencies.InfrastructureLogger;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -478,7 +494,7 @@ private void TryDetectChanges(EntityEntry entry)
{
if (ChangeTracker.AutoDetectChangesEnabled)
{
entry.DetectChanges();
entry.DetectChanges();
}
}

Expand Down Expand Up @@ -584,6 +600,7 @@ public virtual async Task<int> SaveChangesAsync(
void IDbContextPoolable.SetPool(IDbContextPool contextPool)
{
_dbContextPool = contextPool;
_lease = 1;
}

/// <summary>
Expand All @@ -610,6 +627,7 @@ DbContextPoolConfigurationSnapshot IDbContextPoolable.SnapshotConfiguration()
void IDbContextPoolable.Resurrect(DbContextPoolConfigurationSnapshot configurationSnapshot)
{
_disposed = false;
++_lease;

if (configurationSnapshot.AutoDetectChangesEnabled != null)
{
Expand Down Expand Up @@ -904,7 +922,7 @@ public virtual async ValueTask<EntityEntry<TEntity>> AddAsync<TEntity>(
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -947,7 +965,7 @@ public virtual EntityEntry<TEntity> Attach<TEntity>([NotNull] TEntity entity)
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1108,7 +1126,7 @@ public virtual async ValueTask<EntityEntry> AddAsync(
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1149,7 +1167,7 @@ public virtual EntityEntry Attach([NotNull] object entity)
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1280,7 +1298,7 @@ public virtual Task AddRangeAsync([NotNull] params object[] entities)
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1317,7 +1335,7 @@ public virtual void AttachRange([NotNull] params object[] entities)
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1435,7 +1453,7 @@ await SetEntityStateAsync(
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Unchanged" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1472,7 +1490,7 @@ public virtual void AttachRange([NotNull] IEnumerable<object> entities)
/// to anything other than the CLR default for the property type.
/// </para>
/// <para>
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified"/>.
/// For entity types without generated keys, the state set is always <see cref="EntityState.Modified" />.
/// </para>
/// <para>
/// Use <see cref="EntityEntry.State" /> to set the state of only a single entity.
Expand Down Expand Up @@ -1574,7 +1592,8 @@ public virtual ValueTask<object> FindAsync([NotNull] Type entityType, [CanBeNull
/// <param name="keyValues">The values of the primary key for the entity to be found.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <returns>The entity found, or null.</returns>
public virtual ValueTask<object> FindAsync([NotNull] Type entityType, [CanBeNull] object[] keyValues, CancellationToken cancellationToken)
public virtual ValueTask<object> FindAsync(
[NotNull] Type entityType, [CanBeNull] object[] keyValues, CancellationToken cancellationToken)
{
CheckDisposed();

Expand Down
100 changes: 100 additions & 0 deletions src/EFCore/DbContextId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// <para>
/// A unique identifier for the context instance and pool lease, if any.
/// </para>
/// <para>
/// This identifier is primarily intended as a correlation ID for logging and debugging such
/// that it is easy to identify that multiple events are using the same or different context instances.
/// </para>
/// </summary>
public readonly struct DbContextId
{
/// <summary>
/// Compares this ID to another ID to see if they represent the same leased context.
/// </summary>
/// <param name="other"> The other ID. </param>
/// <returns> True if they represent the same leased context; false otherwise. </returns>
public bool Equals(DbContextId other)
=> InstanceId == other.InstanceId
&& Lease == other.Lease;

/// <summary>
/// Compares this ID to another ID to see if they represent the same leased context.
/// </summary>
/// <param name="obj"> The other ID. </param>
/// <returns> True if they represent the same leased context; false otherwise. </returns>
public override bool Equals(object obj)
=> obj is DbContextId other && Equals(other);

/// <summary>
/// A hash code for this ID.
/// </summary>
/// <returns> The hash code. </returns>
public override int GetHashCode()
=> HashCode.Combine(InstanceId, Lease);

/// <summary>
/// Compares one ID to another ID to see if they represent the same leased context.
/// </summary>
/// <param name="left"> The first ID. </param>
/// <param name="right"> The second ID. </param>
/// <returns> True if they represent the same leased context; false otherwise. </returns>
public static bool operator ==(DbContextId left, DbContextId right) => left.Equals(right);

/// <summary>
/// Compares one ID to another ID to see if they represent different leased contexts.
/// </summary>
/// <param name="left"> The first ID. </param>
/// <param name="right"> The second ID. </param>
/// <returns> True if they represent different leased contexts; false otherwise. </returns>
public static bool operator !=(DbContextId left, DbContextId right) => !left.Equals(right);

/// <summary>
/// Creates a new <see cref="DbContextId" /> with the given <see cref="InstanceId" /> and lease number.
/// </summary>
/// <param name="id"> A unique identifier for the <see cref="DbContext" /> being used. </param>
/// <param name="lease"> A number indicating whether this is the first, second, third, etc. lease of this instance. </param>
public DbContextId(Guid id, int lease)
{
InstanceId = id;
Lease = lease;
}

/// <summary>
/// <para>
/// A unique identifier for the <see cref="DbContext" /> being used.
/// </para>
/// <para>
/// When context pooling is being used, then this ID must be combined with
/// the <see cref="Lease" /> in order to get a unique ID for the effective instance being used.
/// </para>
/// </summary>
public Guid InstanceId { get; }

/// <summary>
/// <para>
/// A number that is incremented each time this particular <see cref="DbContext" /> instance is leased
/// from the context pool.
/// </para>
/// <para>
/// Will be zero if context pooling is not being used.
/// </para>
/// </summary>
public int Lease { get; }

/// <summary>Returns the fully qualified type name of this instance.</summary>
/// <returns>The fully qualified type name.</returns>
public override string ToString()
{
return InstanceId + ":" + Lease.ToString(CultureInfo.InvariantCulture);
}
}
}
Loading

0 comments on commit c55ee41

Please sign in to comment.