使缓存数据和依赖注入模式失效

Invalidating Cached Data and Dependency Injection Pattern

我有一个数据缓存 class(使用 MemoryCache class)。

这个class的基本功能是缓存引用数据。要获取此参考数据,它需要一个 Entity Framework dbContext 的实例。这通过依赖注入(简单注入器)传入。

但是这个 dbContext 的生命周期是 "per call" (AsyncScopedLifestyle)。因此,为了满足这一点,我将设置缓存的调用放在调用后过期的 "scope" 中。

缓存每 2 小时失效一次并重新查询。不出所料,那时 dbContext 已被清理(因为它超出了范围)。

我可以想办法解决这个问题。但我想知道对于此类问题是否应该遵循某种模式。 (我的大多数解决方案都让我将容器传递到我的缓存中 class。但这似乎违反了几种 DI 模式。)

有谁知道当您反复需要在 class 中注入时可以使用的设计模式?

更多背景知识:

.

using (AsyncScopedLifestyle.BeginScope(container))
{
    // Setup the long lived data caching
    var dataCache = container.GetInstance<DataCache>();
    dataCache.SetupCachedItems();
}

你应该全程依赖DI。换句话说,如果缓存 class 需要上下文,那么这是一个依赖项,应该这样注入:

public class MyCacheClass
{
    private readonly MyContext _context;

    public MyCacheClass(MyContext context)
    {
        _context = context;
    }

    ...
}

这当然假设缓存 class 也有范围内的生命周期,这真的没有理由不应该,因为它与范围内的依赖项交互。但是,如果由于某种原因你需要它有一个单例生命周期,那么你可以简单地注入 IServiceProvider 然后创建一个范围并在需要时拉出上下文:

using (var scope = _serviceProvider.CreateScope())
{
    var context = scope.ServiceProvider.GetRequiredService<MyContext>();
    // do something with context
}

如果您使用的是静态 class,请不要。

我在这里看到两个通用解决方案:

  1. DataCache 管理的缓存移出 class,这样 MyCacheClass 就可以变成 Scoped。这接缝很容易,因为这可能是 MemoryCache 的目的。内存缓存可能是单例。
  2. DataCache 移入 Composition Root so it can safely depend on the container (or a container abstraction), without falling into Service Locator anti-pattern 陷阱。

第一个解决方案可以以多种方式应用。也许这是在 static 字段中定义缓存的问题:

public class DataCache
{
    private static ConcurrentDictionary<string, object> cache;
}

并且如果您注入 MemoryCache 作为数据的存储提供程序,它将包含缓存,并且 DataCache 的生活方式变得无关紧要:

public class DataCache
{
    public DataCache(MyContext context, IMemoryCache cache)
}

但是,如果 DataCache 需要注入到 Singleton 消费者中,则它本身需要是 Singleton。这不允许这种方法,因为 MyContext 需要 Scoped,以防止 Captive Dependencies。为此,您可以使用解决方案 2。

通过解决方案,您可以确保 DataCache 在您的 Composition Root 中创建。这迫使您将 DataCache 隐藏在抽象背后,例如IDataCache。这个抽象可以放在消费者可以依赖的位置,而 DataCache 实现将完全隐藏在 Composition Root 内部。在那个位置依赖 DI 容器变得安全。

// Part of the Composition Root
sealed class DataCache: IDataCache
{
    public DataCache(Container container, IMemoryCache cache) ...

    public ProductData GetProductByKey(string key)
    {
        if (key not in cache)
        {
            using (AsyncScopedLifestyle.BeginScope(this.container))
            {
                var context = container.GetInstance<MyContext>();
                var p = context.Products.SingleOrDefault(p => p.Key == key);
                var data = new ProductData(p);
                AddProductToCache(key, data);
                return data;
            }
        }
    }
}