diff --git a/src/Abstractions/Entities/TaskEntity.cs b/src/Abstractions/Entities/TaskEntity.cs
index bad549fa..c3c33d85 100644
--- a/src/Abstractions/Entities/TaskEntity.cs
+++ b/src/Abstractions/Entities/TaskEntity.cs
@@ -72,6 +72,14 @@ public interface ITaskEntity
/// Entity state will be hydrated into the property. See for
/// more information.
///
+///
+/// Implicit Operations
+///
+/// This class supports some operations implicitly. Implicit operations have the lowest priority, after entity and state
+/// method dispatching. To override an implicit operation, implement a public method of the same name. Throw
+/// from the method to indicate the implicit operation is not supported at all.
+///
+/// delete : deletes the entity state from storage.
///
public abstract class TaskEntity : ITaskEntity
{
@@ -120,6 +128,12 @@ public abstract class TaskEntity : ITaskEntity
if (!operation.TryDispatch(this, out object? result, out Type returnType)
&& !this.TryDispatchState(operation, out result, out returnType))
{
+ if (TryDispatchImplicit(operation, out ValueTask task))
+ {
+ // We do not go into UnwrapAsync for implicit tasks
+ return task;
+ }
+
throw new NotSupportedException($"No suitable method found for entity operation '{operation}'.");
}
@@ -144,6 +158,20 @@ protected virtual TState InitializeState()
return Activator.CreateInstance();
}
+ static bool TryDispatchImplicit(TaskEntityOperation operation, out ValueTask result)
+ {
+ // We do not implement implicit operations via methods because then they would supersede state-dispatching.
+ // As such, implicit operations are manually implemented here.
+ result = default;
+ if (string.Equals(operation.Name, "delete", StringComparison.OrdinalIgnoreCase))
+ {
+ operation.State.SetState(null);
+ return true;
+ }
+
+ return false;
+ }
+
bool TryDispatchState(TaskEntityOperation operation, out object? result, out Type returnType)
{
if (!this.AllowStateDispatch)
diff --git a/test/Abstractions.Tests/Entities/EntityTaskEntityTests.cs b/test/Abstractions.Tests/Entities/EntityTaskEntityTests.cs
index 2f37b03d..4ca908b4 100644
--- a/test/Abstractions.Tests/Entities/EntityTaskEntityTests.cs
+++ b/test/Abstractions.Tests/Entities/EntityTaskEntityTests.cs
@@ -125,6 +125,34 @@ public async Task DefaultValue_Input_Succeeds()
result.Should().BeOfType().Which.Should().Be("not-default");
}
+ [Theory]
+ [InlineData("delete")]
+ [InlineData("Delete")]
+ public async Task ImplicitDelete_ClearsState(string op)
+ {
+ TestEntityOperation operation = new(op, 10, default);
+ TestEntity entity = new();
+
+ object? result = await entity.RunAsync(operation);
+
+ result.Should().BeNull();
+ operation.State.GetState(typeof(object)).Should().BeNull();
+ }
+
+ [Theory]
+ [InlineData("delete")]
+ [InlineData("Delete")]
+ public async Task ExplicitDelete_Overridden(string op)
+ {
+ TestEntityOperation operation = new(op, 10, default);
+ DeleteEntity entity = new();
+
+ object? result = await entity.RunAsync(operation);
+
+ result.Should().BeNull();
+ operation.State.GetState(typeof(int)).Should().Be(0);
+ }
+
#pragma warning disable CA1822 // Mark members as static
#pragma warning disable IDE0060 // Remove unused parameter
class TestEntity : TaskEntity
@@ -215,6 +243,11 @@ int Get(Optional context)
return this.State;
}
}
+
+ class DeleteEntity : TaskEntity
+ {
+ public void Delete() => this.State = 0;
+ }
#pragma warning restore IDE0060 // Remove unused parameter
#pragma warning restore CA1822 // Mark members as static
}
diff --git a/test/Abstractions.Tests/Entities/StateTaskEntityTests.cs b/test/Abstractions.Tests/Entities/StateTaskEntityTests.cs
index 2250c925..e778ff20 100644
--- a/test/Abstractions.Tests/Entities/StateTaskEntityTests.cs
+++ b/test/Abstractions.Tests/Entities/StateTaskEntityTests.cs
@@ -158,6 +158,20 @@ public async Task DefaultValue_Input_Succeeds()
result.Should().BeOfType().Which.Should().Be("not-default");
}
+ [Theory]
+ [InlineData("delete")]
+ [InlineData("Delete")]
+ public async Task ExplicitDelete_Overridden(string op)
+ {
+ TestEntityOperation operation = new(op, State(10), default);
+ TestEntity entity = new();
+
+ object? result = await entity.RunAsync(operation);
+
+ result.Should().BeNull();
+ operation.State.GetState(typeof(TestState)).Should().BeOfType().Which.Value.Should().Be(0);
+ }
+
static TestState State(int value) => new() { Value = value };
class NullStateEntity : TestEntity
@@ -187,6 +201,8 @@ class TestState
public static string StaticMethod() => throw new NotImplementedException();
+ public void Delete() => this.Value = 0;
+
public int Precedence() => 10;
public int Add0(int value) => this.Add(value, default);