有什么方法可以让 ITicketStore 范围化吗?

问题描述 投票:0回答:1

我正在按照这篇文章使用ITicketStore。每当需要执行任何数据库操作时,都会创建此 DbContext。

在我的代码中,不是通过 using() 语法创建 DbContext,而是通过构造函数注入。在代码投入生产之前,一切都工作正常。 当流量增加时开始出现以下异常。

  • System.InvalidOperationException 在上一个操作完成之前,在此上下文上启动了第二个操作。
  • Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException

这可能是因为 ITicketStore 是一个单例,并且使用在多个线程之间共享相同 DbContext 实例的 DI 注入 DbContext。

我更改了代码以通过 using() 创建 DbContext 实例,现在工作正常。

但是我正在尝试找到任何方法通过 DI 注入来使 DbContext 工作。

c# .net asp.net-identity identityserver4 asp.net-core-3.1
1个回答
3
投票

ITicketStore 必须是单例,但您可以注册主单例,并使用

CreateScope()
方法注入您可能需要的任何作用域提供程序。您可以在下面找到使用
SingletonTicketStore
作为包装器的示例实现,并使用
PostConfigureCookieAuthenticationOptionsTicketStore
来使用适当商店中的
CookieAuthenticationOptions

请注意,并非所有情况都需要 PostConfigure。

    //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>());

希望这有帮助;)

© www.soinside.com 2019 - 2024. All rights reserved.