如何在消费者类型应用程序中缓存DataContext实例?
如何在消费者类型应用程序中缓存DataContext实例?
我们有一个应用程序,使用我们提供的SDK与供应商轻松集成。该SDK连接到AMQP端点,简单地将消息分发、缓存和转换给我们的消费者。以前,这种集成是通过HTTP使用XML作为数据源进行的,并且旧的集成有两种方式来缓存DataContext-每个Web请求和每个托管线程ID。(1)
现在,我们不再通过HTTP进行集成,而是使用AMQP,这对我们来说是透明的,因为SDK完成了所有的连接逻辑,我们只需要定义我们的消费者,所以没有选项可以缓存DataContext "per web request",只剩下每个托管线程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 IQueryableGet (params Expression >[] includes) { var query = DatabaseDataContext.Set ().AsQueryable(); if (includes != null && includes.Length > 0) { foreach (var item in includes) { query = query.Include(item); } } return query; }
然后,每当我的消费者应用程序接收到AMQP消息时,我的责任链模式开始检查是否已经处理了此消息及其数据。所以我有一个像这样的方法:
public async TaskHandle (TEntity sportEvent) where TEntity : ISportEvent { ...一些不重要的业务逻辑 //保存体育项目 if (sport.SportID > 0) // <-- 这里基本上检查所谓的运动是否在缓存中找到 // 如果找到了,我们就更新数据库中的实体 // 然后更新缓存 { _sportRepository.Update(sport); /* * 因为可能会有同一体育项目的消息更新 * 由于DataContext像我说的那样由线程ID缓存 * 并且Update可能是从不同的线程执行的 * 这就是上述异常抛出的地方 */ } else // 如果没有,就简单地在数据库和缓存中插入实体 { _sportRepository.Insert(sport); } _sportRepository.SaveDbChanges(); ... 更新缓存逻辑 }
我以为使用AsNoTracking()
方法从数据库获取实体,或者每次"更新"或"插入"实体时都分离实体,会解决这个问题,但事实并非如此。
在一个消费者类型的应用程序中如何缓存DataContext实例?
这个问题的出现原因是,应用程序中存在多个DataContext实例,而实体被多个DataContext实例引用。当尝试使用第二个DataContext实例更新从第一个DataContext实例加载的实体时,就会导致错误。
解决方法是将db contexts的生命周期更改为每个消息一次,或者在处理完每个消息后清空它们的更改跟踪器。当从缓存中检索到实体后,在应用任何更改之前,需要将其附加到当前的db context中,以便处理实体更新。
虽然作者同意这个解决方法,但问题仍然存在。目前的解决方法是跳过“repository”,直接与data context一起工作。
以上是关于在消费者类型应用程序中如何缓存DataContext实例的问题的原因和解决方法。
问题的原因是在一个消费型应用程序中,每次执行简单的CRUD操作都会new一个新的DbContext实例,导致了一定的开销。解决方法是将DbContext实例进行缓存,以减少开销。
文章内容如下:
在一个消费型应用程序中,每次执行简单的CRUD操作都会new一个新的DbContext实例,这样会产生一定的开销。虽然使用依赖注入(DI)来共享单个DbContext实例可以节省一部分开销,但对于简单的CRUD操作来说,每个操作都new一个新的DbContext实例可能更简单。
看一下您目前发布的代码,我可能会在Repository构造函数中new一个私有的DbContext实例,然后在每个方法中new一个Repository实例。
然后,您的方法可能会像这样:
public async TaskHandle (TEntity sportEvent) where TEntity : ISportEvent { var sportsRepository = new SportsRepository() ... 一些不重要的业务逻辑 //保存运动项目 if (sport.SportID > 0) { _sportRepository.Update(sport); } else { _sportRepository.Insert(sport); } _sportRepository.SaveDbChanges(); }
您还可以考虑使用“Stub Entities”作为避免与其他存储库类共享DbContext的一种方法。
是的,不幸的是,数据层项目既被我们的新服务使用,也被旧的网站应用程序使用,并且不会改变。:/ 我最终使用了一个单例的DbContext来处理所有的流程,当我们将我们的流程设置为多线程时,我想以后会考虑更改这个。