使缓存数据和依赖注入模式失效
Invalidating Cached Data and Dependency Injection Pattern
我有一个数据缓存 class(使用 MemoryCache
class)。
这个class的基本功能是缓存引用数据。要获取此参考数据,它需要一个 Entity Framework dbContext 的实例。这通过依赖注入(简单注入器)传入。
但是这个 dbContext 的生命周期是 "per call" (AsyncScopedLifestyle
)。因此,为了满足这一点,我将设置缓存的调用放在调用后过期的 "scope" 中。
缓存每 2 小时失效一次并重新查询。不出所料,那时 dbContext 已被清理(因为它超出了范围)。
我可以想办法解决这个问题。但我想知道对于此类问题是否应该遵循某种模式。 (我的大多数解决方案都让我将容器传递到我的缓存中 class。但这似乎违反了几种 DI 模式。)
有谁知道当您反复需要在 class 中注入时可以使用的设计模式?
更多背景知识:
- 我的缓存 class(称为
DataCache
)从构造函数注入中获取上下文。
- 设置它的调用是从 Startup.cs 中的 Configure 方法进行的。这看起来像这样:
.
using (AsyncScopedLifestyle.BeginScope(container))
{
// Setup the long lived data caching
var dataCache = container.GetInstance<DataCache>();
dataCache.SetupCachedItems();
}
- 它设置MemoryCache 在两小时后使缓存中的数据过期。但是注入的上下文到那时早就被清理了。
你应该全程依赖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,请不要。
我在这里看到两个通用解决方案:
- 将
DataCache
管理的缓存移出 class,这样 MyCacheClass
就可以变成 Scoped
。这接缝很容易,因为这可能是 MemoryCache
的目的。内存缓存可能是单例。
- 将
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;
}
}
}
}
我有一个数据缓存 class(使用 MemoryCache
class)。
这个class的基本功能是缓存引用数据。要获取此参考数据,它需要一个 Entity Framework dbContext 的实例。这通过依赖注入(简单注入器)传入。
但是这个 dbContext 的生命周期是 "per call" (AsyncScopedLifestyle
)。因此,为了满足这一点,我将设置缓存的调用放在调用后过期的 "scope" 中。
缓存每 2 小时失效一次并重新查询。不出所料,那时 dbContext 已被清理(因为它超出了范围)。
我可以想办法解决这个问题。但我想知道对于此类问题是否应该遵循某种模式。 (我的大多数解决方案都让我将容器传递到我的缓存中 class。但这似乎违反了几种 DI 模式。)
有谁知道当您反复需要在 class 中注入时可以使用的设计模式?
更多背景知识:
- 我的缓存 class(称为
DataCache
)从构造函数注入中获取上下文。 - 设置它的调用是从 Startup.cs 中的 Configure 方法进行的。这看起来像这样:
.
using (AsyncScopedLifestyle.BeginScope(container))
{
// Setup the long lived data caching
var dataCache = container.GetInstance<DataCache>();
dataCache.SetupCachedItems();
}
- 它设置MemoryCache 在两小时后使缓存中的数据过期。但是注入的上下文到那时早就被清理了。
你应该全程依赖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,请不要。
我在这里看到两个通用解决方案:
- 将
DataCache
管理的缓存移出 class,这样MyCacheClass
就可以变成Scoped
。这接缝很容易,因为这可能是MemoryCache
的目的。内存缓存可能是单例。 - 将
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;
}
}
}
}