Tenemos una aplicación que utiliza SDK proporcionada por nuestro proveedor para integrarse fácilmente con ellos. Este SDK se conecta al punto final AMQP y simplemente distribuye, almacena en caché y transforma mensajes a nuestros consumidores. Anteriormente, esta integración era a través de HTTP con XML como fuente de datos y la integración anterior tenía dos formas de almacenar en caché DataContext: por solicitud web y por ID de subproceso administrado. (1)
Ahora, sin embargo, no nos integramos a través de HTTP, sino más bien AMQP, que es transparente para nosotros, ya que el SDK está haciendo toda la lógica de conexión y solo nos queda definir a nuestros consumidores, por lo que no hay opción de almacenar en caché DataContext "por solicitud web". solo queda por ID de hilo administrado. Implementé un patrón de cadena de responsabilidad, por lo que cuando recibimos una actualización, se coloca en una tubería de controladores que usa DataContext para actualizar la base de datos de acuerdo con las nuevas actualizaciones. Así es como se ve el método de invocación de la canalización:
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;
}
El problema es (al menos lo que creo que es) que esta cadena de responsabilidad es una cadena de métodos que devuelven / inician nuevas tareas, por lo que cuando llega una actualización para la entidad A, se maneja mediante el hilo administrado id = 1, digamos, y solo en algún momento después de nuevo, la misma entidad A llega solo para ser manejada por el hilo administrado id = 2, por ejemplo . Esto lleva a:
System.InvalidOperationException: "Un objeto de entidad no puede ser referenciado por múltiples instancias de IEntityChangeTracker".
porque DataContext del hilo administrado id = 1 ya rastrea la entidad A. (al menos eso es lo que creo que es)
Mi pregunta es ¿cómo puedo almacenar en caché DataContext en mi caso? ¿Ustedes tuvieron el mismo problema? Leí esto y estas respuestas y, por lo que entendí, usar un DataContext estático tampoco es una opción. (2)
- Descargo de responsabilidad: debería haber dicho que heredamos la aplicación y no puedo responder por qué se implementó así.
- Descargo de responsabilidad 2: Tengo poca o ninguna experiencia con EF.
La comunidad hizo preguntas:
- ¿Qué versión de EF estamos usando? 5.0
- ¿Por qué las entidades viven más tiempo que el contexto? - No lo hacen, pero tal vez se pregunte por qué las entidades necesitan vivir más tiempo que el contexto. Utilizo repositorios que usan DataContext en caché para obtener entidades de la base de datos para almacenarlas en una colección en memoria que uso como caché.
Así es como se "extraen" las entidades, dónde DatabaseDataContext
está el DataContext en caché del que estoy hablando (BLOB con conjuntos de bases de datos enteras 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;
}
Luego, cada vez que mi solicitud de consumidor recibe un mensaje AMQP, mi patrón de cadena de responsabilidad comienza a verificar si este mensaje y sus datos ya los he procesado. Entonces tengo un método que se ve así:
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
}
Pensé que obtener entidades de la base de datos con el AsNoTracking()
método o separar entidades cada vez que "actualizaba" o "insertaba" la entidad resolvería esto, pero no fue así.
SaveChanges
.