【问题标题】:netcore 2.1 Distributed cache with Scoped repositorynetcore 2.1 具有范围存储库的分布式缓存
【发布时间】:2019-06-12 14:45:09
【问题描述】:

需要帮助。

我有一个 .netcore 2.1 API,它通过来自其客户端的 Azure Bearer 令牌进行保护。我想从客户端的不记名令牌中收集用户信息并将其存储在 SQL 数据库中,以便在添加/删除/编辑等时标记数据库中的条目。因此,对于 SQL 表连接,我需要用户SQL 中的信息。

下面是我使用 IDistributedCache 实现的缓存服务。在初始化时,我试图将所有当前存储的用户从 SQL DB 加载到缓存中,然后在新用户连接时添加到缓存中。

为了捕获整个 API 之间的连接,我使用 TypeFilterAttribute 来执行 OnActionExecuting。

问题是 CacheService 是一个单例并且正在调用 UserRepository - 这是范围内的。这是不允许的。

有什么想法吗?

startup.cs

public void ConfigureServices(IServiceCollection services)
        {
...
            // Context
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.TryAddSingleton<CacheService>();

            // Repositories
            services.TryAddScoped<IUserRepository, UserRepository>();                    

            services.AddDistributedMemoryCache();

            services.AddMvc(
               opts => opts.Filters.Add(new HttpInterceptor())
                )
...

CacheService.cs

public class CacheService
    {
        private readonly IDistributedCache _cache;
        private readonly IUserRepository _userRepository;

        public CacheService(
            IDistributedCache cache,
          [FromServices]  IUserRepository userRepository
            )
        {
            _cache = cache;
            _userRepository = userRepository;

            // Populate cache from DB
            var users = _userRepository.GetAll().Result;
            foreach (var u in users)
            {
                if (_cache.GetAsync(u.Username).Result == null)
                {
                    var profileSerialised = JsonConvert.SerializeObject(UserToUserProfile(u));
                    var entry = Encoding.UTF8.GetBytes(profileSerialised);
                    _cache.SetAsync(u.Username, entry, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
                }
            }
        }

HttpInterceptor.cs

 public class HttpInterceptor : TypeFilterAttribute
    {
        public HttpInterceptor() : base(typeof(IHttpInterceptor))
        {

        }

        private class IHttpInterceptor : IActionFilter
        {
            private readonly CacheService _cache;
            private readonly IUserRepository _userRepository;

            public IHttpInterceptor(
                CacheService cache,
                IUserRepository userRepository)
            {
                _cache = cache;
                _userRepository = userRepository;
            }


            public void OnActionExecuting(ActionExecutingContext context)
            {
                if (context.HttpContext.User.Identity.IsAuthenticated)
                {
                  this._cache.GetUserProfile(context.HttpContext.User.Identity.Name);
                }
            }

【问题讨论】:

    标签: asp.net-core dependency-injection asp.net-core-2.0 repository-pattern


    【解决方案1】:

    首先,您是在上下颠倒地看待这个问题。让一些服务将内容添加到缓存中,然后让其他代码假设这些内容已在缓存中准备就绪,这是灾难的根源。相反,让您的依赖代码逐字地请求它需要的数据,然后,如果您想缓存它,请使用获取数据的方法。这样,您的应用程序代码就不会知道数据的来源;它只是调用一个方法并获取它想要的数据。在后台,它要么从数据库中提取,要么从缓存中提取,具体取决于哪个可用/首选。

    无论如何,您的缓存服务存在严重问题。首先,它首先不应该是单例。没有理由这样做,并且由于您正在处理内部的范围服务,您只会让事情变得比他们需要的更困难。其次,你永远不应该在构造函数中使用 I/O。只有简单的变量/道具初始化应该在那里完成。任何需要实际工作的东西都应该进入方法。如果你真的想在初始化时做点什么,那么你应该实现一个工厂模式。例如,您可能有类似 CacheServiceFactoryCreate 方法的东西,它返回完全实例化的 CacheService,包括调用任何实际工作的方法。

    撇开免责声明不谈,一般来说,要在单例中使用作用域服务,您必须创建一个作用域。每次您想使用该服务时都必须这样做;您不能将服务持久化到单例类上的 ivar。简单地说,你将IServiceProvider 注入你的类,它本身是单例范围的,所以你不会有任何问题。然后,当您需要使用范围服务时:

    using (var scope = provider.CreateScope())
    {
        var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>();
        // do something with repo
    }
    

    这称为服务定位器反模式。之所以这样称呼它,是因为您确实应该避免这样做。有时这并不总是可能的。但是,通常情况下,您可以简单地以不同的方式设计事物:例如使服务本身限定范围。

    【讨论】:

    • 感谢您深思熟虑的回复。我没有包含用于检索信息的代码,但我确实首先调用了缓存,如果没有存储任何内容,它将从数据库或 Microsoft Graph API 收集,将其添加到缓存和数据库并返回它。我对使 Chache 服务成为范围服务的担忧是,每次启动都会重新加载整个缓存,这会破坏缓存的要点吗?
    • 缓存不是这样工作的。有一个提供程序,可以是内存、Redis、SQL Server 等。这就是 IDistributedCache 的支持,它是一个 Singleton。将其注入缓存服务只使用现有实例。
    • 当然,在访问数据库之前先访问缓存。我的观点是,您首先用来检索数据的方法也应该负责缓存。您的应用程序不应该知道或关心它是来自数据库还是来自缓存。它只是要求提供数据。
    猜你喜欢
    • 2012-12-28
    • 1970-01-01
    • 2014-12-26
    • 1970-01-01
    • 2014-03-20
    • 1970-01-01
    • 2020-01-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多