From 53bdc99453265cbe4f4569bb9077dcac39a3630c Mon Sep 17 00:00:00 2001 From: Christopher Jolly Date: Wed, 18 Oct 2023 22:09:15 +0800 Subject: [PATCH] Upgrade DbContextPooling tests --- .../DbContextPoolingTest.cs | 3233 +++++++++-------- 1 file changed, 1750 insertions(+), 1483 deletions(-) diff --git a/test/EFCore.Jet.FunctionalTests/DbContextPoolingTest.cs b/test/EFCore.Jet.FunctionalTests/DbContextPoolingTest.cs index efe7e1b..a5dac66 100644 --- a/test/EFCore.Jet.FunctionalTests/DbContextPoolingTest.cs +++ b/test/EFCore.Jet.FunctionalTests/DbContextPoolingTest.cs @@ -1,1481 +1,1822 @@ -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using EntityFrameworkCore.Jet.Data; +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Data; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using EntityFrameworkCore.Jet.Data; using EntityFrameworkCore.Jet.FunctionalTests.Query; using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; -using Microsoft.EntityFrameworkCore.Infrastructure; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Collections.Specialized; + +// ReSharper disable MethodHasAsyncOverload // ReSharper disable InconsistentNaming // ReSharper disable UnusedAutoPropertyAccessor.Local // ReSharper disable UnusedMember.Local // ReSharper disable ClassNeverInstantiated.Local // ReSharper disable VirtualMemberCallInConstructor -namespace EntityFrameworkCore.Jet.FunctionalTests +namespace EntityFrameworkCore.Jet.FunctionalTests; + +public class DbContextPoolingTest : IClassFixture> { - public class DbContextPoolingTest : IClassFixture> - { - private static DbContextOptionsBuilder ConfigureOptions(DbContextOptionsBuilder optionsBuilder) + private static DbContextOptionsBuilder ConfigureOptions(DbContextOptionsBuilder optionsBuilder) where TContext : DbContext => optionsBuilder .UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) .EnableServiceProviderCaching(false); - private static DbContextOptionsBuilder ConfigureOptions(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder - .UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) - .EnableServiceProviderCaching(false); - - private static IServiceProvider BuildServiceProvider() - where TContextService : class - where TContext : DbContext, TContextService - => new ServiceCollection() - .AddDbContextPool(ob => ConfigureOptions(ob)) - .AddDbContextPool(ob => ConfigureOptions(ob)) - .BuildServiceProvider(validateScopes: true); + private static DbContextOptionsBuilder ConfigureOptions(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder + .UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) + .EnableServiceProviderCaching(false); - private static IServiceProvider BuildServiceProvider() - where TContext : DbContext - => new ServiceCollection() - .AddDbContextPool(ob => ConfigureOptions(ob)) - .AddDbContextPool(ob => ConfigureOptions(ob)) - .BuildServiceProvider(validateScopes: true); + private static IServiceProvider BuildServiceProvider(Action optionsAction = null) + where TContextService : class + where TContext : DbContext, TContextService + => new ServiceCollection() + .AddDbContextPool( + ob => + { + var builder = ConfigureOptions(ob); + if (optionsAction != null) + { + optionsAction(builder); + } + }) + .AddDbContextPool( + ob => + { + var builder = ConfigureOptions(ob); + if (optionsAction != null) + { + optionsAction(builder); + } + }) + .BuildServiceProvider(validateScopes: true); - private static IServiceProvider BuildServiceProviderWithFactory() - where TContext : DbContext - => new ServiceCollection() - .AddPooledDbContextFactory(ob => ConfigureOptions(ob)) - .AddDbContextPool(ob => ConfigureOptions(ob)) - .BuildServiceProvider(validateScopes: true); + private static IServiceProvider BuildServiceProvider(Action optionsAction = null) + where TContext : DbContext + => new ServiceCollection() + .AddDbContextPool( + ob => + { + var builder = ConfigureOptions(ob); + if (optionsAction != null) + { + optionsAction(builder); + } + }) + .AddDbContextPool( + ob => + { + var builder = ConfigureOptions(ob); + if (optionsAction != null) + { + optionsAction(builder); + } + }) + .BuildServiceProvider(validateScopes: true); - private static IServiceProvider BuildServiceProvider(int poolSize) - where TContextService : class - where TContext : DbContext, TContextService - => new ServiceCollection() - .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) - .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) - .BuildServiceProvider(validateScopes: true); + private static IServiceProvider BuildServiceProviderWithFactory() + where TContext : DbContext + => new ServiceCollection() + .AddPooledDbContextFactory(ob => ConfigureOptions(ob)) + .AddDbContextPool(ob => ConfigureOptions(ob)) + .BuildServiceProvider(validateScopes: true); + + private static IServiceProvider BuildServiceProvider(int poolSize) + where TContextService : class + where TContext : DbContext, TContextService + => new ServiceCollection() + .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) + .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) + .BuildServiceProvider(validateScopes: true); + + private static IServiceProvider BuildServiceProvider(int poolSize) + where TContext : DbContext + => new ServiceCollection() + .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) + .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) + .BuildServiceProvider(validateScopes: true); - private static IServiceProvider BuildServiceProvider(int poolSize) - where TContext : DbContext - => new ServiceCollection() - .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) - .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) - .BuildServiceProvider(validateScopes: true); + private static IServiceProvider BuildServiceProviderWithFactory(int poolSize) + where TContext : DbContext + => new ServiceCollection() + .AddPooledDbContextFactory(ob => ConfigureOptions(ob), poolSize) + .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) + .BuildServiceProvider(validateScopes: true); - private static IServiceProvider BuildServiceProviderWithFactory(int poolSize) - where TContext : DbContext - => new ServiceCollection() - .AddPooledDbContextFactory(ob => ConfigureOptions(ob), poolSize) - .AddDbContextPool(ob => ConfigureOptions(ob), poolSize) - .BuildServiceProvider(validateScopes: true); + private static IDbContextFactory BuildFactory(bool withDependencyInjection) + where TContext : DbContext + => withDependencyInjection + ? BuildServiceProviderWithFactory().GetService>() + : new PooledDbContextFactory(ConfigureOptions(new DbContextOptionsBuilder()).Options); - private static IDbContextFactory BuildFactory(bool withDependencyInjection) - where TContext : DbContext - => withDependencyInjection - ? BuildServiceProviderWithFactory().GetService>() - : new PooledDbContextFactory(ConfigureOptions(new DbContextOptionsBuilder()).Options); + private static IDbContextFactory BuildFactory(bool withDependencyInjection, int poolSize) + where TContext : DbContext + => withDependencyInjection + ? BuildServiceProviderWithFactory(poolSize).GetService>() + : new PooledDbContextFactory(ConfigureOptions(new DbContextOptionsBuilder()).Options, poolSize); - private static IDbContextFactory BuildFactory(bool withDependencyInjection, int poolSize) - where TContext : DbContext - => withDependencyInjection - ? BuildServiceProviderWithFactory(poolSize).GetService>() - : new PooledDbContextFactory(ConfigureOptions(new DbContextOptionsBuilder()).Options, poolSize); + private interface IPooledContext + { + } - private interface IPooledContext + private class DefaultOptionsPooledContext : DbContext + { + public DefaultOptionsPooledContext(DbContextOptions options) + : base(options) { } + } + + private class PooledContext : DbContext, IPooledContext + { + public static int DisposedCount; + public static int InstanceCount; + + public static bool ModifyOptions; - private class DefaultOptionsPooledContext : DbContext + public PooledContext(DbContextOptions options) + : base(options) { - public DefaultOptionsPooledContext(DbContextOptions options) - : base(options) - { - } + Interlocked.Increment(ref InstanceCount); + + ChangeTracker.AutoDetectChangesEnabled = false; + ChangeTracker.LazyLoadingEnabled = false; + Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; + Database.AutoSavepointsEnabled = false; + ChangeTracker.CascadeDeleteTiming = CascadeTiming.Never; + ChangeTracker.DeleteOrphansTiming = CascadeTiming.Never; + SavingChanges += (sender, args) => { }; + SavedChanges += (sender, args) => { }; + SaveChangesFailed += (sender, args) => { }; + ChangeTracker.Tracking += (sender, args) => { }; + ChangeTracker.Tracked += (sender, args) => { }; + ChangeTracker.StateChanging += (sender, args) => { }; + ChangeTracker.StateChanged += (sender, args) => { }; + ChangeTracker.DetectingAllChanges += (sender, args) => { }; + ChangeTracker.DetectedAllChanges += (sender, args) => { }; + ChangeTracker.DetectingEntityChanges += (sender, args) => { }; + ChangeTracker.DetectedEntityChanges += (sender, args) => { }; } - private class PooledContext : DbContext, IPooledContext + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - public static int DisposedCount; - public static int InstanceCount; - - public static bool ModifyOptions; - - public PooledContext(DbContextOptions options) - : base(options) + if (ModifyOptions) { - Interlocked.Increment(ref InstanceCount); - - ChangeTracker.AutoDetectChangesEnabled = false; - ChangeTracker.LazyLoadingEnabled = false; - Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; - Database.AutoSavepointsEnabled = false; - ChangeTracker.CascadeDeleteTiming = CascadeTiming.Never; - ChangeTracker.DeleteOrphansTiming = CascadeTiming.Never; - SavingChanges += (sender, args) => { }; - SavedChanges += (sender, args) => { }; - SaveChangesFailed += (sender, args) => { }; - ChangeTracker.Tracking += (sender, args) => { }; - ChangeTracker.Tracked += (sender, args) => { }; - ChangeTracker.StateChanging += (sender, args) => { }; - ChangeTracker.StateChanged += (sender, args) => { }; - ChangeTracker.DetectingAllChanges += (sender, args) => { }; - ChangeTracker.DetectedAllChanges += (sender, args) => { }; - ChangeTracker.DetectingEntityChanges += (sender, args) => { }; - ChangeTracker.DetectedEntityChanges += (sender, args) => { }; + optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); } + } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (ModifyOptions) - { - optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - } - } - - public DbSet Customers { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity().ToTable("Customers"); - - public override void Dispose() - { - base.Dispose(); + public DbSet Customers { get; set; } - Interlocked.Increment(ref DisposedCount); - } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().ToTable("Customers"); + modelBuilder.Entity().ToTable("Orders"); } - private class PooledContextWithOverrides : DbContext, IPooledContext + public override void Dispose() { - public PooledContextWithOverrides(DbContextOptions options) - : base(options) - { - } + base.Dispose(); - public DbSet Customers { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity().ToTable("Customers"); + Interlocked.Increment(ref DisposedCount); } + } - public class Customer + private class PooledContextWithOverrides : DbContext, IPooledContext + { + public PooledContextWithOverrides(DbContextOptions options) + : base(options) { - public string CustomerId { get; set; } - public string CompanyName { get; set; } - public ObservableCollection Orders { get; } = new(); } - public class Order + public DbSet Customers { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { - public string OrderId { get; set; } + modelBuilder.Entity().ToTable("Customers"); + modelBuilder.Entity().ToTable("Orders"); } + } + + public class Customer + { + public string CustomerId { get; set; } + public string CompanyName { get; set; } + public ILazyLoader LazyLoader { get; set; } + public ObservableCollection Orders { get; } = new(); + } + + public class Order + { + public int OrderId { get; set; } + public ILazyLoader LazyLoader { get; set; } + public string CustomerId { get; set; } + public Customer Customer { get; set; } + } + + private interface ISecondContext + { + } + + private class SecondContext : DbContext, ISecondContext + { + public DbSet Blogs { get; set; } - private interface ISecondContext + public SecondContext(DbContextOptions options) + : base(options) { } - private class SecondContext : DbContext, ISecondContext + public class Blog { - public DbSet Blogs { get; set; } + public int Id { get; set; } + } + } - public SecondContext(DbContextOptions options) - : base(options) - { - } + [ConditionalFact] + public void Invalid_pool_size() + { + Assert.Throws( + () => BuildServiceProvider(poolSize: 0)); - public class Blog - { - public int Id { get; set; } - } - } + Assert.Throws( + () => BuildServiceProvider(poolSize: -1)); - [ConditionalFact] - public void Invalid_pool_size() - { - Assert.Throws( - () => BuildServiceProvider(poolSize: 0)); + Assert.Throws( + () => BuildServiceProvider(poolSize: 0)); - Assert.Throws( - () => BuildServiceProvider(poolSize: -1)); + Assert.Throws( + () => BuildServiceProvider(poolSize: -1)); + } - Assert.Throws( - () => BuildServiceProvider(poolSize: 0)); + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public void Invalid_pool_size_with_factory(bool withDependencyInjection) + { + Assert.Throws( + () => BuildFactory(withDependencyInjection, poolSize: 0)); - Assert.Throws( - () => BuildServiceProvider(poolSize: -1)); - } + Assert.Throws( + () => BuildFactory(withDependencyInjection, poolSize: -1)); + } - [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] - public void Invalid_pool_size_with_factory(bool withDependencyInjection) - { - Assert.Throws( - () => BuildFactory(withDependencyInjection, poolSize: 0)); + [ConditionalFact] + public void Validate_pool_size() + { + var serviceProvider = BuildServiceProvider(poolSize: 64); - Assert.Throws( - () => BuildFactory(withDependencyInjection, poolSize: -1)); - } + using var scope = serviceProvider.CreateScope(); - [ConditionalFact] - public void Validate_pool_size() - { - var serviceProvider = BuildServiceProvider(poolSize: 64); + Assert.Equal( + 64, + scope.ServiceProvider + .GetRequiredService() + .GetService() + .FindExtension()!.MaxPoolSize); + } - using var scope = serviceProvider.CreateScope(); + [ConditionalFact] + public void Validate_pool_size_with_service_interface() + { + var serviceProvider = BuildServiceProvider(poolSize: 64); - Assert.Equal( - 64, - scope.ServiceProvider - .GetRequiredService() - .GetService() - .FindExtension()!.MaxPoolSize); - } + using var scope = serviceProvider.CreateScope(); - [ConditionalFact] - public void Validate_pool_size_with_service_interface() - { - var serviceProvider = BuildServiceProvider(poolSize: 64); + Assert.Equal( + 64, + ((DbContext)scope.ServiceProvider + .GetRequiredService()) + .GetService() + .FindExtension()!.MaxPoolSize); + } - using var scope = serviceProvider.CreateScope(); + [ConditionalFact] + public void Validate_pool_size_with_factory() + { + var serviceProvider = BuildServiceProviderWithFactory(poolSize: 64); - Assert.Equal( - 64, - ((DbContext)scope.ServiceProvider - .GetRequiredService()) - .GetService() + using var context = serviceProvider.GetRequiredService>().CreateDbContext(); + + Assert.Equal( + 64, + context.GetService() .FindExtension()!.MaxPoolSize); - } + } - [ConditionalFact] - public void Validate_pool_size_with_factory() - { - var serviceProvider = BuildServiceProviderWithFactory(poolSize: 64); + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public void Validate_pool_size_behavior_with_factory(bool withDependencyInjection) + { + var factory = BuildFactory(withDependencyInjection, poolSize: 1); - using var context = serviceProvider.GetRequiredService>().CreateDbContext(); + var (ctx1, ctx2) = (factory.CreateDbContext(), factory.CreateDbContext()); + ctx1.Dispose(); + ctx2.Dispose(); - Assert.Equal( - 64, - context.GetService() - .FindExtension()!.MaxPoolSize); - } + using var ctx3 = factory.CreateDbContext(); + using var ctx4 = factory.CreateDbContext(); + Assert.Same(ctx1, ctx3); + Assert.NotSame(ctx2, ctx4); + } - [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] - public void Validate_pool_size_behavior_with_factory(bool withDependencyInjection) - { - var factory = BuildFactory(withDependencyInjection, poolSize: 1); + [ConditionalFact] + public void Validate_pool_size_default() + { + var serviceProvider = BuildServiceProvider(); - var (ctx1, ctx2) = (factory.CreateDbContext(), factory.CreateDbContext()); - ctx1.Dispose(); - ctx2.Dispose(); + using var scope = serviceProvider.CreateScope(); - using var ctx3 = factory.CreateDbContext(); - using var ctx4 = factory.CreateDbContext(); - Assert.Same(ctx1, ctx3); - Assert.NotSame(ctx2, ctx4); - } + Assert.Equal( + 1024, + scope.ServiceProvider + .GetRequiredService() + .GetService() + .FindExtension()!.MaxPoolSize); + } - [ConditionalFact] - public void Validate_pool_size_default() - { - var serviceProvider = BuildServiceProvider(); + [ConditionalFact] + public void Validate_pool_size_with_service_interface_default() + { + var serviceProvider = BuildServiceProvider(); - using var scope = serviceProvider.CreateScope(); + using var scope = serviceProvider.CreateScope(); - Assert.Equal( - 1024, - scope.ServiceProvider - .GetRequiredService() - .GetService() - .FindExtension()!.MaxPoolSize); - } + Assert.Equal( + 1024, + ((DbContext)scope.ServiceProvider + .GetRequiredService()) + .GetService() + .FindExtension()!.MaxPoolSize); + } - [ConditionalFact] - public void Validate_pool_size_with_service_interface_default() - { - var serviceProvider = BuildServiceProvider(); + [ConditionalFact] + public void Pool_can_get_context_by_concrete_type_even_when_service_interface_is_used() + { + var serviceProvider = BuildServiceProvider(); - using var scope = serviceProvider.CreateScope(); + using var scope = serviceProvider.CreateScope(); - Assert.Equal( - 1024, - ((DbContext)scope.ServiceProvider - .GetRequiredService()) - .GetService() + Assert.Same( + scope.ServiceProvider.GetRequiredService(), + scope.ServiceProvider.GetRequiredService()); + } + + [ConditionalFact] + public void Validate_pool_size_with_factory_default() + { + var serviceProvider = BuildServiceProviderWithFactory(); + + using var context = serviceProvider.GetRequiredService>().CreateDbContext(); + + Assert.Equal( + 1024, + context.GetService() .FindExtension()!.MaxPoolSize); - } + } - [ConditionalFact] - public void Pool_can_get_context_by_concrete_type_even_when_service_interface_is_used() - { - var serviceProvider = BuildServiceProvider(); + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public void Options_modified_in_on_configuring(bool useInterface) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - using var scope = serviceProvider.CreateScope(); + var scopedProvider = serviceProvider.CreateScope().ServiceProvider; - Assert.Same( - scope.ServiceProvider.GetRequiredService(), - scope.ServiceProvider.GetRequiredService()); - } + PooledContext.ModifyOptions = true; - [ConditionalFact] - public void Validate_pool_size_with_factory_default() + try { - var serviceProvider = BuildServiceProviderWithFactory(); + Assert.Throws( + () => useInterface + ? scopedProvider.GetService() + : scopedProvider.GetService()); + } + finally + { + PooledContext.ModifyOptions = false; + } + } - using var context = serviceProvider.GetRequiredService>().CreateDbContext(); + [ConditionalFact] + public void Options_modified_in_on_configuring_with_factory() + { + var serviceProvider = BuildServiceProviderWithFactory(); + var scopedProvider = serviceProvider.CreateScope().ServiceProvider; - Assert.Equal( - 1024, - context.GetService() - .FindExtension()!.MaxPoolSize); - } + PooledContext.ModifyOptions = true; - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public void Options_modified_in_on_configuring(bool useInterface) + try { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + var factory = scopedProvider.GetService>(); + Assert.Throws(() => factory!.CreateDbContext()); + } + finally + { + PooledContext.ModifyOptions = false; + } + } - var scopedProvider = serviceProvider.CreateScope().ServiceProvider; + private class BadCtorContext : DbContext + { + } - PooledContext.ModifyOptions = true; + [ConditionalFact] + public void Throws_when_used_with_parameterless_constructor_context() + { + var serviceCollection = new ServiceCollection(); + + Assert.Equal( + CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)), + Assert.Throws( + () => serviceCollection.AddDbContextPool( + _ => { })).Message); + + Assert.Equal( + CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)), + Assert.Throws( + () => serviceCollection.AddDbContextPool( + (_, __) => { })).Message); + + Assert.Equal( + CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)), + Assert.Throws( + () => serviceCollection.AddPooledDbContextFactory( + (_, __) => { })).Message); + } - try - { - Assert.Throws( - () => useInterface - ? scopedProvider.GetService() - : scopedProvider.GetService()); - } - finally - { - PooledContext.ModifyOptions = false; - } - } + [ConditionalFact] + public void Throws_when_pooled_context_constructor_has_second_parameter_that_cannot_be_resolved_from_service_provider() + { + var serviceProvider + = new ServiceCollection().AddDbContextPool(_ => { }) + .BuildServiceProvider(validateScopes: true); - [ConditionalFact] - public void Options_modified_in_on_configuring_with_factory() - { - var serviceProvider = BuildServiceProviderWithFactory(); - var scopedProvider = serviceProvider.CreateScope().ServiceProvider; + using var scope = serviceProvider.CreateScope(); - PooledContext.ModifyOptions = true; + Assert.Throws(() => scope.ServiceProvider.GetService()); + } - try - { - var factory = scopedProvider.GetService>(); - Assert.Throws(() => factory!.CreateDbContext()); - } - finally - { - PooledContext.ModifyOptions = false; - } - } + private class TwoParameterConstructorContext : DbContext + { + public string StringParameter { get; } - private class BadCtorContext : DbContext + public TwoParameterConstructorContext(DbContextOptions options, string x) + : base(options) { + StringParameter = x; } + } + + [ConditionalFact] + public void Throws_when_pooled_context_constructor_has_single_parameter_that_cannot_be_resolved_from_service_provider() + { + var serviceProvider + = new ServiceCollection().AddDbContextPool(_ => { }) + .BuildServiceProvider(validateScopes: true); + + using var scope = serviceProvider.CreateScope(); + + Assert.Throws(() => scope.ServiceProvider.GetService()); + } - [ConditionalFact] - public void Throws_when_used_with_parameterless_constructor_context() + private class WrongParameterConstructorContext : DbContext + { + public WrongParameterConstructorContext(string x) + : base(new DbContextOptions()) { - var serviceCollection = new ServiceCollection(); - - Assert.Equal( - CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)), - Assert.Throws( - () => serviceCollection.AddDbContextPool( - _ => { })).Message); - - Assert.Equal( - CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)), - Assert.Throws( - () => serviceCollection.AddDbContextPool( - (_, __) => { })).Message); - - Assert.Equal( - CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)), - Assert.Throws( - () => serviceCollection.AddPooledDbContextFactory( - (_, __) => { })).Message); } + } - [ConditionalFact] - public void Throws_when_pooled_context_constructor_has_more_than_one_parameter() - { - var serviceProvider - = new ServiceCollection().AddDbContextPool(_ => { }) - .BuildServiceProvider(validateScopes: true); + [ConditionalFact] + public void Throws_when_pooled_context_constructor_has_scoped_service() + { + var serviceProvider + = new ServiceCollection() + .AddDbContextPool(_ => { }) + .AddScoped(sp => "string") + .BuildServiceProvider(validateScopes: true); - using var scope = serviceProvider.CreateScope(); + using var scope = serviceProvider.CreateScope(); - Assert.Equal( - CoreStrings.PoolingContextCtorError(nameof(TwoParameterConstructorContext)), - Assert.Throws(() => scope.ServiceProvider.GetService()).Message); - } + Assert.Throws(() => scope.ServiceProvider.GetService()); + } + + [ConditionalFact] + public void Does_not_throw_when_pooled_context_constructor_has_singleton_service() + { + var serviceProvider + = new ServiceCollection() + .AddDbContextPool(_ => { }) + .AddSingleton("string") + .BuildServiceProvider(validateScopes: true); + + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetService(); + + Assert.Equal("string", context.StringParameter); + } + + [ConditionalFact] + public void Does_not_throw_when_parameterless_and_correct_constructor() + { + var serviceProvider + = new ServiceCollection().AddDbContextPool(_ => { }) + .BuildServiceProvider(validateScopes: true); + + using var scope = serviceProvider.CreateScope(); - private class TwoParameterConstructorContext : DbContext + var context = scope.ServiceProvider.GetRequiredService(); + + Assert.Equal("Options", context.ConstructorUsed); + } + + [ConditionalFact] + public void Does_not_throw_when_parameterless_and_correct_constructor_using_factory_pool() + { + var serviceProvider + = new ServiceCollection().AddPooledDbContextFactory(_ => { }) + .BuildServiceProvider(validateScopes: true); + + using var scope = serviceProvider.CreateScope(); + + var factory = scope.ServiceProvider.GetRequiredService>(); + using var context = factory.CreateDbContext(); + + Assert.Equal("Options", context.ConstructorUsed); + } + + private class WithParameterlessConstructorContext : DbContext + { + public string ConstructorUsed { get; } + + public WithParameterlessConstructorContext() { - public TwoParameterConstructorContext(DbContextOptions options, string x) - : base(options) - { - } + ConstructorUsed = "Parameterless"; } - [ConditionalFact] - public void Throws_when_pooled_context_constructor_wrong_parameter() + public WithParameterlessConstructorContext(DbContextOptions options) + : base(options) { - var serviceProvider - = new ServiceCollection().AddDbContextPool(_ => { }) - .BuildServiceProvider(validateScopes: true); + ConstructorUsed = "Options"; + } + } - using var scope = serviceProvider.CreateScope(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Can_pool_non_derived_context(bool useFactory, bool async) + { + var serviceProvider = useFactory + ? BuildServiceProviderWithFactory() + : BuildServiceProvider(); - Assert.Equal( - CoreStrings.PoolingContextCtorError(nameof(WrongParameterConstructorContext)), - Assert.Throws(() => scope.ServiceProvider.GetService()) - .Message); - } + var serviceScope1 = serviceProvider.CreateScope(); + var context1 = await GetContextAsync(serviceScope1); + + var serviceScope2 = serviceProvider.CreateScope(); + var context2 = await GetContextAsync(serviceScope2); + + Assert.NotSame(context1, context2); + + var id1 = context1.ContextId; + var id2 = context2.ContextId; + + Assert.NotEqual(default, id1.InstanceId); + Assert.NotEqual(id1, id2); + Assert.NotEqual(id1.InstanceId, id2.InstanceId); + Assert.Equal(1, id1.Lease); + Assert.Equal(1, id2.Lease); - private class WrongParameterConstructorContext : DbContext + if (useFactory) { - public WrongParameterConstructorContext(string x) - : base(new DbContextOptions()) - { - } + await Dispose(context1, async); } - [ConditionalFact] - public void Does_not_throw_when_parameterless_and_correct_constructor() + await Dispose(serviceScope1, async); + await Dispose(serviceScope2, async); + + if (useFactory) { - var serviceProvider - = new ServiceCollection().AddDbContextPool(_ => { }) - .BuildServiceProvider(validateScopes: true); + await Dispose(context2, async); + } - using var scope = serviceProvider.CreateScope(); + var id1d = context1.ContextId; + var id2d = context2.ContextId; - var context = scope.ServiceProvider.GetRequiredService(); + Assert.Equal(id1, id1d); + Assert.Equal(id1.InstanceId, id1d.InstanceId); + Assert.Equal(1, id1d.Lease); + Assert.Equal(1, id2d.Lease); - Assert.Equal("Options", context.ConstructorUsed); - } + var serviceScope3 = serviceProvider.CreateScope(); + var context3 = await GetContextAsync(serviceScope3); - [ConditionalFact] - public void Does_not_throw_when_parameterless_and_correct_constructor_using_factory_pool() - { - var serviceProvider - = new ServiceCollection().AddPooledDbContextFactory(_ => { }) - .BuildServiceProvider(validateScopes: true); + var id1r = context3.ContextId; - using var scope = serviceProvider.CreateScope(); + Assert.Same(context1, context3); + Assert.Equal(id1.InstanceId, id1r.InstanceId); + Assert.NotEqual(default, id1r.InstanceId); + Assert.NotEqual(id1, id1r); + Assert.Equal(2, id1r.Lease); - var factory = scope.ServiceProvider.GetRequiredService>(); - using var context = factory.CreateDbContext(); + var serviceScope4 = serviceProvider.CreateScope(); + var context4 = await GetContextAsync(serviceScope4); - Assert.Equal("Options", context.ConstructorUsed); - } + var id2r = context4.ContextId; - private class WithParameterlessConstructorContext : DbContext - { - public string ConstructorUsed { get; } + Assert.Same(context2, context4); + Assert.Equal(id2.InstanceId, id2r.InstanceId); + Assert.NotEqual(default, id2r.InstanceId); + Assert.NotEqual(id2, id2r); + Assert.Equal(2, id2r.Lease); - public WithParameterlessConstructorContext() - { - ConstructorUsed = "Parameterless"; - } + async Task GetContextAsync(IServiceScope serviceScope) + => useFactory + ? async + ? await serviceScope.ServiceProvider.GetService>()!.CreateDbContextAsync() + : serviceScope.ServiceProvider.GetService>()!.CreateDbContext() + : serviceScope.ServiceProvider.GetService(); + } - public WithParameterlessConstructorContext(DbContextOptions options) - : base(options) - { - ConstructorUsed = "Options"; - } - } + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public async Task ContextIds_make_sense_when_not_pooling(bool async) + { + var serviceProvider = new ServiceCollection() + .AddDbContext( + ob + => ob.UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) + .EnableServiceProviderCaching(false)) + .BuildServiceProvider(validateScopes: true); - [ConditionalFact] - public void Can_pool_non_derived_context() - { - var serviceProvider = BuildServiceProvider(); + var serviceScope1 = serviceProvider.CreateScope(); + var context1 = serviceScope1.ServiceProvider.GetService(); - var serviceScope1 = serviceProvider.CreateScope(); - var context1 = serviceScope1.ServiceProvider.GetService(); + var serviceScope2 = serviceProvider.CreateScope(); + var context2 = serviceScope2.ServiceProvider.GetService(); - var serviceScope2 = serviceProvider.CreateScope(); - var context2 = serviceScope2.ServiceProvider.GetService(); + Assert.NotSame(context1, context2); - Assert.NotSame(context1, context2); + var id1 = context1!.ContextId; + var id2 = context2!.ContextId; - var id1 = context1.ContextId; - var id2 = context2.ContextId; + Assert.NotEqual(default, id1.InstanceId); + Assert.NotEqual(default, id2.InstanceId); - Assert.NotEqual(default, id1.InstanceId); - Assert.NotEqual(id1, id2); - Assert.NotEqual(id1.InstanceId, id2.InstanceId); - Assert.Equal(1, id1.Lease); - Assert.Equal(1, id2.Lease); + Assert.NotEqual(id1, id2); + Assert.Equal(0, id1.Lease); + Assert.Equal(0, id2.Lease); - serviceScope1.Dispose(); - serviceScope2.Dispose(); + await Dispose(serviceScope1, async); + await Dispose(serviceScope2, async); - var id1d = context1.ContextId; - var id2d = context2.ContextId; + var id1d = context1.ContextId; + var id2d = context2.ContextId; - Assert.Equal(id1, id1d); - Assert.Equal(id1.InstanceId, id1d.InstanceId); - Assert.Equal(1, id1d.Lease); - Assert.Equal(1, id2d.Lease); + Assert.Equal(id1.InstanceId, id1d.InstanceId); + Assert.Equal(id2.InstanceId, id2d.InstanceId); + Assert.Equal(0, id1d.Lease); + Assert.Equal(0, id2d.Lease); - var serviceScope3 = serviceProvider.CreateScope(); - var context3 = serviceScope3.ServiceProvider.GetService(); + var serviceScope3 = serviceProvider.CreateScope(); + var context3 = serviceScope3.ServiceProvider.GetService(); - var id1r = context3.ContextId; + var id1r = context3.ContextId; - Assert.Same(context1, context3); - Assert.Equal(id1.InstanceId, id1r.InstanceId); - Assert.NotEqual(default, id1r.InstanceId); - Assert.NotEqual(id1, id1r); - Assert.Equal(2, id1r.Lease); + Assert.NotSame(context1, context3); + Assert.NotEqual(default, id1r.InstanceId); + Assert.NotEqual(id1.InstanceId, id1r.InstanceId); + Assert.NotEqual(id1, id1r); + Assert.Equal(0, id1r.Lease); - var serviceScope4 = serviceProvider.CreateScope(); - var context4 = serviceScope4.ServiceProvider.GetService(); + var serviceScope4 = serviceProvider.CreateScope(); + var context4 = serviceScope4.ServiceProvider.GetService(); - var id2r = context4.ContextId; + var id2r = context4.ContextId; - Assert.Same(context2, context4); - Assert.Equal(id2.InstanceId, id2r.InstanceId); - Assert.NotEqual(default, id2r.InstanceId); - Assert.NotEqual(id2, id2r); - Assert.Equal(2, id2r.Lease); - } + Assert.NotSame(context2, context4); + Assert.NotEqual(default, id2r.InstanceId); + Assert.NotEqual(id2.InstanceId, id2r.InstanceId); + Assert.NotEqual(id2, id2r); + Assert.Equal(0, id2r.Lease); + } - [ConditionalFact] - public void ContextIds_make_sense_when_not_pooling() - { - var serviceProvider = new ServiceCollection() - .AddDbContext( - ob - => ob.UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString, TestEnvironment.DataAccessProviderFactory) - .EnableServiceProviderCaching(false)) - .BuildServiceProvider(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Contexts_are_pooled(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); + + var serviceScope1 = serviceProvider.CreateScope(); + var scopedProvider1 = serviceScope1.ServiceProvider; + + var context1 = useInterface + ? scopedProvider1.GetService() + : scopedProvider1.GetService(); + + var secondContext1 = useInterface + ? scopedProvider1.GetService() + : scopedProvider1.GetService(); + + var serviceScope2 = serviceProvider.CreateScope(); + var scopedProvider2 = serviceScope2.ServiceProvider; + + var context2 = useInterface + ? scopedProvider2.GetService() + : scopedProvider2.GetService(); - var serviceScope1 = serviceProvider.CreateScope(); - var context1 = serviceScope1.ServiceProvider.GetService(); + var secondContext2 = useInterface + ? scopedProvider2.GetService() + : scopedProvider2.GetService(); - var serviceScope2 = serviceProvider.CreateScope(); - var context2 = serviceScope2.ServiceProvider.GetService(); + Assert.NotSame(context1, context2); + Assert.NotSame(secondContext1, secondContext2); + + await Dispose(serviceScope1, async); + + await Dispose(serviceScope2, async); + + var serviceScope3 = serviceProvider.CreateScope(); + var scopedProvider3 = serviceScope3.ServiceProvider; + + var context3 = useInterface + ? scopedProvider3.GetService() + : scopedProvider3.GetService(); + + var secondContext3 = useInterface + ? scopedProvider3.GetService() + : scopedProvider3.GetService(); + + Assert.Same(context1, context3); + Assert.Same(secondContext1, secondContext3); + + var serviceScope4 = serviceProvider.CreateScope(); + var scopedProvider4 = serviceScope4.ServiceProvider; + + var context4 = useInterface + ? scopedProvider4.GetService() + : scopedProvider4.GetService(); + + var secondContext4 = useInterface + ? scopedProvider4.GetService() + : scopedProvider4.GetService(); + + Assert.Same(context2, context4); + Assert.Same(secondContext2, secondContext4); + + await Dispose(serviceScope3, async); + + await Dispose(serviceScope4, async); + } + + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Contexts_are_pooled_with_factory(bool async, bool withDependencyInjection) + { + var factory = BuildFactory(withDependencyInjection); + + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + var secondContext1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + var secondContext2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + + Assert.NotSame(context1, context2); + Assert.NotSame(secondContext1, secondContext2); + + await Dispose(context1, async); + await Dispose(secondContext1, async); + await Dispose(context2, async); + await Dispose(secondContext2, async); + + var context3 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + var secondContext3 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + + Assert.Same(context1, context3); + Assert.Same(secondContext1, secondContext3); + + var context4 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + var secondContext4 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + + Assert.Same(context2, context4); + Assert.Same(secondContext2, secondContext4); + + await Dispose(context1, async); + await Dispose(secondContext1, async); + await Dispose(context2, async); + await Dispose(secondContext2, async); + } + + [ConditionalTheory] + [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(b => UseQueryTrackingBehavior(b, queryTrackingBehavior)) + : BuildServiceProvider(b => UseQueryTrackingBehavior(b, queryTrackingBehavior)); + + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; + + var context1 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); + + Assert.Null(context1!.Database.GetCommandTimeout()); + + var set = context1.Customers; + var localView = set.Local; + localView.PropertyChanged += LocalView_OnPropertyChanged; + localView.PropertyChanging += LocalView_OnPropertyChanging; + localView.CollectionChanged += LocalView_OnCollectionChanged; + var customer1 = new Customer { CustomerId = "C" }; + context1.Customers.Attach(customer1); + Assert.Equal(1, localView.Count); + Assert.Same(customer1, localView.ToBindingList().Single()); + Assert.Same(customer1, localView.ToObservableCollection().Single()); + Assert.True(_localView_OnPropertyChanging); + Assert.True(_localView_OnPropertyChanged); + Assert.True(_localView_OnCollectionChanged); + + context1.ChangeTracker.AutoDetectChangesEnabled = true; + context1.ChangeTracker.LazyLoadingEnabled = true; + context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; + context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded; + context1.Database.AutoSavepointsEnabled = true; + context1.Database.SetCommandTimeout(1); + context1.ChangeTracker.Tracking += ChangeTracker_OnTracking; + context1.ChangeTracker.Tracked += ChangeTracker_OnTracked; + context1.ChangeTracker.StateChanging += ChangeTracker_OnStateChanging; + context1.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; + context1.ChangeTracker.DetectingAllChanges += ChangeTracker_OnDetectingAllChanges; + context1.ChangeTracker.DetectedAllChanges += ChangeTracker_OnDetectedAllChanges; + context1.ChangeTracker.DetectingEntityChanges += ChangeTracker_OnDetectingEntityChanges; + context1.ChangeTracker.DetectedEntityChanges += ChangeTracker_OnDetectedEntityChanges; + context1.SavingChanges += Context_OnSavingChanges; + context1.SavedChanges += Context_OnSavedChanges; + context1.SaveChangesFailed += Context_OnSaveChangesFailed; + + _localView_OnPropertyChanging = false; + _localView_OnPropertyChanged = false; + _localView_OnCollectionChanged = false; + + await Dispose(serviceScope, async); + + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; + + var context2 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); + + Assert.Same(context1, context2); + + Assert.False(context2!.ChangeTracker.AutoDetectChangesEnabled); + Assert.False(context2.ChangeTracker.LazyLoadingEnabled); + 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); + Assert.False(context2.Database.AutoSavepointsEnabled); + Assert.Null(context1.Database.GetCommandTimeout()); + + Assert.Empty(localView); + Assert.Empty(localView.ToBindingList()); + Assert.Empty(localView.ToObservableCollection()); + + var customer2 = new Customer { CustomerId = "C" }; + context2.Customers.Attach(customer2).State = EntityState.Modified; + context2.Customers.Attach(customer2).State = EntityState.Unchanged; + + Assert.False(_changeTracker_OnTracking); + Assert.False(_changeTracker_OnTracked); + Assert.False(_changeTracker_OnStateChanging); + Assert.False(_changeTracker_OnStateChanged); + + context2.SaveChanges(); + + Assert.False(_changeTracker_OnDetectingAllChanges); + Assert.False(_changeTracker_OnDetectedAllChanges); + Assert.False(_changeTracker_OnDetectingEntityChanges); + Assert.False(_changeTracker_OnDetectedEntityChanges); + Assert.False(_context_OnSavedChanges); + Assert.False(_context_OnSavingChanges); + Assert.False(_context_OnSaveChangesFailed); + + Assert.Same(set, context2!.Customers); + Assert.Same(localView, context2!.Customers.Local); + Assert.Equal(1, localView.Count); + Assert.Same(customer2, localView.ToBindingList().Single()); + Assert.Same(customer2, localView.ToObservableCollection().Single()); + Assert.False(_localView_OnPropertyChanging); + Assert.False(_localView_OnPropertyChanged); + Assert.False(_localView_OnCollectionChanged); + } + + [ConditionalTheory] + [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(b => UseQueryTrackingBehavior(b, queryTrackingBehavior)); + + var serviceScope = serviceProvider.CreateScope(); + var ctx = serviceScope.ServiceProvider.GetRequiredService(); + await Dispose(ctx, async); + await Dispose(serviceScope, async); + + serviceScope = serviceProvider.CreateScope(); + var ctx2 = serviceScope.ServiceProvider.GetRequiredService(); + Assert.Same(ctx, ctx2); + ctx2.Blogs.Add(new SecondContext.Blog()); + await Dispose(ctx2, async); + await Dispose(serviceScope, async); + + serviceScope = serviceProvider.CreateScope(); + var ctx3 = serviceScope.ServiceProvider.GetRequiredService(); + Assert.Same(ctx, ctx3); + Assert.Empty(ctx3.ChangeTracker.Entries()); + await Dispose(ctx2, async); + await Dispose(serviceScope, async); + } + + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Context_configuration_is_reset_with_factory(bool async, bool withDependencyInjection) + { + var factory = BuildFactory(withDependencyInjection); + + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + var set = context1.Customers; + + var localView = set.Local; + localView.PropertyChanged += LocalView_OnPropertyChanged; + localView.PropertyChanging += LocalView_OnPropertyChanging; + localView.CollectionChanged += LocalView_OnCollectionChanged; + var customer1 = new Customer { CustomerId = "C" }; + context1.Customers.Attach(customer1); + Assert.Equal(1, localView.Count); + Assert.Same(customer1, localView.ToBindingList().Single()); + Assert.Same(customer1, localView.ToObservableCollection().Single()); + Assert.True(_localView_OnPropertyChanging); + Assert.True(_localView_OnPropertyChanged); + Assert.True(_localView_OnCollectionChanged); + + context1.ChangeTracker.AutoDetectChangesEnabled = true; + context1.ChangeTracker.LazyLoadingEnabled = true; + context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; + context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded; + context1.Database.AutoSavepointsEnabled = true; + context1.ChangeTracker.Tracking += ChangeTracker_OnTracking; + context1.ChangeTracker.Tracked += ChangeTracker_OnTracked; + context1.ChangeTracker.StateChanging += ChangeTracker_OnStateChanging; + context1.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; + context1.ChangeTracker.DetectingAllChanges += ChangeTracker_OnDetectingAllChanges; + context1.ChangeTracker.DetectedAllChanges += ChangeTracker_OnDetectedAllChanges; + context1.ChangeTracker.DetectingEntityChanges += ChangeTracker_OnDetectingEntityChanges; + context1.ChangeTracker.DetectedEntityChanges += ChangeTracker_OnDetectedEntityChanges; + context1.SavingChanges += Context_OnSavingChanges; + context1.SavedChanges += Context_OnSavedChanges; + context1.SaveChangesFailed += Context_OnSaveChangesFailed; + + _localView_OnPropertyChanging = false; + _localView_OnPropertyChanged = false; + _localView_OnCollectionChanged = false; + + await Dispose(context1, async); + + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + + Assert.Same(context1, context2); + + Assert.Empty(localView); + Assert.Empty(localView.ToBindingList()); + Assert.Empty(localView.ToObservableCollection()); + + var customer2 = new Customer { CustomerId = "C" }; + context2.Customers.Attach(customer2).State = EntityState.Modified; + context2.Customers.Attach(customer2).State = EntityState.Unchanged; + + Assert.False(_changeTracker_OnTracking); + Assert.False(_changeTracker_OnTracked); + Assert.False(_changeTracker_OnStateChanging); + Assert.False(_changeTracker_OnStateChanged); + + context2.SaveChanges(); + + Assert.False(_changeTracker_OnDetectingAllChanges); + Assert.False(_changeTracker_OnDetectedAllChanges); + Assert.False(_changeTracker_OnDetectingEntityChanges); + Assert.False(_changeTracker_OnDetectedEntityChanges); + Assert.False(_context_OnSavedChanges); + Assert.False(_context_OnSavingChanges); + Assert.False(_context_OnSaveChangesFailed); + + Assert.Same(set, context2!.Customers); + Assert.Same(localView, context2!.Customers.Local); + Assert.Equal(1, localView.Count); + Assert.Same(customer2, localView.ToBindingList().Single()); + Assert.Same(customer2, localView.ToObservableCollection().Single()); + Assert.False(_localView_OnPropertyChanging); + Assert.False(_localView_OnPropertyChanged); + Assert.False(_localView_OnCollectionChanged); + } - Assert.NotSame(context1, context2); + [ConditionalFact] + public void Change_tracker_can_be_cleared_without_resetting_context_config() + { + var context = new PooledContext( + new DbContextOptionsBuilder().UseJet( + JetNorthwindTestStoreFactory.NorthwindConnectionString).Options); + + context.ChangeTracker.AutoDetectChangesEnabled = true; + context.ChangeTracker.LazyLoadingEnabled = true; + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; + context.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded; + context.Database.AutoSavepointsEnabled = true; + context.ChangeTracker.Tracking += ChangeTracker_OnTracking; + context.ChangeTracker.Tracked += ChangeTracker_OnTracked; + context.ChangeTracker.StateChanging += ChangeTracker_OnStateChanging; + context.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; + context.ChangeTracker.DetectingAllChanges += ChangeTracker_OnDetectingAllChanges; + context.ChangeTracker.DetectedAllChanges += ChangeTracker_OnDetectedAllChanges; + context.ChangeTracker.DetectingEntityChanges += ChangeTracker_OnDetectingEntityChanges; + context.ChangeTracker.DetectedEntityChanges += ChangeTracker_OnDetectedEntityChanges; + context.SavingChanges += Context_OnSavingChanges; + context.SavedChanges += Context_OnSavedChanges; + context.SaveChangesFailed += Context_OnSaveChangesFailed; + + context.ChangeTracker.Clear(); + + Assert.True(context.ChangeTracker.AutoDetectChangesEnabled); + Assert.True(context.ChangeTracker.LazyLoadingEnabled); + Assert.Equal(QueryTrackingBehavior.NoTracking, context.ChangeTracker.QueryTrackingBehavior); + Assert.Equal(CascadeTiming.Immediate, context.ChangeTracker.CascadeDeleteTiming); + Assert.Equal(CascadeTiming.Immediate, context.ChangeTracker.DeleteOrphansTiming); + Assert.Equal(AutoTransactionBehavior.WhenNeeded, context.Database.AutoTransactionBehavior); + Assert.True(context.Database.AutoSavepointsEnabled); + + Assert.False(_changeTracker_OnTracking); + Assert.False(_changeTracker_OnTracked); + Assert.False(_changeTracker_OnStateChanging); + Assert.False(_changeTracker_OnStateChanged); + Assert.False(_changeTracker_OnDetectingAllChanges); + Assert.False(_changeTracker_OnDetectedAllChanges); + Assert.False(_changeTracker_OnDetectingEntityChanges); + Assert.False(_changeTracker_OnDetectedEntityChanges); + + var customer = new Customer { CustomerId = "C" }; + context.Customers.Attach(customer).State = EntityState.Modified; + context.Customers.Attach(customer).State = EntityState.Unchanged; + + Assert.True(_changeTracker_OnTracking); + Assert.True(_changeTracker_OnTracked); + Assert.True(_changeTracker_OnStateChanging); + Assert.True(_changeTracker_OnStateChanged); + Assert.False(_changeTracker_OnDetectingAllChanges); + Assert.False(_changeTracker_OnDetectedAllChanges); + Assert.False(_changeTracker_OnDetectingEntityChanges); + Assert.False(_changeTracker_OnDetectedEntityChanges); + + context.SaveChanges(); + + Assert.True(_changeTracker_OnDetectingAllChanges); + Assert.True(_changeTracker_OnDetectedAllChanges); + Assert.True(_changeTracker_OnDetectingEntityChanges); + Assert.True(_changeTracker_OnDetectedEntityChanges); + Assert.True(_context_OnSavedChanges); + Assert.True(_context_OnSavingChanges); + Assert.False(_context_OnSaveChangesFailed); + } - var id1 = context1.ContextId; - var id2 = context2.ContextId; + private void Context_OnSavingChanges(object sender, SavingChangesEventArgs e) + => _context_OnSavingChanges = true; - Assert.NotEqual(default, id1.InstanceId); - Assert.NotEqual(default, id2.InstanceId); + private bool _context_OnSavingChanges; - Assert.NotEqual(id1, id2); - Assert.Equal(0, id1.Lease); - Assert.Equal(0, id2.Lease); + private void Context_OnSavedChanges(object sender, SavedChangesEventArgs e) + => _context_OnSavedChanges = true; - serviceScope1.Dispose(); - serviceScope2.Dispose(); + private bool _context_OnSavedChanges; - var id1d = context1.ContextId; - var id2d = context2.ContextId; + private void Context_OnSaveChangesFailed(object sender, SaveChangesFailedEventArgs e) + => _context_OnSaveChangesFailed = true; - Assert.Equal(id1.InstanceId, id1d.InstanceId); - Assert.Equal(id2.InstanceId, id2d.InstanceId); - Assert.Equal(0, id1d.Lease); - Assert.Equal(0, id2d.Lease); + private bool _context_OnSaveChangesFailed; - var serviceScope3 = serviceProvider.CreateScope(); - var context3 = serviceScope3.ServiceProvider.GetService(); + private bool _localView_OnPropertyChanged; - var id1r = context3.ContextId; + private void LocalView_OnPropertyChanged(object sender, PropertyChangedEventArgs e) + => _localView_OnPropertyChanged = true; - Assert.NotSame(context1, context3); - Assert.NotEqual(default, id1r.InstanceId); - Assert.NotEqual(id1.InstanceId, id1r.InstanceId); - Assert.NotEqual(id1, id1r); - Assert.Equal(0, id1r.Lease); + private bool _localView_OnPropertyChanging; - var serviceScope4 = serviceProvider.CreateScope(); - var context4 = serviceScope4.ServiceProvider.GetService(); + private void LocalView_OnPropertyChanging(object sender, PropertyChangingEventArgs e) + => _localView_OnPropertyChanging = true; - var id2r = context4.ContextId; + private bool _localView_OnCollectionChanged; - Assert.NotSame(context2, context4); - Assert.NotEqual(default, id2r.InstanceId); - Assert.NotEqual(id2.InstanceId, id2r.InstanceId); - Assert.NotEqual(id2, id2r); - Assert.Equal(0, id2r.Lease); - } + private void LocalView_OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + => _localView_OnCollectionChanged = true; - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public void Contexts_are_pooled(bool useInterface) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + private bool _changeTracker_OnTracking; - var serviceScope1 = serviceProvider.CreateScope(); - var scopedProvider1 = serviceScope1.ServiceProvider; + private void ChangeTracker_OnTracking(object sender, EntityTrackingEventArgs e) + => _changeTracker_OnTracking = true; - var context1 = useInterface - ? scopedProvider1.GetService() - : scopedProvider1.GetService(); + private bool _changeTracker_OnTracked; - var secondContext1 = useInterface - ? scopedProvider1.GetService() - : scopedProvider1.GetService(); + private void ChangeTracker_OnTracked(object sender, EntityTrackedEventArgs e) + => _changeTracker_OnTracked = true; - var serviceScope2 = serviceProvider.CreateScope(); - var scopedProvider2 = serviceScope2.ServiceProvider; + private bool _changeTracker_OnStateChanging; - var context2 = useInterface - ? scopedProvider2.GetService() - : scopedProvider2.GetService(); + private void ChangeTracker_OnStateChanging(object sender, EntityStateChangingEventArgs e) + => _changeTracker_OnStateChanging = true; - var secondContext2 = useInterface - ? scopedProvider2.GetService() - : scopedProvider2.GetService(); + private bool _changeTracker_OnStateChanged; - Assert.NotSame(context1, context2); - Assert.NotSame(secondContext1, secondContext2); + private void ChangeTracker_OnStateChanged(object sender, EntityStateChangedEventArgs e) + => _changeTracker_OnStateChanged = true; - serviceScope1.Dispose(); - serviceScope2.Dispose(); + private bool _changeTracker_OnDetectingAllChanges; - var serviceScope3 = serviceProvider.CreateScope(); - var scopedProvider3 = serviceScope3.ServiceProvider; + private void ChangeTracker_OnDetectingAllChanges(object sender, DetectChangesEventArgs e) + => _changeTracker_OnDetectingAllChanges = true; - var context3 = useInterface - ? scopedProvider3.GetService() - : scopedProvider3.GetService(); + private bool _changeTracker_OnDetectedAllChanges; - var secondContext3 = useInterface - ? scopedProvider3.GetService() - : scopedProvider3.GetService(); + private void ChangeTracker_OnDetectedAllChanges(object sender, DetectedChangesEventArgs e) + => _changeTracker_OnDetectedAllChanges = true; - Assert.Same(context1, context3); - Assert.Same(secondContext1, secondContext3); + private bool _changeTracker_OnDetectingEntityChanges; - var serviceScope4 = serviceProvider.CreateScope(); - var scopedProvider4 = serviceScope4.ServiceProvider; + private void ChangeTracker_OnDetectingEntityChanges(object sender, DetectEntityChangesEventArgs e) + => _changeTracker_OnDetectingEntityChanges = true; - var context4 = useInterface - ? scopedProvider4.GetService() - : scopedProvider4.GetService(); + private bool _changeTracker_OnDetectedEntityChanges; - var secondContext4 = useInterface - ? scopedProvider4.GetService() - : scopedProvider4.GetService(); + private void ChangeTracker_OnDetectedEntityChanges(object sender, DetectedEntityChangesEventArgs e) + => _changeTracker_OnDetectedEntityChanges = true; - Assert.Same(context2, context4); - Assert.Same(secondContext2, secondContext4); - } + [ConditionalTheory] + [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(b => UseQueryTrackingBehavior(b, queryTrackingBehavior)); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Context_configuration_is_reset(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); - - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; - - var context1 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); - - Assert.Null(context1!.Database.GetCommandTimeout()); - - var set = context1.Customers; - var localView = set.Local; - localView.PropertyChanged += LocalView_OnPropertyChanged; - localView.PropertyChanging += LocalView_OnPropertyChanging; - localView.CollectionChanged += LocalView_OnCollectionChanged; - var customer1 = new Customer { CustomerId = "C" }; - context1.Customers.Attach(customer1); - Assert.Equal(1, localView.Count); - Assert.Same(customer1, localView.ToBindingList().Single()); - Assert.Same(customer1, localView.ToObservableCollection().Single()); - Assert.True(_localView_OnPropertyChanging); - Assert.True(_localView_OnPropertyChanged); - Assert.True(_localView_OnCollectionChanged); - - context1.ChangeTracker.AutoDetectChangesEnabled = true; - context1.ChangeTracker.LazyLoadingEnabled = true; - context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; - context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; - context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded; - context1.Database.AutoSavepointsEnabled = true; - context1.Database.SetCommandTimeout(1); - context1.ChangeTracker.Tracking += ChangeTracker_OnTracking; - context1.ChangeTracker.Tracked += ChangeTracker_OnTracked; - context1.ChangeTracker.StateChanging += ChangeTracker_OnStateChanging; - context1.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; - context1.ChangeTracker.DetectingAllChanges += ChangeTracker_OnDetectingAllChanges; - context1.ChangeTracker.DetectedAllChanges += ChangeTracker_OnDetectedAllChanges; - context1.ChangeTracker.DetectingEntityChanges += ChangeTracker_OnDetectingEntityChanges; - context1.ChangeTracker.DetectedEntityChanges += ChangeTracker_OnDetectedEntityChanges; - context1.SavingChanges += Context_OnSavingChanges; - context1.SavedChanges += Context_OnSavedChanges; - context1.SaveChangesFailed += Context_OnSaveChangesFailed; - - _localView_OnPropertyChanging = false; - _localView_OnPropertyChanged = false; - _localView_OnCollectionChanged = false; - - await Dispose(serviceScope, async); - - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; - - var context2 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); - - Assert.Same(context1, context2); - - Assert.False(context2!.ChangeTracker.AutoDetectChangesEnabled); - Assert.False(context2.ChangeTracker.LazyLoadingEnabled); - Assert.Equal(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); - Assert.False(context2.Database.AutoSavepointsEnabled); - Assert.Null(context1.Database.GetCommandTimeout()); - - Assert.Empty(localView); - Assert.Empty(localView.ToBindingList()); - Assert.Empty(localView.ToObservableCollection()); - - var customer2 = new Customer { CustomerId = "C" }; - context2.Customers.Attach(customer2).State = EntityState.Modified; - context2.Customers.Attach(customer2).State = EntityState.Unchanged; - - Assert.False(_changeTracker_OnTracking); - Assert.False(_changeTracker_OnTracked); - Assert.False(_changeTracker_OnStateChanging); - Assert.False(_changeTracker_OnStateChanged); - - context2.SaveChanges(); - - Assert.False(_changeTracker_OnDetectingAllChanges); - Assert.False(_changeTracker_OnDetectedAllChanges); - Assert.False(_changeTracker_OnDetectingEntityChanges); - Assert.False(_changeTracker_OnDetectedEntityChanges); - Assert.False(_context_OnSavedChanges); - Assert.False(_context_OnSavingChanges); - Assert.False(_context_OnSaveChangesFailed); - - Assert.Same(set, context2!.Customers); - Assert.Same(localView, context2!.Customers.Local); - Assert.Equal(1, localView.Count); - Assert.Same(customer2, localView.ToBindingList().Single()); - Assert.Same(customer2, localView.ToObservableCollection().Single()); - Assert.False(_localView_OnPropertyChanging); - Assert.False(_localView_OnPropertyChanged); - Assert.False(_localView_OnCollectionChanged); - } + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] - public async Task Uninitialized_context_configuration_is_reset_properly(bool async) - { - var serviceProvider = BuildServiceProvider(); - - var serviceScope = serviceProvider.CreateScope(); - var ctx = serviceScope.ServiceProvider.GetRequiredService(); - await Dispose(ctx, async); - await Dispose(serviceScope, async); - - serviceScope = serviceProvider.CreateScope(); - var ctx2 = serviceScope.ServiceProvider.GetRequiredService(); - Assert.Same(ctx, ctx2); - ctx2.Blogs.Add(new SecondContext.Blog()); - await Dispose(ctx2, async); - await Dispose(serviceScope, async); - - serviceScope = serviceProvider.CreateScope(); - var ctx3 = serviceScope.ServiceProvider.GetRequiredService(); - Assert.Same(ctx, ctx3); - Assert.Empty(ctx3.ChangeTracker.Entries()); - await Dispose(ctx2, async); - await Dispose(serviceScope, async); - } + var context1 = scopedProvider.GetService(); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Context_configuration_is_reset_with_factory(bool async, bool withDependencyInjection) - { - var factory = BuildFactory(withDependencyInjection); - - var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - var set = context1.Customers; - - var localView = set.Local; - localView.PropertyChanged += LocalView_OnPropertyChanged; - localView.PropertyChanging += LocalView_OnPropertyChanging; - localView.CollectionChanged += LocalView_OnCollectionChanged; - var customer1 = new Customer { CustomerId = "C" }; - context1.Customers.Attach(customer1); - Assert.Equal(1, localView.Count); - Assert.Same(customer1, localView.ToBindingList().Single()); - Assert.Same(customer1, localView.ToObservableCollection().Single()); - Assert.True(_localView_OnPropertyChanging); - Assert.True(_localView_OnPropertyChanged); - Assert.True(_localView_OnCollectionChanged); - - context1.ChangeTracker.AutoDetectChangesEnabled = true; - context1.ChangeTracker.LazyLoadingEnabled = true; - context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; - context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; - context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded; - context1.Database.AutoSavepointsEnabled = true; - context1.ChangeTracker.Tracking += ChangeTracker_OnTracking; - context1.ChangeTracker.Tracked += ChangeTracker_OnTracked; - context1.ChangeTracker.StateChanging += ChangeTracker_OnStateChanging; - context1.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; - context1.ChangeTracker.DetectingAllChanges += ChangeTracker_OnDetectingAllChanges; - context1.ChangeTracker.DetectedAllChanges += ChangeTracker_OnDetectedAllChanges; - context1.ChangeTracker.DetectingEntityChanges += ChangeTracker_OnDetectingEntityChanges; - context1.ChangeTracker.DetectedEntityChanges += ChangeTracker_OnDetectedEntityChanges; - context1.SavingChanges += Context_OnSavingChanges; - context1.SavedChanges += Context_OnSavedChanges; - context1.SaveChangesFailed += Context_OnSaveChangesFailed; - - _localView_OnPropertyChanging = false; - _localView_OnPropertyChanged = false; - _localView_OnCollectionChanged = false; + context1!.ChangeTracker.AutoDetectChangesEnabled = false; + context1.ChangeTracker.LazyLoadingEnabled = false; + context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; + context1.Database.AutoSavepointsEnabled = false; + context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; - await Dispose(context1, async); + await Dispose(serviceScope, async); - var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - - Assert.Same(context1, context2); - - Assert.Empty(localView); - Assert.Empty(localView.ToBindingList()); - Assert.Empty(localView.ToObservableCollection()); - - var customer2 = new Customer { CustomerId = "C" }; - context2.Customers.Attach(customer2).State = EntityState.Modified; - context2.Customers.Attach(customer2).State = EntityState.Unchanged; - - Assert.False(_changeTracker_OnTracking); - Assert.False(_changeTracker_OnTracked); - Assert.False(_changeTracker_OnStateChanging); - Assert.False(_changeTracker_OnStateChanged); - - context2.SaveChanges(); - - Assert.False(_changeTracker_OnDetectingAllChanges); - Assert.False(_changeTracker_OnDetectedAllChanges); - Assert.False(_changeTracker_OnDetectingEntityChanges); - Assert.False(_changeTracker_OnDetectedEntityChanges); - Assert.False(_context_OnSavedChanges); - Assert.False(_context_OnSavingChanges); - Assert.False(_context_OnSaveChangesFailed); - - Assert.Same(set, context2!.Customers); - Assert.Same(localView, context2!.Customers.Local); - Assert.Equal(1, localView.Count); - Assert.Same(customer2, localView.ToBindingList().Single()); - Assert.Same(customer2, localView.ToObservableCollection().Single()); - Assert.False(_localView_OnPropertyChanging); - Assert.False(_localView_OnPropertyChanged); - Assert.False(_localView_OnCollectionChanged); - } + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; - [ConditionalFact] - public void Change_tracker_can_be_cleared_without_resetting_context_config() - { - var context = new PooledContext( - new DbContextOptionsBuilder().UseJet( - JetNorthwindTestStoreFactory.NorthwindConnectionString).Options); - - context.ChangeTracker.AutoDetectChangesEnabled = true; - context.ChangeTracker.LazyLoadingEnabled = true; - context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; - context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; - context.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded; - context.Database.AutoSavepointsEnabled = true; - context.ChangeTracker.Tracking += ChangeTracker_OnTracking; - context.ChangeTracker.Tracked += ChangeTracker_OnTracked; - context.ChangeTracker.StateChanging += ChangeTracker_OnStateChanging; - context.ChangeTracker.StateChanged += ChangeTracker_OnStateChanged; - context.ChangeTracker.DetectingAllChanges += ChangeTracker_OnDetectingAllChanges; - context.ChangeTracker.DetectedAllChanges += ChangeTracker_OnDetectedAllChanges; - context.ChangeTracker.DetectingEntityChanges += ChangeTracker_OnDetectingEntityChanges; - context.ChangeTracker.DetectedEntityChanges += ChangeTracker_OnDetectedEntityChanges; - context.SavingChanges += Context_OnSavingChanges; - context.SavedChanges += Context_OnSavedChanges; - context.SaveChangesFailed += Context_OnSaveChangesFailed; - - context.ChangeTracker.Clear(); - - Assert.True(context.ChangeTracker.AutoDetectChangesEnabled); - Assert.True(context.ChangeTracker.LazyLoadingEnabled); - Assert.Equal(QueryTrackingBehavior.NoTracking, context.ChangeTracker.QueryTrackingBehavior); - Assert.Equal(CascadeTiming.Immediate, context.ChangeTracker.CascadeDeleteTiming); - Assert.Equal(CascadeTiming.Immediate, context.ChangeTracker.DeleteOrphansTiming); - Assert.Equal(AutoTransactionBehavior.WhenNeeded, context.Database.AutoTransactionBehavior); - Assert.True(context.Database.AutoSavepointsEnabled); - - Assert.False(_changeTracker_OnTracking); - Assert.False(_changeTracker_OnTracked); - Assert.False(_changeTracker_OnStateChanging); - Assert.False(_changeTracker_OnStateChanged); - Assert.False(_changeTracker_OnDetectingAllChanges); - Assert.False(_changeTracker_OnDetectedAllChanges); - Assert.False(_changeTracker_OnDetectingEntityChanges); - Assert.False(_changeTracker_OnDetectedEntityChanges); - - var customer = new Customer { CustomerId = "C" }; - context.Customers.Attach(customer).State = EntityState.Modified; - context.Customers.Attach(customer).State = EntityState.Unchanged; - - Assert.True(_changeTracker_OnTracking); - Assert.True(_changeTracker_OnTracked); - Assert.True(_changeTracker_OnStateChanging); - Assert.True(_changeTracker_OnStateChanged); - Assert.False(_changeTracker_OnDetectingAllChanges); - Assert.False(_changeTracker_OnDetectedAllChanges); - Assert.False(_changeTracker_OnDetectingEntityChanges); - Assert.False(_changeTracker_OnDetectedEntityChanges); - - context.SaveChanges(); - - Assert.True(_changeTracker_OnDetectingAllChanges); - Assert.True(_changeTracker_OnDetectedAllChanges); - Assert.True(_changeTracker_OnDetectingEntityChanges); - Assert.True(_changeTracker_OnDetectedEntityChanges); - Assert.True(_context_OnSavedChanges); - Assert.True(_context_OnSavingChanges); - Assert.False(_context_OnSaveChangesFailed); - } + var context2 = scopedProvider.GetService(); - private void Context_OnSavingChanges(object sender, SavingChangesEventArgs e) - => _context_OnSavingChanges = true; + Assert.Same(context1, context2); - private bool _context_OnSavingChanges; + Assert.True(context2!.ChangeTracker.AutoDetectChangesEnabled); + Assert.True(context2.ChangeTracker.LazyLoadingEnabled); + 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); + Assert.True(context2.Database.AutoSavepointsEnabled); + } - private void Context_OnSavedChanges(object sender, SavedChangesEventArgs e) - => _context_OnSavedChanges = true; + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Default_Context_configuration_is_reset_with_factory(bool async, bool withDependencyInjection) + { + var factory = BuildFactory(withDependencyInjection); - private bool _context_OnSavedChanges; + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - private void Context_OnSaveChangesFailed(object sender, SaveChangesFailedEventArgs e) - => _context_OnSaveChangesFailed = true; + context1.ChangeTracker.AutoDetectChangesEnabled = false; + context1.ChangeTracker.LazyLoadingEnabled = false; + context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; + context1.Database.AutoSavepointsEnabled = false; + context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; - private bool _context_OnSaveChangesFailed; + await Dispose(context1, async); - private bool _localView_OnPropertyChanged; + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - private void LocalView_OnPropertyChanged(object sender, PropertyChangedEventArgs e) - => _localView_OnPropertyChanged = true; + Assert.Same(context1, context2); - private bool _localView_OnPropertyChanging; + Assert.True(context2.ChangeTracker.AutoDetectChangesEnabled); + Assert.True(context2.ChangeTracker.LazyLoadingEnabled); + Assert.Equal(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); + Assert.True(context2.Database.AutoSavepointsEnabled); + } - private void LocalView_OnPropertyChanging(object sender, PropertyChangingEventArgs e) - => _localView_OnPropertyChanging = true; + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task State_manager_is_reset(bool useInterface, bool async) + { + var weakRef = await Scoper( + async () => + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - private bool _localView_OnCollectionChanged; + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - private void LocalView_OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - => _localView_OnCollectionChanged = true; + var context1 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - private bool _changeTracker_OnTracking; + var entity = context1.Customers.First(c => c.CustomerId == "ALFKI"); - private void ChangeTracker_OnTracking(object sender, EntityTrackingEventArgs e) - => _changeTracker_OnTracking = true; + Assert.Single(context1.ChangeTracker.Entries()); - private bool _changeTracker_OnTracked; + await Dispose(serviceScope, async); - private void ChangeTracker_OnTracked(object sender, EntityTrackedEventArgs e) - => _changeTracker_OnTracked = true; + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; - private bool _changeTracker_OnStateChanging; + var context2 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - private void ChangeTracker_OnStateChanging(object sender, EntityStateChangingEventArgs e) - => _changeTracker_OnStateChanging = true; + Assert.Same(context1, context2); + Assert.Empty(context2.ChangeTracker.Entries()); - private bool _changeTracker_OnStateChanged; + return new WeakReference(entity); + }); - private void ChangeTracker_OnStateChanged(object sender, EntityStateChangedEventArgs e) - => _changeTracker_OnStateChanged = true; + GC.Collect(); - private bool _changeTracker_OnDetectingAllChanges; + Assert.False(weakRef.IsAlive); + } - private void ChangeTracker_OnDetectingAllChanges(object sender, DetectChangesEventArgs e) - => _changeTracker_OnDetectingAllChanges = true; + private static async Task Scoper(Func> getter) + => await getter(); - private bool _changeTracker_OnDetectedAllChanges; + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task State_manager_is_reset_with_factory(bool async, bool withDependencyInjection) + { + var weakRef = await Scoper( + async () => + { + var factory = BuildFactory(withDependencyInjection); - private void ChangeTracker_OnDetectedAllChanges(object sender, DetectedChangesEventArgs e) - => _changeTracker_OnDetectedAllChanges = true; + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - private bool _changeTracker_OnDetectingEntityChanges; + var entity = context1.Customers.First(c => c.CustomerId == "ALFKI"); - private void ChangeTracker_OnDetectingEntityChanges(object sender, DetectEntityChangesEventArgs e) - => _changeTracker_OnDetectingEntityChanges = true; + Assert.Single(context1.ChangeTracker.Entries()); - private bool _changeTracker_OnDetectedEntityChanges; + await Dispose(context1, async); - private void ChangeTracker_OnDetectedEntityChanges(object sender, DetectedEntityChangesEventArgs e) - => _changeTracker_OnDetectedEntityChanges = true; + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] - public async Task Default_Context_configuration_is_reset(bool async) - { - var serviceProvider = BuildServiceProvider(); + Assert.Same(context1, context2); + Assert.Empty(context2.ChangeTracker.Entries()); - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; + return new WeakReference(entity); + }); - var context1 = scopedProvider.GetService(); + GC.Collect(); - context1!.ChangeTracker.AutoDetectChangesEnabled = false; - context1.ChangeTracker.LazyLoadingEnabled = false; - context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; - context1.Database.AutoSavepointsEnabled = false; - context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; - context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; + Assert.False(weakRef.IsAlive); + } - await Dispose(serviceScope, async); + [ConditionalTheory] // Issue #25486 + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + public async Task Service_properties_are_disposed(bool useInterface, bool async, bool load) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - var context2 = scopedProvider.GetService(); + var context1 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - Assert.Same(context1, context2); + context1.ChangeTracker.LazyLoadingEnabled = true; - Assert.True(context2!.ChangeTracker.AutoDetectChangesEnabled); - Assert.True(context2.ChangeTracker.LazyLoadingEnabled); - Assert.Equal(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); - Assert.True(context2.Database.AutoSavepointsEnabled); + var entity = context1.Customers.First(c => c.CustomerId == "ALFKI"); + var orderLoader = entity.LazyLoader; + if (load) + { + orderLoader.Load(entity, nameof(Customer.Orders)); + Assert.True(orderLoader.IsLoaded(entity, nameof(Customer.Orders))); } - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Default_Context_configuration_is_reset_with_factory(bool async, bool withDependencyInjection) - { - var factory = BuildFactory(withDependencyInjection); + Assert.Equal(load ? 7 : 1, context1.ChangeTracker.Entries().Count()); - var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + await Dispose(serviceScope, async); - context1.ChangeTracker.AutoDetectChangesEnabled = false; - context1.ChangeTracker.LazyLoadingEnabled = false; - context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - context1.Database.AutoTransactionBehavior = AutoTransactionBehavior.Never; - context1.Database.AutoSavepointsEnabled = false; - context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; - context1.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Immediate; + if (load) + { + orderLoader.Load(entity, nameof(Customer.Orders)); + Assert.True(orderLoader.IsLoaded(entity, nameof(Customer.Orders))); + orderLoader.SetLoaded(entity, nameof(Customer.Orders), loaded: false); + } - await Dispose(context1, async); + AssertDisposed(() => orderLoader.Load(entity, nameof(Customer.Orders)), "Customer", "Orders"); + } - var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + [ConditionalTheory] // Issue #25486 + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + public async Task Service_properties_are_disposed_with_factory(bool async, bool withDependencyInjection, bool load) + { + var factory = BuildFactory(withDependencyInjection); - Assert.Same(context1, context2); + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - Assert.True(context2.ChangeTracker.AutoDetectChangesEnabled); - Assert.True(context2.ChangeTracker.LazyLoadingEnabled); - Assert.Equal(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); - Assert.True(context2.Database.AutoSavepointsEnabled); - } + context1.ChangeTracker.LazyLoadingEnabled = true; - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task State_manager_is_reset(bool useInterface, bool async) + var entity = context1.Customers.First(c => c.CustomerId == "ALFKI"); + var orderLoader = entity.LazyLoader; + if (load) { - var weakRef = await Scoper( - async () => - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); - - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; - - var context1 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + orderLoader.Load(entity, nameof(Customer.Orders)); + Assert.True(orderLoader.IsLoaded(entity, nameof(Customer.Orders))); + } - var entity = context1.Customers.First(c => c.CustomerId == "ALFKI"); + Assert.Equal(load ? 7 : 1, context1.ChangeTracker.Entries().Count()); - Assert.Single(context1.ChangeTracker.Entries()); + await Dispose(context1, async); - await Dispose(serviceScope, async); + if (load) + { + orderLoader.Load(entity, nameof(Customer.Orders)); + Assert.True(orderLoader.IsLoaded(entity, nameof(Customer.Orders))); + orderLoader.SetLoaded(entity, nameof(Customer.Orders), loaded: false); + } - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; + AssertDisposed(() => orderLoader.Load(entity, nameof(Customer.Orders)), "Customer", "Orders"); + } - var context2 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + private static void AssertDisposed(Action testCode, string entityTypeName, string navigationName) + => Assert.Equal( + CoreStrings.WarningAsErrorTemplate( + CoreEventId.LazyLoadOnDisposedContextWarning.ToString(), + CoreResources.LogLazyLoadOnDisposedContext(new TestLogger()) + .GenerateMessage(entityTypeName, navigationName), + "CoreEventId.LazyLoadOnDisposedContextWarning"), + Assert.Throws( + testCode).Message); + + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Pool_disposes_context_when_context_not_pooled(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - Assert.Same(context1, context2); - Assert.Empty(context2.ChangeTracker.Entries()); + var serviceScope1 = serviceProvider.CreateScope(); + var scopedProvider1 = serviceScope1.ServiceProvider; - return new WeakReference(entity); - }); + var context1 = useInterface + ? (PooledContext)scopedProvider1.GetService() + : scopedProvider1.GetService(); - GC.Collect(); + var serviceScope2 = serviceProvider.CreateScope(); + var scopedProvider2 = serviceScope2.ServiceProvider; - Assert.False(weakRef.IsAlive); - } + var context2 = useInterface + ? (PooledContext)scopedProvider2.GetService() + : scopedProvider2.GetService(); - private static async Task Scoper(Func> getter) - => await getter(); + await Dispose(serviceScope1, async); + await Dispose(serviceScope2, async); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task State_manager_is_reset_with_factory(bool async, bool withDependencyInjection) - { - var weakRef = await Scoper( - async () => - { - var factory = BuildFactory(withDependencyInjection); + Assert.Throws(() => context1.Customers.ToList()); + Assert.Throws(() => context2.Customers.ToList()); + } - var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Pool_disposes_contexts_when_disposed(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - var entity = context1.Customers.First(c => c.CustomerId == "ALFKI"); + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - Assert.Single(context1.ChangeTracker.Entries()); + var context = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - await Dispose(context1, async); + await Dispose(serviceScope, async); - var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + await Dispose((IDisposable)serviceProvider, async); - Assert.Same(context1, context2); - Assert.Empty(context2.ChangeTracker.Entries()); + Assert.Throws(() => context.Customers.ToList()); + } - return new WeakReference(entity); - }); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Object_in_pool_is_disposed(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - GC.Collect(); + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - Assert.False(weakRef.IsAlive); - } + var context = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Pool_disposes_context_when_context_not_pooled(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + await Dispose(serviceScope, async); - var serviceScope1 = serviceProvider.CreateScope(); - var scopedProvider1 = serviceScope1.ServiceProvider; + Assert.Throws(() => context!.Customers.ToList()); + } - var context1 = useInterface - ? (PooledContext)scopedProvider1.GetService() - : scopedProvider1.GetService(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Double_dispose_does_not_enter_pool_twice(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - var serviceScope2 = serviceProvider.CreateScope(); - var scopedProvider2 = serviceScope2.ServiceProvider; + var scope = serviceProvider.CreateScope(); + var lease = scope.ServiceProvider.GetRequiredService>(); + var context = lease.Context; - var context2 = useInterface - ? (PooledContext)scopedProvider2.GetService() - : scopedProvider2.GetService(); + await Dispose(scope, async); - await Dispose(serviceScope1, async); - await Dispose(serviceScope2, async); + await Dispose(scope, async); - Assert.Throws(() => context1.Customers.ToList()); - Assert.Throws(() => context2.Customers.ToList()); - } + using var scope1 = serviceProvider.CreateScope(); + var lease1 = scope1.ServiceProvider.GetRequiredService>(); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Pool_disposes_contexts_when_disposed(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + using var scope2 = serviceProvider.CreateScope(); + var lease2 = scope2.ServiceProvider.GetRequiredService>(); - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; + Assert.Same(context, lease1.Context); + Assert.NotSame(lease1.Context, lease2.Context); + } - var context = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Double_dispose_with_standalone_lease_does_not_enter_pool_twice(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - await Dispose(serviceScope, async); + var pool = serviceProvider.GetRequiredService>(); + var lease = new DbContextLease(pool, standalone: true); + var context = (PooledContext)lease.Context; + ((IDbContextPoolable)context).SetLease(lease); - await Dispose((IDisposable)serviceProvider, async); + await Dispose(context, async); - Assert.Throws(() => context.Customers.ToList()); - } + await Dispose(context, async); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Object_in_pool_is_disposed(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + using var context1 = new DbContextLease(pool, standalone: true).Context; + using var context2 = new DbContextLease(pool, standalone: true).Context; - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; + Assert.Same(context, context1); + Assert.NotSame(context1, context2); + } - var context = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Can_double_dispose_with_factory(bool async, bool withDependencyInjection) + { + var factory = BuildFactory(withDependencyInjection); - await Dispose(serviceScope, async); + var context = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - Assert.Throws(() => context!.Customers.ToList()); - } + context.Customers.Load(); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Double_dispose_does_not_enter_pool_twice(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + await Dispose(context, async); - var scope = serviceProvider.CreateScope(); - var lease = scope.ServiceProvider.GetRequiredService>(); - var context = lease.Context; + Assert.Throws(() => context.Customers.ToList()); - await Dispose(scope, async); + await Dispose(context, async); - await Dispose(scope, async); + Assert.Throws(() => context.Customers.ToList()); + } - using var scope1 = serviceProvider.CreateScope(); - var lease1 = scope1.ServiceProvider.GetRequiredService>(); + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Provider_services_are_reset(bool useInterface, bool async) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(o => o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)); - using var scope2 = serviceProvider.CreateScope(); - var lease2 = scope2.ServiceProvider.GetRequiredService>(); + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - Assert.Same(context, lease1.Context); - Assert.NotSame(lease1.Context, lease2.Context); - } + var context1 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Double_dispose_with_standalone_lease_does_not_enter_pool_twice(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + context1!.Database.BeginTransaction(); - var pool = serviceProvider.GetRequiredService>(); - var lease = new DbContextLease(pool, standalone: true); - var context = (PooledContext)lease.Context; - ((IDbContextPoolable)context).SetLease(lease); + Assert.NotNull(context1.Database.CurrentTransaction); - await Dispose(context, async); + await Dispose(serviceScope, async); - await Dispose(context, async); + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; - using var context1 = new DbContextLease(pool, standalone: true).Context; - using var context2 = new DbContextLease(pool, standalone: true).Context; + var context2 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - Assert.Same(context, context1); - Assert.NotSame(context1, context2); - } + Assert.Same(context1, context2); + Assert.Null(context2!.Database.CurrentTransaction); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Can_double_dispose_with_factory(bool async, bool withDependencyInjection) - { - var factory = BuildFactory(withDependencyInjection); + context2.Database.BeginTransaction(); - var context = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + Assert.NotNull(context2.Database.CurrentTransaction); - context.Customers.Load(); + await Dispose(serviceScope, async); - await Dispose(context, async); + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; - Assert.Throws(() => context.Customers.ToList()); + var context3 = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - await Dispose(context, async); + Assert.Same(context2, context3); + Assert.Null(context3!.Database.CurrentTransaction); + } - Assert.Throws(() => context.Customers.ToList()); - } + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Provider_services_are_reset_with_factory(bool async, bool withDependencyInjection) + { + var factory = BuildFactory(withDependencyInjection); - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Provider_services_are_reset(bool useInterface, bool async) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; + context1.Database.BeginTransaction(); - var context1 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + Assert.NotNull(context1.Database.CurrentTransaction); - context1!.Database.BeginTransaction(); + await Dispose(context1, async); - Assert.NotNull(context1.Database.CurrentTransaction); + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - await Dispose(serviceScope, async); + Assert.Same(context1, context2); + Assert.Null(context2.Database.CurrentTransaction); - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; + context2.Database.BeginTransaction(); - var context2 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + Assert.NotNull(context2.Database.CurrentTransaction); - Assert.Same(context1, context2); - Assert.Null(context2!.Database.CurrentTransaction); + await Dispose(context2, async); - context2.Database.BeginTransaction(); + var context3 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - Assert.NotNull(context2.Database.CurrentTransaction); + Assert.Same(context2, context3); + Assert.Null(context3.Database.CurrentTransaction); + } - await Dispose(serviceScope, async); + [ConditionalTheory] // Issue #27308. + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Handle_open_connection_when_returning_to_pool_for_owned_connection(bool async, bool openWithEf) + { + var serviceProvider = new ServiceCollection() + .AddDbContextPool( + ob => ob.UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) + .EnableServiceProviderCaching(false)) + .BuildServiceProvider(validateScopes: true); - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - var context3 = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + var context1 = scopedProvider.GetRequiredService(); + var connection1 = context1.Database.GetDbConnection(); - Assert.Same(context2, context3); - Assert.Null(context3!.Database.CurrentTransaction); + if (async) + { + if (openWithEf) + { + await context1.Database.OpenConnectionAsync(); + } + else + { + await connection1.OpenAsync(); + } } - - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Provider_services_are_reset_with_factory(bool async, bool withDependencyInjection) + else { - var factory = BuildFactory(withDependencyInjection); - - var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + if (openWithEf) + { + context1.Database.OpenConnection(); + } + else + { + connection1.Open(); + } + } - context1.Database.BeginTransaction(); + Assert.Equal(ConnectionState.Open, connection1.State); - Assert.NotNull(context1.Database.CurrentTransaction); + await Dispose(serviceScope, async); - await Dispose(context1, async); + Assert.Equal(ConnectionState.Closed, connection1.State); - var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; - Assert.Same(context1, context2); - Assert.Null(context2.Database.CurrentTransaction); + var context2 = scopedProvider.GetRequiredService(); + Assert.Same(context1, context2); - context2.Database.BeginTransaction(); + var connection2 = context2.Database.GetDbConnection(); + Assert.Same(connection1, connection2); - Assert.NotNull(context2.Database.CurrentTransaction); + Assert.Equal(ConnectionState.Closed, connection1.State); - await Dispose(context2, async); + await Dispose(serviceScope, async); + } - var context3 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + [ConditionalTheory] // Issue #27308. + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + public async Task Handle_open_connection_when_returning_to_pool_for_external_connection(bool async, bool startsOpen, bool openWithEf) + { + using var connection = new JetConnection(JetNorthwindTestStoreFactory.NorthwindConnectionString); - Assert.Same(context2, context3); - Assert.Null(context3.Database.CurrentTransaction); + if (startsOpen) + { + if (async) + { + await connection.OpenAsync(); + } + else + { + connection.Open(); + } } - [ConditionalTheory] // Issue #27308. - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Handle_open_connection_when_returning_to_pool_for_owned_connection(bool async, bool openWithEf) - { - var serviceProvider = new ServiceCollection() - .AddDbContextPool( - ob => ob.UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) - .EnableServiceProviderCaching(false)) - .BuildServiceProvider(validateScopes: true); + var serviceProvider = new ServiceCollection() + .AddDbContextPool( + ob => ob.UseJet(connection) + .EnableServiceProviderCaching(false)) + .BuildServiceProvider(validateScopes: true); - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; + var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - var context1 = scopedProvider.GetRequiredService(); - var connection1 = context1.Database.GetDbConnection(); + var context1 = scopedProvider.GetRequiredService(); + Assert.Same(connection, context1.Database.GetDbConnection()); + if (!startsOpen) + { if (async) { if (openWithEf) @@ -1484,7 +1825,7 @@ namespace EntityFrameworkCore.Jet.FunctionalTests } else { - await connection1.OpenAsync(); + await connection.OpenAsync(); } } else @@ -1495,437 +1836,363 @@ namespace EntityFrameworkCore.Jet.FunctionalTests } else { - connection1.Open(); + connection.Open(); } } + } - Assert.Equal(System.Data.ConnectionState.Open, connection1.State); + Assert.Equal(ConnectionState.Open, connection.State); - await Dispose(serviceScope, async); + await Dispose(serviceScope, async); - Assert.Equal(System.Data.ConnectionState.Closed, connection1.State); + Assert.Equal(ConnectionState.Open, connection.State); - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; + serviceScope = serviceProvider.CreateScope(); + scopedProvider = serviceScope.ServiceProvider; - var context2 = scopedProvider.GetRequiredService(); - Assert.Same(context1, context2); + var context2 = scopedProvider.GetRequiredService(); + Assert.Same(context1, context2); - var connection2 = context2.Database.GetDbConnection(); - Assert.Same(connection1, connection2); + Assert.Same(connection, context2.Database.GetDbConnection()); - Assert.Equal(System.Data.ConnectionState.Closed, connection1.State); + Assert.Equal(ConnectionState.Open, connection.State); - await Dispose(serviceScope, async); - } + await Dispose(serviceScope, async); + } - [ConditionalTheory] // Issue #27308. - [InlineData(false, false, false)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(true, true, false)] - [InlineData(false, false, true)] - [InlineData(true, false, true)] - public async Task Handle_open_connection_when_returning_to_pool_for_external_connection(bool async, bool startsOpen, bool openWithEf) - { - using var connection = new JetConnection(JetNorthwindTestStoreFactory.NorthwindConnectionString); + [ConditionalTheory] // Issue #27308. + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + public async Task Handle_open_connection_when_returning_to_pool_for_owned_connection_with_factory( + bool async, + bool openWithEf, + bool withDependencyInjection) + { + var options = new DbContextOptionsBuilder() + .UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) + .EnableServiceProviderCaching(false) + .Options; + + var factory = + withDependencyInjection + ? new ServiceCollection() + .AddPooledDbContextFactory( + ob => ob.UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) + .EnableServiceProviderCaching(false)) + .BuildServiceProvider(validateScopes: true) + .GetRequiredService>() + : new PooledDbContextFactory(options); + + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + var connection1 = context1.Database.GetDbConnection(); - if (startsOpen) + if (async) + { + if (openWithEf) { - if (async) - { - await connection.OpenAsync(); - } - else - { - connection.Open(); - } + await context1.Database.OpenConnectionAsync(); } - - var serviceProvider = new ServiceCollection() - .AddDbContextPool( - ob => ob.UseJet(connection) - .EnableServiceProviderCaching(false)) - .BuildServiceProvider(validateScopes: true); - - var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; - - var context1 = scopedProvider.GetRequiredService(); - Assert.Same(connection, context1.Database.GetDbConnection()); - - if (!startsOpen) + else { - if (async) - { - if (openWithEf) - { - await context1.Database.OpenConnectionAsync(); - } - else - { - await connection.OpenAsync(); - } - } - else - { - if (openWithEf) - { - context1.Database.OpenConnection(); - } - else - { - connection.Open(); - } - } + await connection1.OpenAsync(); + } + } + else + { + if (openWithEf) + { + context1.Database.OpenConnection(); + } + else + { + connection1.Open(); } + } - Assert.Equal(System.Data.ConnectionState.Open, connection.State); + Assert.Equal(ConnectionState.Open, connection1.State); - await Dispose(serviceScope, async); + await Dispose(context1, async); - Assert.Equal(System.Data.ConnectionState.Open, connection.State); + Assert.Equal(ConnectionState.Closed, connection1.State); - serviceScope = serviceProvider.CreateScope(); - scopedProvider = serviceScope.ServiceProvider; + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + Assert.Same(context1, context2); - var context2 = scopedProvider.GetRequiredService(); - Assert.Same(context1, context2); + var connection2 = context2.Database.GetDbConnection(); + Assert.Same(connection1, connection2); - Assert.Same(connection, context2.Database.GetDbConnection()); + Assert.Equal(ConnectionState.Closed, connection1.State); - Assert.Equal(System.Data.ConnectionState.Open, connection.State); + await Dispose(context2, async); + } - await Dispose(serviceScope, async); - } + [ConditionalTheory] // Issue #27308. + [InlineData(false, false, false, false)] + [InlineData(true, false, false, false)] + [InlineData(false, true, false, false)] + [InlineData(true, true, false, false)] + [InlineData(false, false, true, false)] + [InlineData(true, false, true, false)] + [InlineData(false, false, false, true)] + [InlineData(true, false, false, true)] + [InlineData(false, true, false, true)] + [InlineData(true, true, false, true)] + [InlineData(false, false, true, true)] + [InlineData(true, false, true, true)] + public async Task Handle_open_connection_when_returning_to_pool_for_external_connection_with_factory( + bool async, + bool startsOpen, + bool openWithEf, + bool withDependencyInjection) + { + using var connection = new JetConnection(JetNorthwindTestStoreFactory.NorthwindConnectionString); - [ConditionalTheory] // Issue #27308. - [InlineData(false, false, false)] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(true, true, false)] - [InlineData(false, false, true)] - [InlineData(true, false, true)] - [InlineData(false, true, true)] - [InlineData(true, true, true)] - public async Task Handle_open_connection_when_returning_to_pool_for_owned_connection_with_factory( - bool async, - bool openWithEf, - bool withDependencyInjection) + if (startsOpen) { - var options = new DbContextOptionsBuilder() - .UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) - .EnableServiceProviderCaching(false) - .Options; - - var factory = - withDependencyInjection - ? new ServiceCollection() - .AddPooledDbContextFactory( - ob => ob.UseJet(JetNorthwindTestStoreFactory.NorthwindConnectionString) - .EnableServiceProviderCaching(false)) - .BuildServiceProvider(validateScopes: true) - .GetRequiredService>() - : new PooledDbContextFactory(options); - - var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - var connection1 = context1.Database.GetDbConnection(); - if (async) { - if (openWithEf) - { - await context1.Database.OpenConnectionAsync(); - } - else - { - await connection1.OpenAsync(); - } + await connection.OpenAsync(); } else { - if (openWithEf) - { - context1.Database.OpenConnection(); - } - else - { - connection1.Open(); - } + connection.Open(); } + } - Assert.Equal(System.Data.ConnectionState.Open, connection1.State); - - await Dispose(context1, async); - - Assert.Equal(System.Data.ConnectionState.Closed, connection1.State); - - var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - Assert.Same(context1, context2); - - var connection2 = context2.Database.GetDbConnection(); - Assert.Same(connection1, connection2); + var options = new DbContextOptionsBuilder() + .UseJet(connection) + .EnableServiceProviderCaching(false) + .Options; - Assert.Equal(System.Data.ConnectionState.Closed, connection1.State); + var factory = + withDependencyInjection + ? new ServiceCollection() + .AddPooledDbContextFactory( + ob => ob.UseJet(connection) + .EnableServiceProviderCaching(false)) + .BuildServiceProvider(validateScopes: true) + .GetRequiredService>() + : new PooledDbContextFactory(options); - await Dispose(context2, async); - } + var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + Assert.Same(connection, context1.Database.GetDbConnection()); - [ConditionalTheory] // Issue #27308. - [InlineData(false, false, false, false)] - [InlineData(true, false, false, false)] - [InlineData(false, true, false, false)] - [InlineData(true, true, false, false)] - [InlineData(false, false, true, false)] - [InlineData(true, false, true, false)] - [InlineData(false, false, false, true)] - [InlineData(true, false, false, true)] - [InlineData(false, true, false, true)] - [InlineData(true, true, false, true)] - [InlineData(false, false, true, true)] - [InlineData(true, false, true, true)] - public async Task Handle_open_connection_when_returning_to_pool_for_external_connection_with_factory( - bool async, - bool startsOpen, - bool openWithEf, - bool withDependencyInjection) + if (!startsOpen) { - using var connection = new JetConnection(JetNorthwindTestStoreFactory.NorthwindConnectionString); - - if (startsOpen) + if (async) { - if (async) + if (openWithEf) { - await connection.OpenAsync(); + await context1.Database.OpenConnectionAsync(); } else { - connection.Open(); + await connection.OpenAsync(); } } - - var options = new DbContextOptionsBuilder() - .UseJet(connection) - .EnableServiceProviderCaching(false) - .Options; - - var factory = - withDependencyInjection - ? new ServiceCollection() - .AddPooledDbContextFactory( - ob => ob.UseJet(connection) - .EnableServiceProviderCaching(false)) - .BuildServiceProvider(validateScopes: true) - .GetRequiredService>() - : new PooledDbContextFactory(options); - - var context1 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - Assert.Same(connection, context1.Database.GetDbConnection()); - - if (!startsOpen) + else { - if (async) + if (openWithEf) { - if (openWithEf) - { - await context1.Database.OpenConnectionAsync(); - } - else - { - await connection.OpenAsync(); - } + context1.Database.OpenConnection(); } else { - if (openWithEf) - { - context1.Database.OpenConnection(); - } - else - { - connection.Open(); - } + connection.Open(); } } + } - Assert.Equal(System.Data.ConnectionState.Open, connection.State); + Assert.Equal(ConnectionState.Open, connection.State); - await Dispose(context1, async); + await Dispose(context1, async); - Assert.Equal(System.Data.ConnectionState.Open, connection.State); + Assert.Equal(ConnectionState.Open, connection.State); - var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); - Assert.Same(context1, context2); + var context2 = async ? await factory.CreateDbContextAsync() : factory.CreateDbContext(); + Assert.Same(context1, context2); - Assert.Same(connection, context2.Database.GetDbConnection()); + Assert.Same(connection, context2.Database.GetDbConnection()); - Assert.Equal(System.Data.ConnectionState.Open, connection.State); + Assert.Equal(ConnectionState.Open, connection.State); - await Dispose(context2, async); - } + await Dispose(context2, async); + } - [ConditionalTheory] - [InlineData(true)] - [InlineData(false)] - public void Double_dispose_concurrency_test(bool useInterface) - { - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public void Double_dispose_concurrency_test(bool useInterface) + { + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - Parallel.For( - fromInclusive: 0, toExclusive: 32, body: s => - { - using var scope = serviceProvider.CreateScope(); - var scopedProvider = scope.ServiceProvider; + Parallel.For( + fromInclusive: 0, toExclusive: 32, body: s => + { + using var scope = serviceProvider.CreateScope(); + var scopedProvider = scope.ServiceProvider; - var context = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + var context = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - var _ = context.Customers.ToList(); + var _ = context.Customers.ToList(); - context.Dispose(); - }); - } + context.Dispose(); + }); + } - [ConditionalTheory] - [InlineData(false, false)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(true, true)] - public async Task Concurrency_test(bool useInterface, bool async) - { - PooledContext.InstanceCount = 0; - PooledContext.DisposedCount = 0; + [ConditionalTheory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task Concurrency_test(bool useInterface, bool async) + { + PooledContext.InstanceCount = 0; + PooledContext.DisposedCount = 0; - var results = WriteResults(); + var results = WriteResults(); - var serviceProvider = useInterface - ? BuildServiceProvider() - : BuildServiceProvider(); + var serviceProvider = useInterface + ? BuildServiceProvider() + : BuildServiceProvider(); - async Task ProcessRequest() + async Task ProcessRequest() + { + while (_stopwatch.IsRunning) { - while (_stopwatch.IsRunning) - { - using var serviceScope = serviceProvider.CreateScope(); - var scopedProvider = serviceScope.ServiceProvider; + using var serviceScope = serviceProvider.CreateScope(); + var scopedProvider = serviceScope.ServiceProvider; - var context = useInterface - ? (PooledContext)scopedProvider.GetService() - : scopedProvider.GetService(); + var context = useInterface + ? (PooledContext)scopedProvider.GetService() + : scopedProvider.GetService(); - await context!.Customers.AsNoTracking().FirstAsync(c => c.CustomerId == "ALFKI"); + await context!.Customers.AsNoTracking().FirstAsync(c => c.CustomerId == "ALFKI"); - Interlocked.Increment(ref _requests); - } + Interlocked.Increment(ref _requests); } + } - var tasks = new Task[32]; + var tasks = new Task[32]; - for (var i = 0; i < 32; i++) - { - tasks[i] = ProcessRequest(); - } + for (var i = 0; i < 32; i++) + { + tasks[i] = ProcessRequest(); + } - await Task.WhenAll(tasks); - await results; + await Task.WhenAll(tasks); + await results; - Assert.Equal(_requests, PooledContext.DisposedCount); - Assert.InRange(PooledContext.InstanceCount, low: 32, high: 64); - } + Assert.Equal(_requests, PooledContext.DisposedCount); + Assert.InRange(PooledContext.InstanceCount, low: 32, high: 64); + } - private readonly TimeSpan _duration = TimeSpan.FromSeconds(value: 10); + private readonly TimeSpan _duration = TimeSpan.FromSeconds(value: 10); - private int _stopwatchStarted; + private int _stopwatchStarted; - private readonly Stopwatch _stopwatch = new(); + private readonly Stopwatch _stopwatch = new(); - private long _requests; + private long _requests; - private async Task WriteResults() + private async Task WriteResults() + { + if (Interlocked.Exchange(ref _stopwatchStarted, value: 1) == 0) { - if (Interlocked.Exchange(ref _stopwatchStarted, value: 1) == 0) - { - _stopwatch.Start(); - } + _stopwatch.Start(); + } - var lastRequests = (long)0; - var lastElapsed = TimeSpan.Zero; + var lastRequests = (long)0; + var lastElapsed = TimeSpan.Zero; - while (_stopwatch.IsRunning) - { - await Task.Delay(TimeSpan.FromSeconds(value: 1)); + while (_stopwatch.IsRunning) + { + await Task.Delay(TimeSpan.FromSeconds(value: 1)); - var currentRequests = _requests - lastRequests; - lastRequests = _requests; + var currentRequests = _requests - lastRequests; + lastRequests = _requests; - var elapsed = _stopwatch.Elapsed; - var currentElapsed = elapsed - lastElapsed; - lastElapsed = elapsed; + var elapsed = _stopwatch.Elapsed; + var currentElapsed = elapsed - lastElapsed; + lastElapsed = elapsed; - _testOutputHelper? - .WriteLine( - $"[{DateTime.Now:HH:mm:ss.fff}] Requests: {_requests}, " - + $"RPS: {Math.Round(currentRequests / currentElapsed.TotalSeconds)}"); + _testOutputHelper? + .WriteLine( + $"[{DateTime.Now:HH:mm:ss.fff}] Requests: {_requests}, " + + $"RPS: {Math.Round(currentRequests / currentElapsed.TotalSeconds)}"); - if (elapsed > _duration) - { - _testOutputHelper?.WriteLine(message: ""); - _testOutputHelper?.WriteLine($"Average RPS: {Math.Round(_requests / elapsed.TotalSeconds)}"); + if (elapsed > _duration) + { + _testOutputHelper?.WriteLine(message: ""); + _testOutputHelper?.WriteLine($"Average RPS: {Math.Round(_requests / elapsed.TotalSeconds)}"); - _stopwatch.Stop(); - } + _stopwatch.Stop(); } } + } - [ConditionalTheory] - [InlineData(false)] - [InlineData(true)] - public async Task Concurrency_test2(bool async) - { - var factory = BuildFactory(withDependencyInjection: false); + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public async Task Concurrency_test2(bool async) + { + var factory = BuildFactory(withDependencyInjection: false); - await Task.WhenAll( - Enumerable.Range(0, 10).Select( - _ => Task.Run( - async () => + await Task.WhenAll( + Enumerable.Range(0, 10).Select( + _ => Task.Run( + async () => + { + for (var j = 0; j < 1_000_000; j++) { - for (var j = 0; j < 1_000_000; j++) + var ctx = factory.CreateDbContext(); + + if (async) { - var ctx = factory.CreateDbContext(); - - if (async) - { - await ctx.DisposeAsync(); - } - else - { - ctx.Dispose(); - } + await ctx.DisposeAsync(); } - }))); - } + else + { + ctx.Dispose(); + } + } + }))); + } - private async Task Dispose(IDisposable disposable, bool async) + private void UseQueryTrackingBehavior(DbContextOptionsBuilder optionsBuilder, QueryTrackingBehavior? queryTrackingBehavior) + { + if (queryTrackingBehavior.HasValue) { - if (async) - { - await ((IAsyncDisposable)disposable).DisposeAsync(); - } - else - { - disposable.Dispose(); - } + optionsBuilder.UseQueryTrackingBehavior(queryTrackingBehavior.Value); } + } - private readonly ITestOutputHelper _testOutputHelper = null; - - // ReSharper disable once UnusedParameter.Local - public DbContextPoolingTest(NorthwindQueryJetFixture fixture, ITestOutputHelper testOutputHelper) + private async Task Dispose(IDisposable disposable, bool async) + { + if (async) + { + await ((IAsyncDisposable)disposable).DisposeAsync(); + } + else { - //_testOutputHelper = testOutputHelper; + disposable.Dispose(); } } + + private readonly ITestOutputHelper _testOutputHelper; + + public DbContextPoolingTest(NorthwindQueryJetFixture fixture, ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } }