Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to make ITicketStore scoped?

I am using ITicketStore as per this article. In this DbContext is created whenever required to perform any database operations.

In my code instead of creating the DbContext by using() syntax, injecting through the constructor. Everything was working fine until the code goes production. Started getting bellow exceptions when traffic increased.

  • System.InvalidOperationException A second operation started on this context before a previous operation completed.
  • Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException

This probably because ITicketStore is a singleton and injecting DbContext using DI that shared the same DbContext instance between multiple threads.

I changed the code to create a DbContext instance by using() and it's working fine now.

But I am trying to find out any way to make DbContext work by injecting through DI.

like image 889
Ravi Avatar asked Oct 16 '25 12:10

Ravi


1 Answers

ITicketStore has to be a Singleton, but you can register the main one as such and inject any scoped providers that you might want using the CreateScope() method. Below you can find example implementation using SingletonTicketStore as a wrapper with PostConfigureCookieAuthenticationOptionsTicketStore to use CookieAuthenticationOptions from proper store.

Note that PostConfigure is not required in all cases.

    //This sets the store to the options.
    public class PostConfigureCookieAuthenticationOptionsTicketStore : IPostConfigureOptions<CookieAuthenticationOptions>
    {
        private readonly SingletonTicketStore _store;

        public PostConfigureCookieAuthenticationOptionsTicketStore(SingletonTicketStore store) => _store = store;

        public void PostConfigure(string name, CookieAuthenticationOptions options) => options.SessionStore = _store;
    }
    
    //This implemenation is singleton compatible
    public class SingletonTicketStore : ITicketStore
    {
        private readonly IServiceProvider _services;

        public SingletonTicketStore(IServiceProvider services)
        {
            _services = services;
        }

        public async Task RemoveAsync(string key) => await WithScopeAsync(s => s.RemoveAsync(key));
        public async Task RenewAsync(string key, AuthenticationTicket ticket) => await WithScopeAsync(s => s.RenewAsync(key, ticket));
        public async Task<AuthenticationTicket> RetrieveAsync(string key) => await WithScopeAsync(s => s.RetrieveAsync(key));
        public async Task<string> StoreAsync(AuthenticationTicket ticket) => await WithScopeAsync(s => s.StoreAsync(ticket));

        private async Task WithScopeAsync(Func<ITicketStore, Task> action) => await WithScopeAsync(async t => { await action(t); return true; });
        private async Task<T> WithScopeAsync<T>(Func<ITicketStore, Task<T>> action)
        {
            //This opens a new scope, allowing you to use scoped dependencies
            using (var scope = _services.CreateScope())
            {
                //don't forget to await the result here, of stuff (dbcontext) could be disposed otherwise
                return await action(scope.ServiceProvider.GetRequiredService<ITicketStore>());
            }
        }
    }

    public class ScopedTicketStore : ITicketStore
    {
        //your implementation
    }

//in Startup.cs ConfigureServices method  

//register Singleton ticket store as ITicketStore and as standalone singleton
services.AddSingleton<SingletonTicketStore>();
services.AddSingleton<ITicketStore, SingletonTicketStore>(serviceProvider => 
    serviceProvider.GetRequiredService<SingletonTicketStore>());

//register your store as scoped (without specifying interface)
services.AddScoped<ScopedTicketStore>();

// optional
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptionsTicketStore>());

Hope this helps ;)

like image 112
Romain Vergnory Avatar answered Oct 18 '25 03:10

Romain Vergnory



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!