如何在消费者类型应用程序中缓存DataContext实例?

9 浏览
0 Comments

如何在消费者类型应用程序中缓存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)

  1. 免责声明:我应该说我们继承了这个应用程序,我不能回答为什么要这样实现。
  2. 免责声明2:我对EF几乎没有经验。

社区提出的问题:

  1. 我们使用的EF版本是什么?5.0
  2. 为什么实体的生存时间超过上下文的生存时间?- 他们没有,但也许你是在问为什么实体需要比上下文更长的生存时间。我使用的存储库使用了缓存的DataContext从数据库中获取实体,然后将它们存储在内存中的集合中,我将其用作缓存。

这是"提取"实体的方法,其中DatabaseDataContext是我所说的缓存的DataContext(具有整个数据库集合的BLOB)

protected IQueryable Get(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 Task Handle(TEntity sportEvent)
            where TEntity : ISportEvent
{
    ...一些不重要的业务逻辑
    //保存体育项目
    if (sport.SportID > 0) // <-- 这里基本上检查所谓的运动是否在缓存中找到
                           // 如果找到了,我们就更新数据库中的实体
                           // 然后更新缓存
    {
        _sportRepository.Update(sport); /* 
                                         * 因为可能会有同一体育项目的消息更新
                                         * 由于DataContext像我说的那样由线程ID缓存
                                         * 并且Update可能是从不同的线程执行的
                                         * 这就是上述异常抛出的地方
                                        */
    }
    else                   // 如果没有,就简单地在数据库和缓存中插入实体
    {
        _sportRepository.Insert(sport);
    }
    _sportRepository.SaveDbChanges();
    ... 更新缓存逻辑
}

我以为使用AsNoTracking()方法从数据库获取实体,或者每次"更新"或"插入"实体时都分离实体,会解决这个问题,但事实并非如此。

0
0 Comments

在一个消费者类型的应用程序中如何缓存DataContext实例?

这个问题的出现原因是,应用程序中存在多个DataContext实例,而实体被多个DataContext实例引用。当尝试使用第二个DataContext实例更新从第一个DataContext实例加载的实体时,就会导致错误。

解决方法是将db contexts的生命周期更改为每个消息一次,或者在处理完每个消息后清空它们的更改跟踪器。当从缓存中检索到实体后,在应用任何更改之前,需要将其附加到当前的db context中,以便处理实体更新。

虽然作者同意这个解决方法,但问题仍然存在。目前的解决方法是跳过“repository”,直接与data context一起工作。

以上是关于在消费者类型应用程序中如何缓存DataContext实例的问题的原因和解决方法。

0
0 Comments

问题的原因是在一个消费型应用程序中,每次执行简单的CRUD操作都会new一个新的DbContext实例,导致了一定的开销。解决方法是将DbContext实例进行缓存,以减少开销。

文章内容如下:

在一个消费型应用程序中,每次执行简单的CRUD操作都会new一个新的DbContext实例,这样会产生一定的开销。虽然使用依赖注入(DI)来共享单个DbContext实例可以节省一部分开销,但对于简单的CRUD操作来说,每个操作都new一个新的DbContext实例可能更简单。

看一下您目前发布的代码,我可能会在Repository构造函数中new一个私有的DbContext实例,然后在每个方法中new一个Repository实例。

然后,您的方法可能会像这样:

public async Task Handle(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来处理所有的流程,当我们将我们的流程设置为多线程时,我想以后会考虑更改这个。

0