Temos um aplicativo usando o SDK fornecido pelo nosso provedor para integrar-se facilmente a eles. Esse SDK se conecta ao terminal AMQP e simplesmente distribui, armazena em cache e transforma mensagens para nossos consumidores. Anteriormente, essa integração era via HTTP com XML como fonte de dados e a integração antiga tinha duas maneiras de armazenar em cache o DataContext - por solicitação da Web e por ID de encadeamento gerenciado. (1)
Agora, no entanto, não integramos o HTTP, mas sim o AMQP, que é transparente para nós, pois o SDK está fazendo toda a lógica de conexão e resta apenas definir nossos consumidores para que não haja opção de armazenar em cache o DataContext "por solicitação da web", portanto apenas por ID de encadeamento gerenciado é deixado. Eu implementei o padrão de cadeia de responsabilidade; portanto, quando uma atualização chega, ela é colocada em um pipeline de manipuladores que usa o DataContext para atualizar o banco de dados de acordo com as novas atualizações. É assim que o método de chamada do pipeline se parece:
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;
}
O problema é (pelo menos o que eu acho que é) que essa cadeia de responsabilidade é uma cadeia de métodos retornando / iniciando novas tarefas; portanto, quando uma atualização para a entidade A chega, ela é tratada pelo ID do thread gerenciado = 1, digamos, e apenas um tempo depois novamente a mesma entidade A chega apenas para ser tratada pelo ID do encadeamento gerenciado = 2, por exemplo . Isto leva a:
System.InvalidOperationException: 'Um objeto de entidade não pode ser referenciado por várias instâncias do IEntityChangeTracker.'
porque o DataContext do ID do segmento gerenciado = 1 já rastreia a entidade A. (pelo menos é o que eu acho que é)
Minha pergunta é como posso armazenar em cache o DataContext no meu caso? Vocês tiveram o mesmo problema? Eu li estas e estas respostas e pelo que entendi usando um DataContext estático também não é uma opção. (2)
- Isenção de responsabilidade: eu deveria ter dito que herdamos o aplicativo e não posso responder por que ele foi implementado dessa maneira.
- Isenção de responsabilidade 2: Tenho pouca ou nenhuma experiência com a EF.
Perguntas frequentes da comunidade:
- Qual versão do EF estamos usando? 5.0
- Por que as entidades vivem mais que o contexto? - Eles não sabem, mas talvez você esteja se perguntando por que as entidades precisam viver mais do que o contexto. Uso repositórios que usam DataContext em cache para obter entidades do banco de dados para armazená-las em uma coleção na memória que eu uso como cache.
É assim que as entidades são "extraídas", onde DatabaseDataContext
está o DataContext em cache de que estou falando (BLOB com conjuntos de bancos de dados inteiros dentro)
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;
}
Então, sempre que meu aplicativo consumidor recebe a mensagem AMQP, meu padrão de cadeia de responsabilidade começa a verificar se essa mensagem e seus dados já foram processados. Então, eu tenho um método que se parece com isso:
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
}
Eu pensei que obter entidades do banco de dados com AsNoTracking()
método ou desanexar entidades toda vez que "atualizo" ou "insiro" a entidade resolverá isso, mas isso não aconteceu.
SaveChanges
.