如何在使用者类型的应用程序中缓存DataContext实例? [英] How to cache DataContext instances in a consumer type application?

查看:48
本文介绍了如何在使用者类型的应用程序中缓存DataContext实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个由供应商提供的使用SDK的应用程序,可轻松与它们集成。该SDK连接到AMQP端点,并简单地向我们的使用者分发,缓存和转换消息。以前,这种集成是通过HTTP进行的,使用XML作为数据源,并且旧的集成具有两种缓存DataContext的方式-每个Web请求和每个托管线程ID。 (1)

We have an application using SDK provided by our provider to integrate easily with them. This SDK connects to AMQP endpoint and simply distributes, caches and transforms messages to our consumers. Previously this integration was over HTTP with XML as a data source and old integration had two ways of caching DataContext - per web request and per managed thread id. (1)

现在,我们不是通过HTTP集成,而是AMQP集成,这对我们是透明的,因为SDK负责所有连接逻辑,我们只剩下通过定义我们的使用者,因此没有选项按Web请求缓存DataContext,因此仅保留每个受管线程ID。
我实现了责任链模式,所以当有更新出现时,它放在一个处理程序管道中,该处理程序使用DataContext根据新更新来更新数据库。管道的调用方法如下所示:

Now, however, we do not integrate over HTTP but rather AMQP which is transparent to us since the SDK is doing all the connection logic and we are only left with defining our consumers so there is no option to cache DataContext "per web request" so only per managed thread id is left. I implemented chain of responsibility pattern, so when an update comes to us it is put in one pipeline of handlers which uses DataContext to update the database according to the new updates. This is how the invocation method of pipeline looks like:

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例如。这将导致:

The problem is (at least what I think it is) that this chain of responsibility is chain of methods returning/starting new tasks so when an update for entity A comes it is handled by managed thread id = 1 let's say and then only sometime after again same entity A arrives only to be handled by managed thread id = 2 for example. This leads to:


System.InvalidOperationException:'一个实体对象不能被多个IEntityChangeTracker实例引用。'

System.InvalidOperationException: 'An entity object cannot be referenced by multiple instances of IEntityChangeTracker.'

因为托管线程ID = 1的DataContext已经跟踪了实体A。(至少我是这样认为的)

because DataContext from managed thread id = 1 already tracks entity A. (at least that's what I think it is)

我的问题是如何在我的情况下缓存DataContext?你们有同样的问题吗?我阅读了答案,据我了解,使用一个静态DataContext也不是一种选择。(2)

My question is how can I cache DataContext in my case? Did you guys have the same problem? I read this and this answers and from what I understood using one static DataContext is not an option also.(2)


  1. 免责声明:我应该说我们继承了该应用程序,但我无法回答为什么要这样实现。

  2. 免责声明2:我对EF几乎没有经验。

  1. Disclaimer: I should have said that we inherited the application and I cannot answer why it was implemented like that.
  2. Disclaimer 2: I have little to no experience with EF.






社区问的问题:


Comunity asked questions:


  1. 我们正在使用什么版本的EF? 5.0

  2. 为什么实体的生存期比上下文长? -他们没有,但是您可能在问为什么实体的寿命要比上下文长。我使用的存储库使用缓存的DataContext从数据库中获取实体,以将它们存储在内存中的集合中,并将其用作缓存。

这是提取实体的方式,其中 DatabaseDataContext 是我正在谈论的缓存DataContext(内部包含整个数据库集的BLOB)

This is how entities are "extracted", where DatabaseDataContext is the cached DataContext I am talking about (BLOB with whole database sets inside)

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消息时,我的责任链模式就会开始检查如果此消息及其数据我已经处理过。因此,我有一个看起来像这样的方法:

Then, whenever my consumer application receives AMQP message my chain of responsibility pattern begins checking if this message and its data I already processed. So I have method that looks like that:

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()方法从数据库中获取实体或分离实体将解决此问题,但是并没有解决。

I thought that getting entities from the database with AsNoTracking() method or detaching entities every time I "update" or "insert" entity will solve this, but it did not.

推荐答案

尽管有一定的开销来更新DbContext,并且使用DI来共享Web请求中的单个DbContext实例可以节省一些开销,简单的CRUD操作就可以为每个操作新建一个新的DbContext。

Whilst there is a certain overhead to newing up a DbContext, and using DI to share a single instance of a DbContext within a web request can save some of this overhead, simple CRUD operations can just new up a new DbContext for each action.

看看到目前为止已发布的代码,我可能会在Repository构造函数中新建一个DbContext的私有实例,然后为每个实例新建一个Repository方法。

Looking at the code you have posted so far, I would probably have a private instance of the DbContext newed up in the Repository constructor, and then new up a Repository for each method.

然后您的方法将如下所示:

Then your method would look something like this:

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的一种方式。

You might also want to consider the use of Stub Entities as a way around sharing a DbContext with other repository classes.

这篇关于如何在使用者类型的应用程序中缓存DataContext实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆