From e758fbebda4e0f1aba4656ea64b7cda991fde2d2 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 14 Apr 2025 17:29:46 +0700 Subject: [PATCH 1/2] rename UpdateSnapshotsByDeferredCommits to FlushDeferredCommits. Add an index to EntityId on the snapshot table. --- src/SIL.Harmony/DataModel.cs | 8 ++++---- src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SIL.Harmony/DataModel.cs b/src/SIL.Harmony/DataModel.cs index d32e9fa..c0c0915 100644 --- a/src/SIL.Harmony/DataModel.cs +++ b/src/SIL.Harmony/DataModel.cs @@ -103,7 +103,7 @@ private async Task Add(Commit commit, bool deferSnapshotUpdates) if (!deferSnapshotUpdates) { //if there are deferred commits, update snapshots with them first - if (_deferredCommits is not []) await UpdateSnapshotsByDeferredCommits(); + if (_deferredCommits is not []) await FlushDeferredCommits(); await UpdateSnapshots(commit, [commit]); if (AlwaysValidate) await ValidateCommits(); @@ -118,10 +118,10 @@ private async Task Add(Commit commit, bool deferSnapshotUpdates) public async ValueTask DisposeAsync() { if (_deferredCommits is []) return; - await UpdateSnapshotsByDeferredCommits(); + await FlushDeferredCommits(); } - private async Task UpdateSnapshotsByDeferredCommits() + public async Task FlushDeferredCommits() { var commits = Interlocked.Exchange(ref _deferredCommits, []); var oldestChange = commits.MinBy(c => c.CompareKey); @@ -153,7 +153,7 @@ async Task ISyncable.AddRangeFromSync(IEnumerable commits) await using var transaction = await _crdtRepository.BeginTransactionAsync(); //if there are deferred commits, update snapshots with them first - if (_deferredCommits is not []) await UpdateSnapshotsByDeferredCommits(); + if (_deferredCommits is not []) await FlushDeferredCommits(); //don't save since UpdateSnapshots will also modify newCommits with hashes, so changes will be saved once that's done await _crdtRepository.AddCommits(newCommits, false); await UpdateSnapshots(oldestChange, newCommits); diff --git a/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs b/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs index 13c9ac8..8febc27 100644 --- a/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs +++ b/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs @@ -12,6 +12,7 @@ public void Configure(EntityTypeBuilder builder) builder.ToTable("Snapshots"); builder.HasKey(s => s.Id); builder.HasIndex(s => new { s.CommitId, s.EntityId }).IsUnique(); + builder.HasIndex(s => s.EntityId); builder .HasOne(s => s.Commit) .WithMany(c => c.Snapshots) From 57ba93355c0fcfd5c55ad88aacfb92e7bae5564e Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 16 Apr 2025 15:16:20 +0700 Subject: [PATCH 2/2] enable using a compiled model --- .../Db/EntityConfig/ChangeEntityConfig.cs | 38 +++++++++++++++---- .../Db/EntityConfig/CommitEntityConfig.cs | 17 ++++++++- .../Db/EntityConfig/SnapshotEntityConfig.cs | 27 ++++++++++--- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/SIL.Harmony/Db/EntityConfig/ChangeEntityConfig.cs b/src/SIL.Harmony/Db/EntityConfig/ChangeEntityConfig.cs index 306c854..fa3197c 100644 --- a/src/SIL.Harmony/Db/EntityConfig/ChangeEntityConfig.cs +++ b/src/SIL.Harmony/Db/EntityConfig/ChangeEntityConfig.cs @@ -6,23 +6,45 @@ namespace SIL.Harmony.Db.EntityConfig; -public class ChangeEntityConfig(JsonSerializerOptions jsonSerializerOptions) : IEntityTypeConfiguration> +public class ChangeEntityConfig(JsonSerializerOptions jsonSerializerOptions) + : IEntityTypeConfiguration> { + public ChangeEntityConfig() : this(JsonSerializerOptions.Default) + { + } + public void Configure(EntityTypeBuilder> builder) { builder.ToTable("ChangeEntities"); builder.HasKey(c => new { c.CommitId, c.Index }); - builder.Property(c => c.Change) - .HasColumnType("jsonb") - .HasConversion( - change => JsonSerializer.Serialize(change, jsonSerializerOptions), - json => DeserializeChange(json) - ); + var changeProperty = builder.Property(c => c.Change) + .HasColumnType("jsonb"); + if (EF.IsDesignTime) + { + changeProperty + .HasConversion( + change => SerializeChange(change, null), + json => DeserializeChange(json, null) + ); + } + else + { + changeProperty + .HasConversion( + change => SerializeChange(change, jsonSerializerOptions), + json => DeserializeChange(json, jsonSerializerOptions) + ); + } } - private IChange DeserializeChange(string json) + public static IChange DeserializeChange(string json, JsonSerializerOptions? jsonSerializerOptions) { return JsonSerializer.Deserialize(json, jsonSerializerOptions) ?? throw new SerializationException("Could not deserialize Change: " + json); } + + public static string SerializeChange(IChange change, JsonSerializerOptions? jsonSerializerOptions) + { + return JsonSerializer.Serialize(change, jsonSerializerOptions); + } } diff --git a/src/SIL.Harmony/Db/EntityConfig/CommitEntityConfig.cs b/src/SIL.Harmony/Db/EntityConfig/CommitEntityConfig.cs index 9694bb5..f68002a 100644 --- a/src/SIL.Harmony/Db/EntityConfig/CommitEntityConfig.cs +++ b/src/SIL.Harmony/Db/EntityConfig/CommitEntityConfig.cs @@ -1,6 +1,8 @@ +using System.Runtime.Serialization; using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using SIL.Harmony.Changes; namespace SIL.Harmony.Db.EntityConfig; @@ -24,11 +26,22 @@ public void Configure(EntityTypeBuilder builder) builder.Property(c => c.Metadata) .HasColumnType("jsonb") .HasConversion( - m => JsonSerializer.Serialize(m, (JsonSerializerOptions?)null), - json => JsonSerializer.Deserialize(json, (JsonSerializerOptions?)null) ?? new() + m => Serialize(m, null), + json => Deserialize(json, null) ?? new() ); builder.HasMany(c => c.ChangeEntities) .WithOne() .HasForeignKey(c => c.CommitId); } + + public static CommitMetadata Deserialize(string json, JsonSerializerOptions? jsonSerializerOptions) + { + return JsonSerializer.Deserialize(json, jsonSerializerOptions) ?? + throw new SerializationException("Could not deserialize CommitMetadata: " + json); + } + + public static string Serialize(CommitMetadata commitMetadata, JsonSerializerOptions? jsonSerializerOptions) + { + return JsonSerializer.Serialize(commitMetadata, jsonSerializerOptions); + } } diff --git a/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs b/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs index 8febc27..08db942 100644 --- a/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs +++ b/src/SIL.Harmony/Db/EntityConfig/SnapshotEntityConfig.cs @@ -17,17 +17,32 @@ public void Configure(EntityTypeBuilder builder) .HasOne(s => s.Commit) .WithMany(c => c.Snapshots) .HasForeignKey(s => s.CommitId); - builder.Property(s => s.Entity) - .HasColumnType("jsonb") - .HasConversion( - entry => JsonSerializer.Serialize(entry, jsonSerializerOptions), - json => DeserializeObject(json) + var entityProperty = builder.Property(s => s.Entity) + .HasColumnType("jsonb"); + if (EF.IsDesignTime) + { + entityProperty.HasConversion( + entry => Serialize(entry, null), + json => DeserializeObject(json, null) ); + } + else + { + entityProperty.HasConversion( + entry => Serialize(entry, jsonSerializerOptions), + json => DeserializeObject(json, jsonSerializerOptions) + ); + } } - private IObjectBase DeserializeObject(string json) + public static IObjectBase DeserializeObject(string json, JsonSerializerOptions? jsonSerializerOptions) { return JsonSerializer.Deserialize(json, jsonSerializerOptions) ?? throw new SerializationException($"Could not deserialize Entry: {json}"); } + + public static string Serialize(IObjectBase change, JsonSerializerOptions? jsonSerializerOptions) + { + return JsonSerializer.Serialize(change, jsonSerializerOptions); + } }