Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot access a disposed object. Object name: 'IServiceProvider'. #204

Open
bzbetty opened this issue Sep 18, 2024 · 9 comments
Open

Cannot access a disposed object. Object name: 'IServiceProvider'. #204

bzbetty opened this issue Sep 18, 2024 · 9 comments
Labels
wontfix This will not be worked on

Comments

@bzbetty
Copy link

bzbetty commented Sep 18, 2024

Occasionally hit the following which kills our app.

using latest version 3.

We've only recently implemented EFC Triggered, so we may have done something wrong - this issue is just in case you know about something, otherwise if we figure out what we did wrong we'll add to document it.

Exception: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'IServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()
   at EntityFrameworkCore.Triggered.Internal.HybridServiceProvider.GetService(Type serviceType)
   at EntityFrameworkCore.Triggered.Internal.TriggerFactory.Resolve(IServiceProvider serviceProvider, Type triggerType)+MoveNext()
   at System.Linq.Enumerable.SelectIterator[TSource,TResult](IEnumerable`1 source, Func`3 selector)+MoveNext()
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source, Int32& length)
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.OrderedEnumerable`1.GetEnumerator()+MoveNext()
   at System.Linq.Enumerable.SelectIPartitionIterator`2.MoveNext()
   at System.Linq.Enumerable.CastIterator[TResult](IEnumerable source)+MoveNext()
   at EntityFrameworkCore.Triggered.TriggerSession.RaiseBeforeSaveStartingTriggers(CancellationToken cancellationToken)
   at EntityFrameworkCore.Triggered.Internal.TriggerSessionSaveChangesInterceptor.SavingChangesAsync(DbContextEventData eventData, InterceptionResult`1 result, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

potentially related to #124?

we did add the dbcontextfactory by copying code from #195

  public static IServiceCollection AddTriggeredDbContextFactory<TContext>(IServiceCollection serviceCollection, Action<IServiceProvider, DbContextOptionsBuilder>? optionsAction = null, ServiceLifetime lifetime = ServiceLifetime.Singleton)
      where TContext : DbContext
  {
      serviceCollection.AddDbContextFactory<TContext>((serviceProvider, options) => {
          optionsAction?.Invoke(serviceProvider, options);
          //options.UseTriggers();
      }, lifetime);

      var serviceDescriptor = serviceCollection.FirstOrDefault(x => x.ServiceType == typeof(IDbContextFactory<TContext>));

      if (serviceDescriptor?.ImplementationType != null)
      {
          var triggeredFactoryType = typeof(TriggeredDbContextFactory<,>).MakeGenericType(typeof(TContext), serviceDescriptor.ImplementationType);

          serviceCollection.TryAdd(ServiceDescriptor.Describe(
              serviceType: serviceDescriptor.ImplementationType,
              implementationType: serviceDescriptor.ImplementationType,
              lifetime: serviceDescriptor.Lifetime
          ));

          serviceCollection.Replace(ServiceDescriptor.Describe(
              serviceType: typeof(IDbContextFactory<TContext>),
              implementationFactory: serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, triggeredFactoryType, serviceProvider.GetRequiredService(serviceDescriptor.ImplementationType), serviceProvider),
              lifetime: ServiceLifetime.Transient
          ));
      }

      return serviceCollection;
  }

being called in program.cs (Azure Functions, dotnet 8, isolated)

            AddTriggeredDbContextFactory<ESPContext>(builder, (serviceProvider, opt) =>
            {                
                var secondLevelCacheInterceptor = serviceProvider.GetService<SecondLevelCacheInterceptor>();
                var triggerInterceptor = serviceProvider.GetService<TriggerSessionSaveChangesInterceptor>();

                opt.UseSqlServer(x =>
                {
                    x.UseHierarchyId();
                    x.UseQueryableValues(x =>
                    {
                        x.Serialization(SqlServerSerialization.UseJson);
                    });
                })
                .UseTriggers(triggerOptions => {
                    triggerOptions.AddAssemblyTriggers(ServiceLifetime.Scoped, typeof(Program).Assembly); 
                })
                .UseProjectables(c => c.CompatibilityMode(EntityFrameworkCore.Projectables.Infrastructure.CompatibilityMode.Full))
                .AddInterceptors(triggerInterceptor, secondLevelCacheInterceptor)
                .EnableSensitiveDataLogging();
            });

Also saw the following stackoverflow post which suggests it may be better to inject IServiceScopeFactory instead of IServiceProvider, which might make sense if there's a singleton object somewhere, but i haven't seen one.

https://stackoverflow.com/questions/76745332/c-sharp-timer-cannot-access-a-disposed-object-object-name-iserviceprovider

@bzbetty
Copy link
Author

bzbetty commented Sep 18, 2024

Adding the factory as transient instead of singleton may have fixed the issue.

Happened again, could be because I changed to a pool db factory (which has to be singleton) or perhaps I never fixed the issue

image

@bzbetty
Copy link
Author

bzbetty commented Sep 19, 2024

hmm I seem to also be able to get "SaveChangesWithoutTriggersAsync(), but it throws this exception 'System.InvalidOperationException: 'A triggerSession has already been created''!" occasionally too.

I think there's definitely some threadsafety or leakage issues happening for some reason.

@bzbetty
Copy link
Author

bzbetty commented Sep 20, 2024

Starting to think maybe Azure Functions (or my project) has something very different about it.

Added the EFCore Triggerred Source to my project and I'm hitting some rather weird Debug.Asserts

image

@bzbetty
Copy link
Author

bzbetty commented Sep 20, 2024

wonder if it's due to TriggerSessionSaveChangesInterceptor being registered against the db factory like I did, that might effectively make it shared?

@bzbetty
Copy link
Author

bzbetty commented Sep 20, 2024

yup, seems that was it!

using AddInterceptors like this is bad

 opt.UseSqlServer()
                .UseTriggers()
                .AddInterceptors(triggerInterceptor);

as it means it shares the one interceptor between all dbcontexts (threading issue).

adding it in the dbcontext in onconfiguring seems to work

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
      optionsBuilder.AddInterceptors(new TriggerSessionSaveChangesInterceptor());
      base.OnConfiguring(optionsBuilder);
  }

(still no idea why i had to add it myself, but triggers didn't fire without it)

@bzbetty
Copy link
Author

bzbetty commented Sep 20, 2024

That said you can't use OnConfiguring if Db Pooling is enabled :(

System.InvalidOperationException: 'OnConfiguring' cannot be used to modify DbContextOptions when DbContext pooling is enabled.

@bzbetty
Copy link
Author

bzbetty commented Sep 30, 2024

Curiously I think for the Db Pooling, it might actually work better to use the v2 mode where you extend the dbcontext instead.

May be a good reason not to remove it in v4?

@bzbetty
Copy link
Author

bzbetty commented Oct 2, 2024

also recommend against using

services.AddDbContext(options => options.UseTriggers(triggerOptions => triggerOptions.AddAssemblyTriggers()));

syntax as it does an assembly scan each context cretaion (50ms for me).

@bzbetty bzbetty closed this as completed Oct 27, 2024
@koenbeuk koenbeuk reopened this Nov 5, 2024
Copy link

stale bot commented Jan 6, 2025

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix This will not be worked on label Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

2 participants