Skip to content

Commit

Permalink
[release/7.0] Reset query tracking behavior correctly if set on DbCon…
Browse files Browse the repository at this point in the history
…textOptions (#29797)

* Reset query tracking behavior correctly if set on DbContextOptions

Fixes #29733

* Reset query tracking behavior correctly if set on DbContextOptions

Port of #29766
Fixes #29733
  • Loading branch information
ajcvickers authored Jan 4, 2023
1 parent a00c229 commit c70e72a
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 28 deletions.
21 changes: 19 additions & 2 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class DbContext :
IDbSetCache,
IDbContextPoolable
{
private static readonly bool QuirkEnabled29733
= AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue29733", out var enabled) && enabled;

private readonly DbContextOptions _options;

private IDictionary<(Type Type, string? Name), object>? _sets;
Expand Down Expand Up @@ -882,8 +885,20 @@ private void SetLeaseInternal(DbContextLease lease)
|| _configurationSnapshot.HasChangeTrackerConfiguration)
{
var changeTracker = ChangeTracker;
if (QuirkEnabled29733
&& _configurationSnapshot.QueryTrackingBehavior.HasValue)
{
changeTracker.QueryTrackingBehavior = _configurationSnapshot.QueryTrackingBehavior.Value;
}
else
{
((IResettableService)changeTracker).ResetState();
if (_configurationSnapshot.QueryTrackingBehavior.HasValue)
{
changeTracker.QueryTrackingBehavior = _configurationSnapshot.QueryTrackingBehavior.Value;
}
}
changeTracker.AutoDetectChangesEnabled = _configurationSnapshot.AutoDetectChangesEnabled;
changeTracker.QueryTrackingBehavior = _configurationSnapshot.QueryTrackingBehavior;
changeTracker.LazyLoadingEnabled = _configurationSnapshot.LazyLoadingEnabled;
changeTracker.CascadeDeleteTiming = _configurationSnapshot.CascadeDeleteTiming;
changeTracker.DeleteOrphansTiming = _configurationSnapshot.DeleteOrphansTiming;
Expand Down Expand Up @@ -940,7 +955,9 @@ void IDbContextPoolable.SnapshotConfiguration()
_changeTracker != null,
changeDetectorEvents != null,
_changeTracker?.AutoDetectChangesEnabled ?? true,
_changeTracker?.QueryTrackingBehavior ?? QueryTrackingBehavior.TrackAll,
QuirkEnabled29733
? _changeTracker?.QueryTrackingBehavior ?? QueryTrackingBehavior.TrackAll
: _changeTracker?.QueryTrackingBehavior,
_database?.AutoTransactionBehavior ?? AutoTransactionBehavior.WhenNeeded,
_database?.AutoSavepointsEnabled ?? true,
_changeTracker?.LazyLoadingEnabled ?? true,
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore/Internal/DbContextPoolConfigurationSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public DbContextPoolConfigurationSnapshot(
bool hasChangeTrackerConfiguration,
bool hasChangeDetectorConfiguration,
bool autoDetectChangesEnabled,
QueryTrackingBehavior queryTrackingBehavior,
QueryTrackingBehavior? queryTrackingBehavior,
AutoTransactionBehavior autoTransactionBehavior,
bool autoSavepointsEnabled,
bool lazyLoadingEnabled,
Expand Down Expand Up @@ -135,7 +135,7 @@ public DbContextPoolConfigurationSnapshot(
/// 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.
/// </summary>
public QueryTrackingBehavior QueryTrackingBehavior { get; }
public QueryTrackingBehavior? QueryTrackingBehavior { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
112 changes: 88 additions & 24 deletions test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,51 @@ private static DbContextOptionsBuilder ConfigureOptions(DbContextOptionsBuilder
.UseSqlServer(SqlServerNorthwindTestStoreFactory.NorthwindConnectionString)
.EnableServiceProviderCaching(false);

private static IServiceProvider BuildServiceProvider<TContextService, TContext>()
private static IServiceProvider BuildServiceProvider<TContextService, TContext>(Action<DbContextOptionsBuilder> optionsAction = null)
where TContextService : class
where TContext : DbContext, TContextService
=> new ServiceCollection()
.AddDbContextPool<TContextService, TContext>(ob => ConfigureOptions(ob))
.AddDbContextPool<ISecondContext, SecondContext>(ob => ConfigureOptions(ob))
.AddDbContextPool<TContextService, TContext>(
ob =>
{
var builder = ConfigureOptions(ob);
if (optionsAction != null)
{
optionsAction(builder);
}
})
.AddDbContextPool<ISecondContext, SecondContext>(
ob =>
{
var builder = ConfigureOptions(ob);
if (optionsAction != null)
{
optionsAction(builder);
}
})
.BuildServiceProvider(validateScopes: true);

private static IServiceProvider BuildServiceProvider<TContext>()
private static IServiceProvider BuildServiceProvider<TContext>(Action<DbContextOptionsBuilder> optionsAction = null)
where TContext : DbContext
=> new ServiceCollection()
.AddDbContextPool<TContext>(ob => ConfigureOptions(ob))
.AddDbContextPool<SecondContext>(ob => ConfigureOptions(ob))
.AddDbContextPool<TContext>(
ob =>
{
var builder = ConfigureOptions(ob);
if (optionsAction != null)
{
optionsAction(builder);
}
})
.AddDbContextPool<SecondContext>(
ob =>
{
var builder = ConfigureOptions(ob);
if (optionsAction != null)
{
optionsAction(builder);
}
})
.BuildServiceProvider(validateScopes: true);

private static IServiceProvider BuildServiceProviderWithFactory<TContext>()
Expand Down Expand Up @@ -758,15 +790,27 @@ public async Task Contexts_are_pooled_with_factory(bool async, bool withDependen
}

[ConditionalTheory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(true, true)]
public async Task Context_configuration_is_reset(bool useInterface, bool async)
[InlineData(false, false, null)]
[InlineData(true, false, null)]
[InlineData(false, true, null)]
[InlineData(true, true, null)]
[InlineData(false, false, QueryTrackingBehavior.TrackAll)]
[InlineData(true, false, QueryTrackingBehavior.TrackAll)]
[InlineData(false, true, QueryTrackingBehavior.TrackAll)]
[InlineData(true, true, QueryTrackingBehavior.TrackAll)]
[InlineData(false, false, QueryTrackingBehavior.NoTracking)]
[InlineData(true, false, QueryTrackingBehavior.NoTracking)]
[InlineData(false, true, QueryTrackingBehavior.NoTracking)]
[InlineData(true, true, QueryTrackingBehavior.NoTracking)]
[InlineData(false, false, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
[InlineData(true, false, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
[InlineData(false, true, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
[InlineData(true, true, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
public async Task Context_configuration_is_reset(bool useInterface, bool async, QueryTrackingBehavior? queryTrackingBehavior)
{
var serviceProvider = useInterface
? BuildServiceProvider<IPooledContext, PooledContext>()
: BuildServiceProvider<PooledContext>();
? BuildServiceProvider<IPooledContext, PooledContext>(b => UseQueryTrackingBehavior(b, queryTrackingBehavior))
: BuildServiceProvider<PooledContext>(b => UseQueryTrackingBehavior(b, queryTrackingBehavior));

var serviceScope = serviceProvider.CreateScope();
var scopedProvider = serviceScope.ServiceProvider;
Expand Down Expand Up @@ -828,7 +872,7 @@ public async Task Context_configuration_is_reset(bool useInterface, bool async)

Assert.False(context2!.ChangeTracker.AutoDetectChangesEnabled);
Assert.False(context2.ChangeTracker.LazyLoadingEnabled);
Assert.Equal(QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior);
Assert.Equal(queryTrackingBehavior ?? QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior);
Assert.Equal(CascadeTiming.Never, context2.ChangeTracker.CascadeDeleteTiming);
Assert.Equal(CascadeTiming.Never, context2.ChangeTracker.DeleteOrphansTiming);
Assert.Equal(AutoTransactionBehavior.Never, context2.Database.AutoTransactionBehavior);
Expand Down Expand Up @@ -869,11 +913,17 @@ public async Task Context_configuration_is_reset(bool useInterface, bool async)
}

[ConditionalTheory]
[InlineData(false)]
[InlineData(true)]
public async Task Uninitialized_context_configuration_is_reset_properly(bool async)
[InlineData(false, null)]
[InlineData(true, null)]
[InlineData(false, QueryTrackingBehavior.TrackAll)]
[InlineData(true, QueryTrackingBehavior.TrackAll)]
[InlineData(false, QueryTrackingBehavior.NoTracking)]
[InlineData(true, QueryTrackingBehavior.NoTracking)]
[InlineData(false, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
[InlineData(true, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
public async Task Uninitialized_context_configuration_is_reset_properly(bool async, QueryTrackingBehavior? queryTrackingBehavior)
{
var serviceProvider = BuildServiceProvider<SecondContext>();
var serviceProvider = BuildServiceProvider<SecondContext>(b => UseQueryTrackingBehavior(b, queryTrackingBehavior));

var serviceScope = serviceProvider.CreateScope();
var ctx = serviceScope.ServiceProvider.GetRequiredService<SecondContext>();
Expand Down Expand Up @@ -1122,11 +1172,17 @@ private void ChangeTracker_OnDetectedEntityChanges(object sender, DetectedEntity
=> _changeTracker_OnDetectedEntityChanges = true;

[ConditionalTheory]
[InlineData(false)]
[InlineData(true)]
public async Task Default_Context_configuration_is_reset(bool async)
[InlineData(false, null)]
[InlineData(true, null)]
[InlineData(false, QueryTrackingBehavior.TrackAll)]
[InlineData(true, QueryTrackingBehavior.TrackAll)]
[InlineData(false, QueryTrackingBehavior.NoTracking)]
[InlineData(true, QueryTrackingBehavior.NoTracking)]
[InlineData(false, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
[InlineData(true, QueryTrackingBehavior.NoTrackingWithIdentityResolution)]
public async Task Default_Context_configuration_is_reset(bool async, QueryTrackingBehavior? queryTrackingBehavior)
{
var serviceProvider = BuildServiceProvider<DefaultOptionsPooledContext>();
var serviceProvider = BuildServiceProvider<DefaultOptionsPooledContext>(b => UseQueryTrackingBehavior(b, queryTrackingBehavior));

var serviceScope = serviceProvider.CreateScope();
var scopedProvider = serviceScope.ServiceProvider;
Expand All @@ -1152,7 +1208,7 @@ public async Task Default_Context_configuration_is_reset(bool async)

Assert.True(context2!.ChangeTracker.AutoDetectChangesEnabled);
Assert.True(context2.ChangeTracker.LazyLoadingEnabled);
Assert.Equal(QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior);
Assert.Equal(queryTrackingBehavior ?? QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior);
Assert.Equal(CascadeTiming.Immediate, context2.ChangeTracker.CascadeDeleteTiming);
Assert.Equal(CascadeTiming.Immediate, context2.ChangeTracker.DeleteOrphansTiming);
Assert.Equal(AutoTransactionBehavior.WhenNeeded, context2.Database.AutoTransactionBehavior);
Expand Down Expand Up @@ -1441,7 +1497,7 @@ public async Task Provider_services_are_reset(bool useInterface, bool async)
{
var serviceProvider = useInterface
? BuildServiceProvider<IPooledContext, PooledContext>()
: BuildServiceProvider<PooledContext>();
: BuildServiceProvider<PooledContext>(o => o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

var serviceScope = serviceProvider.CreateScope();
var scopedProvider = serviceScope.ServiceProvider;
Expand Down Expand Up @@ -1968,6 +2024,14 @@ await Task.WhenAll(
})));
}
private void UseQueryTrackingBehavior(DbContextOptionsBuilder optionsBuilder, QueryTrackingBehavior? queryTrackingBehavior)
{
if (queryTrackingBehavior.HasValue)
{
optionsBuilder.UseQueryTrackingBehavior(queryTrackingBehavior.Value);
}
}
private async Task Dispose(IDisposable disposable, bool async)
{
if (async)
Expand Down

0 comments on commit c70e72a

Please sign in to comment.