首页 > 解决方案 > 如何在消费者类型的应用程序中缓存 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)

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

社区提出的问题:

  1. 我们使用的是什么版本的 EF?5.0
  2. 为什么实体的寿命比上下文长?- 他们没有,但也许你在问为什么实体需要比上下文更长寿。我使用使用缓存 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()每次我“更新”或“插入”实体时使用方法从数据库中获取实体或分离实体将解决这个问题,但事实并非如此。

标签: c#entity-frameworkdatacontext

解决方案


虽然更新 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 的一种方式。


推荐阅读