Wir haben eine Anwendung, die das von unserem Anbieter bereitgestellte SDK verwendet, um es einfach in sie zu integrieren. Dieses SDK stellt eine Verbindung zum AMQP-Endpunkt her und verteilt, zwischenspeichert und transformiert Nachrichten einfach an unsere Kunden. Zuvor erfolgte diese Integration über HTTP mit XML als Datenquelle, und bei der alten Integration gab es zwei Möglichkeiten, DataContext zwischenzuspeichern - pro Webanforderung und pro verwalteter Thread-ID. (1)
Jetzt integrieren wir jedoch nicht über HTTP, sondern AMQP, was für uns transparent ist, da das SDK die gesamte Verbindungslogik übernimmt und wir nur noch unsere Konsumenten definieren müssen, sodass es keine Option gibt, DataContext "pro Webanforderung" zwischenzuspeichern Nur pro verwalteter Thread-ID bleibt übrig. Ich habe ein Chain-of-Responsibility-Muster implementiert. Wenn ein Update bei uns eingeht, wird es in eine Pipeline von Handlern gestellt, die DataContext verwenden, um die Datenbank gemäß den neuen Updates zu aktualisieren. So sieht die Aufrufmethode der Pipeline aus:
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;
}
Das Problem ist (zumindest was ich denke), dass diese Verantwortungskette eine Kette von Methoden ist, die neue Aufgaben zurückgeben / starten. Wenn also ein Update für Entität A kommt, wird es von der verwalteten Thread-ID = 1 behandelt, sagen wir, und erst einige Zeit später Wiederum kommt dieselbe Entität A nur an, um beispielsweise von der verwalteten Thread-ID = 2 behandelt zu werden . Dies führt zu:
System.InvalidOperationException: 'Ein Entitätsobjekt kann nicht von mehreren Instanzen von IEntityChangeTracker referenziert werden.'
weil DataContext von der verwalteten Thread-ID = 1 bereits Entität A verfolgt (zumindest denke ich, dass dies so ist)
Meine Frage ist, wie ich DataContext in meinem Fall zwischenspeichern kann. Hattest du das gleiche Problem? Ich habe dies und diese Antworten gelesen und nach meinem Verständnis ist die Verwendung eines statischen DataContext ebenfalls keine Option. (2)
- Haftungsausschluss: Ich hätte sagen sollen, dass wir die Anwendung geerbt haben und ich kann nicht antworten, warum sie so implementiert wurde.
- Haftungsausschluss 2: Ich habe wenig bis gar keine Erfahrung mit EF.
Die Gemeinschaft stellte Fragen:
- Welche Version von EF verwenden wir? 5.0
- Warum leben Entitäten länger als der Kontext? - Sie tun es nicht, aber vielleicht fragen Sie sich, warum Entitäten länger leben müssen als der Kontext. Ich verwende Repositorys, die zwischengespeicherten DataContext verwenden, um Entitäten aus der Datenbank abzurufen und sie in einer In-Memory-Sammlung zu speichern, die ich als Cache verwende.
Auf diese Weise werden Entitäten "extrahiert", wobei DatabaseDataContext
sich der zwischengespeicherte DataContext befindet, über den ich spreche (BLOB mit ganzen Datenbanksätzen im Inneren).
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;
}
Immer wenn meine Verbraucheranwendung eine AMQP-Nachricht empfängt, beginnt mein Verantwortungskettenmuster zu prüfen, ob diese Nachricht und ihre Daten, die ich bereits verarbeitet habe. Ich habe also eine Methode, die so aussieht:
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
}
Ich dachte, dass das Abrufen von Entitäten aus der Datenbank mit einer AsNoTracking()
Methode oder das Trennen von Entitäten jedes Mal, wenn ich eine Entität "aktualisiere" oder "einfüge", dies lösen würde, aber dies war nicht der Fall.
SaveChanges
.