Baustein 5: EventApplier (Inbound Apply) + Loop-Prevention (Kapitel 10)

Die zentrale, bisher komplett fehlende Luecke: selbst mit Baustein
1-4 haette SyncEngine.PullAsync empfangene Ereignisse nur zur
Konflikterkennung genutzt, nie in die lokale LiteDB geschrieben -
ankommende Aenderungen von anderen Geraeten waeren nirgends sichtbar
geworden.

Neu EventApplier: entschluesselt, dispatcht ueber eine explizite
EntityType-Tabelle, schreibt IMMER direkt auf die rohe LiteDB-
Collection, nie ueber eine Repository-Save/Delete-Methode - sonst
wuerde der OnChange-Hook (Baustein 2) die gerade angewendete Aenderung
als neues ausgehendes Ereignis re-enqueuen (Sync-Ping-Pong). Ein
gemeinsames Suppress-Flag wurde geprueft und verworfen: SyncEngine
laeuft per Timer nebenlaeufig zum UI-Thread, ein Flag koennte einen
echten Nutzer-Save waehrenddessen verschlucken. Der direkte Collection-
Zugriff ist zustandslos und dadurch korrekt. Kaskaden-Faelle nutzen
dieselben internen LiteDbContext-Hilfsmethoden wie die Repositories
(Baustein 4).

Neu SyncEventPublisher, der den OnChange-Hook in ein verschluesseltes
EventQueue.Enqueue uebersetzt (an LiteDbContext.OnChange gehaengt).

Mit dediziertem Loop-Prevention-Test abgesichert: belegt mit echtem
LiteDbContext + OnChange-Zaehler, dass Apply keinen neuen Hook-Aufruf
ausloest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 11:27:12 +02:00
co-authored by Claude Sonnet 5
parent 1240a2cd8a
commit ce4dfb0197
6 changed files with 305 additions and 3 deletions
+15 -1
View File
@@ -178,9 +178,15 @@ public static class AppBootstrapper
if (!string.IsNullOrEmpty(serverUrl))
{
services.AddSingleton(sp => new EventApplier(
sp.GetRequiredService<LiteDbContext>(), sp.GetRequiredService<byte[]>()));
services.AddSingleton(sp => new SyncEventPublisher(
sp.GetRequiredService<EventQueue>(), deviceId, sp.GetRequiredService<byte[]>()));
services.AddSingleton<SyncEngine>(sp => new SyncEngine(
sp.GetRequiredService<EventQueue>(),
sp.GetRequiredService<ConflictResolver>(),
sp.GetRequiredService<EventApplier>(),
BuildHttp(serverUrl, appData),
new SyncConfig
{
@@ -223,7 +229,15 @@ public static class AppBootstrapper
services.AddTransient<AddGroupDialogViewModel>();
services.AddTransient<SettingsViewModel>();
return services.BuildServiceProvider();
var provider = services.BuildServiceProvider();
// Sync-agnostischer Hook auf LiteDbContext (siehe LiteDbContext.OnChange) wird erst hier,
// außerhalb der Repository-Registrierung, mit der tatsächlichen Sync-Logik verbunden.
if (!string.IsNullOrEmpty(serverUrl))
provider.GetRequiredService<LiteDbContext>().OnChange =
provider.GetRequiredService<SyncEventPublisher>().Publish;
return provider;
}
// ── Hilfsmethoden ─────────────────────────────────────────────────────────