c# - 如何在消费者类型的应用程序中缓存 DataContext 实例?
问题描述
我们有一个使用我们提供商提供的 SDK 的应用程序,可以轻松地与他们集成。此 SDK 连接到 AMQP 端点,并简单地将消息分发、缓存和转换给我们的消费者。以前,这种集成是通过 HTTP 以 XML 作为数据源的,而旧的集成有两种缓存 DataContext 的方法——每个 Web 请求和每个托管线程 ID。(1)
但是,现在我们不通过 HTTP 集成,而是通过 AMQP 进行集成,这对我们来说是透明的,因为 SDK 正在执行所有连接逻辑,我们只需要定义我们的消费者,因此没有选项可以“按 Web 请求”缓存 DataContext,所以只剩下每个托管线程 id。我实现了责任链模式,所以当更新来到我们这里时,它被放入一个处理程序管道中,该处理程序使用 DataContext 根据新的更新来更新数据库。管道的调用方法如下所示:
public Task Invoke(TInput entity)
{
object currentInputArgument = entity;
for (var i = 0; i < _pipeline.Count; ++i)
{
var action = _pipeline[i];
if (action.Method.ReturnType.IsSubclassOf(typeof(Task)))
{
if (action.Method.ReturnType.IsConstructedGenericType)
{
dynamic tmp = action.DynamicInvoke(currentInputArgument);
currentInputArgument = tmp.GetAwaiter().GetResult();
}
else
{
(action.DynamicInvoke(currentInputArgument) as Task).GetAwaiter().GetResult();
}
}
else
{
currentInputArgument = action.DynamicInvoke(currentInputArgument);
}
}
return Task.CompletedTask;
}
问题是(至少我认为是这样)这个责任链是返回/启动新任务的方法链,所以当实体 A 的更新到来时,它由托管线程 id = 1 来处理,比如说,然后只是在某个时间之后例如,同样的实体 A 仅由托管线程 id = 2 处理。这将导致:
System.InvalidOperationException:“一个实体对象不能被多个 IEntityChangeTracker 实例引用。”
因为来自托管线程 id = 1 的 DataContext 已经跟踪实体 A。(至少我认为是这样)
我的问题是如何在我的情况下缓存 DataContext ?你们有同样的问题吗?我读了这个和这个答案,据我了解,使用一个静态 DataContext 也不是一种选择。(2)
- 免责声明:我应该说我们继承了该应用程序,我无法回答为什么要这样实现它。
- 免责声明 2:我对 EF 几乎没有经验。
社区提出的问题:
- 我们使用的是什么版本的 EF?5.0
- 为什么实体的寿命比上下文长?- 他们没有,但也许你在问为什么实体需要比上下文更长寿。我使用使用缓存 DataContext 的存储库从数据库中获取实体,以将它们存储在我用作缓存的内存集合中。
这就是“提取”实体的方式,DatabaseDataContext
我正在谈论的缓存 DataContext 在哪里(内部包含整个数据库集的 BLOB)
protected IQueryable<T> Get<TProperty>(params Expression<Func<T, TProperty>>[] includes)
{
var query = DatabaseDataContext.Set<T>().AsQueryable();
if (includes != null && includes.Length > 0)
{
foreach (var item in includes)
{
query = query.Include(item);
}
}
return query;
}
然后,每当我的消费者应用程序收到 AMQP 消息时,我的责任链模式就会开始检查我是否已经处理了该消息及其数据。所以我有看起来像这样的方法:
public async Task<TEntity> Handle<TEntity>(TEntity sportEvent)
where TEntity : ISportEvent
{
... some unimportant business logic
//save the sport
if (sport.SportID > 0) // <-- this here basically checks if so called
// sport is found in cache or not
// if its found then we update the entity in the db
// and update the cache after that
{
_sportRepository.Update(sport); /*
* because message update for the same sport can come
* and since DataContext is cached by threadId like I said
* and Update can be executed from different threads
* this is where aforementioned exception is thrown
*/
}
else // if not simply insert the entity in the db and the caches
{
_sportRepository.Insert(sport);
}
_sportRepository.SaveDbChanges();
... updating caches logic
}
我认为AsNoTracking()
每次我“更新”或“插入”实体时使用方法从数据库中获取实体或分离实体将解决这个问题,但事实并非如此。
解决方案
虽然更新 DbContext 有一定的开销,并且使用 DI 在 Web 请求中共享 DbContext 的单个实例可以节省一些开销,但简单的 CRUD 操作可以为每个操作新建一个新的 DbContext。
查看您迄今为止发布的代码,我可能会在 Repository 构造函数中新建一个 DbContext 的私有实例,然后为每个方法新建一个 Repository。
然后你的方法看起来像这样:
public async Task<TEntity> Handle<TEntity>(TEntity sportEvent)
where TEntity : ISportEvent
{
var sportsRepository = new SportsRepository()
... some unimportant business logic
//save the sport
if (sport.SportID > 0)
{
_sportRepository.Update(sport);
}
else
{
_sportRepository.Insert(sport);
}
_sportRepository.SaveDbChanges();
}
public class SportsRepository
{
private DbContext _dbContext;
public SportsRepository()
{
_dbContext = new DbContext();
}
}
您可能还想考虑使用存根实体作为与其他存储库类共享 DbContext 的一种方式。
推荐阅读
- build - 是否有相当于“仅显示最新构建状态”的 Jenkins 管道?
- sqlite - Android上后台获取中的Flutter访问数据库
- python - 如何找到最小的四位数字,其数字不重复,但加起来是一个随机数
- php - 如何在codeigniter 4中使用ajax
- terraform - 如何在具有 0.12 for_each 的模块内拥有条件资源
- error-handling - 带有未定义索引的 php 文件中的错误,我已经尝试了一切
- angular - 重置 Angular 表单的更简洁方法
- node.js - 如何在 Gitlab CI 中使用用户名和密码将 Mongo 服务 URI 设置为变量?
- java - Java Crypto AES/GCM/NoPadding 适用于 Windows 但不适用于 Docker (AEADBadTagException)
- monad-transformers - 如何调用依赖于 Kotlin/Arrow 中多个类型类的多态函数