-
Notifications
You must be signed in to change notification settings - Fork 1
Description
What
We want changes to be able to dispatch other changes. For example deletes:
public class DeleteEntryChange(Guid entityId) : EditChange<Entry>(entityId), IPolyType
{
public static string TypeName => "delete:Entry";
public override async ValueTask ApplyChange(Entry entry, IChangeContext context)
{
entry.DeletedAt = context.Commit.DateTime;
await foreach (var obj in context.GetObjectsReferencing(EntityId, includeDeleted: false))
{
context.DispatchChange(new RemoveReferenceChange(obj));
}
}
}or when we want to update objects which contain a copy of data
public class UpdateHeadwordChange(Guid entityId, string headword) : EditChange<Entry>(entityId), IPolyType
{
public static string TypeName => "update:Entry:Headword";
public override async ValueTask ApplyChange(Entry entry, IChangeContext context)
{
entry.Headword = headword;
await foreach(var complexForm in context.GetObjectsReferencing(entry.Id).OfType<ComplexForm>())
{
context.DispatchChange(new UpdateComplexFormChange(complexForm.Id, headword));
}
}
}this would also be used for data migrations, maybe previously a field was owned by an object, but you want to move that field to a different object, however you have old changes which reference the old object, you can now dispatch a change to the correct object.
Why
- simplify delete handling code. Much of our current delete code could be moved into 2 dedicated types, one for a delete and one for removing a reference.
- allow updating derived data, eg writing system exemplars, or complex forms.
- data migrations, right now if you want to move a field from one object to another there's no way to write a change which allows that because a change can only modify the object it references in it's EntityId.
How
Add a new API to IChangeContext:
harmony/src/SIL.Harmony.Core/IChangeContext.cs
Lines 3 to 18 in 5a660d1
| public interface IChangeContext | |
| { | |
| public CommitBase Commit { get; } | |
| ValueTask<IObjectSnapshot?> GetSnapshot(Guid entityId); | |
| public async ValueTask<T?> GetCurrent<T>(Guid entityId) where T : class | |
| { | |
| var snapshot = await GetSnapshot(entityId); | |
| if (snapshot is null) return null; | |
| return (T) snapshot.Entity.DbObject; | |
| } | |
| public async ValueTask<bool> IsObjectDeleted(Guid entityId) => (await GetSnapshot(entityId))?.EntityIsDeleted ?? true; | |
| internal IObjectBase Adapt(object obj); | |
| IAsyncEnumerable<object> GetObjectsReferencing(Guid entityId, bool includeDeleted = false); | |
| IAsyncEnumerable<T> GetObjectsOfType<T>(string jsonTypeName, bool includeDeleted = false) where T : class; | |
| } |
void DispatchChanges(params Span<IChange> changes);this would require a lot of changes to SnapshotWorker, after a change was applied we need to gather the changes it dispatched and apply them. Much of the code in MarkDeleted will prove a useful model I suspect, however MarkDeleted itself should go away entirely as it will be handled via changes now instead.