Skip to content

Commit 831faf0

Browse files
CopilotYunchuWang
andcommitted
Add InitializeStateAsync support for async entity state initialization
Co-authored-by: YunchuWang <[email protected]>
1 parent 60ce9f6 commit 831faf0

File tree

2 files changed

+109
-4
lines changed

2 files changed

+109
-4
lines changed

src/Abstractions/Entities/TaskEntity.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,25 +119,40 @@ public abstract class TaskEntity<TState> : ITaskEntity
119119
protected TaskEntityContext Context { get; private set; } = null!;
120120

121121
/// <inheritdoc/>
122-
public ValueTask<object?> RunAsync(TaskEntityOperation operation)
122+
public async ValueTask<object?> RunAsync(TaskEntityOperation operation)
123123
{
124124
Check.NotNull(operation);
125125
this.Context = operation.Context;
126126
object? state = operation.State.GetState(typeof(TState));
127-
this.State = state is null ? this.InitializeState(operation) : (TState)state;
127+
this.State = state is null ? await this.InitializeStateAsync(operation) : (TState)state;
128128
if (!operation.TryDispatch(this, out object? result, out Type returnType)
129129
&& !this.TryDispatchState(operation, out result, out returnType))
130130
{
131131
if (TryDispatchImplicit(operation, out ValueTask<object?> task))
132132
{
133133
// We do not go into UnwrapAsync for implicit tasks
134-
return task;
134+
return await task;
135135
}
136136

137137
throw new NotSupportedException($"No suitable method found for entity operation '{operation}'.");
138138
}
139139

140-
return TaskEntityHelpers.UnwrapAsync(operation.State, () => this.State, result, returnType);
140+
return await TaskEntityHelpers.UnwrapAsync(operation.State, () => this.State, result, returnType);
141+
}
142+
143+
/// <summary>
144+
/// Initializes the entity state asynchronously. This is only called when there is no current state for this entity.
145+
/// </summary>
146+
/// <param name="entityOperation">The entity operation to be executed.</param>
147+
/// <returns>A task that resolves to the entity state.</returns>
148+
/// <remarks>
149+
/// The default implementation calls <see cref="InitializeState"/> for backward compatibility.
150+
/// Override this method to perform async initialization operations, such as loading state from a database or
151+
/// external storage.
152+
/// </remarks>
153+
protected virtual ValueTask<TState> InitializeStateAsync(TaskEntityOperation entityOperation)
154+
{
155+
return new ValueTask<TState>(this.InitializeState(entityOperation));
141156
}
142157

143158
/// <summary>

test/Abstractions.Tests/Entities/StateTaskEntityTests.cs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,103 @@ public async Task ExplicitDelete_Overridden(string op)
172172
operation.State.GetState(typeof(TestState)).Should().BeOfType<TestState>().Which.Value.Should().Be(0);
173173
}
174174

175+
[Fact]
176+
public async Task AsyncInitializeState_Called()
177+
{
178+
TestEntityOperation operation = new("get0", new TestEntityState(null), default);
179+
AsyncInitEntity entity = new();
180+
181+
object? result = await entity.RunAsync(operation);
182+
183+
result.Should().BeOfType<int>().Which.Should().Be(42);
184+
entity.InitializeAsyncCalled.Should().BeTrue();
185+
}
186+
187+
[Fact]
188+
public async Task AsyncInitializeState_WithYield_Succeeds()
189+
{
190+
TestEntityOperation operation = new("get0", new TestEntityState(null), default);
191+
AsyncInitWithYieldEntity entity = new();
192+
193+
object? result = await entity.RunAsync(operation);
194+
195+
result.Should().BeOfType<int>().Which.Should().Be(99);
196+
entity.InitializeAsyncCalled.Should().BeTrue();
197+
}
198+
199+
[Fact]
200+
public async Task AsyncInitializeState_ValueTask_Succeeds()
201+
{
202+
TestEntityOperation operation = new("get0", new TestEntityState(null), default);
203+
AsyncInitValueTaskEntity entity = new();
204+
205+
object? result = await entity.RunAsync(operation);
206+
207+
result.Should().BeOfType<int>().Which.Should().Be(77);
208+
}
209+
210+
[Fact]
211+
public async Task SyncInitializeState_StillWorks()
212+
{
213+
TestEntityOperation operation = new("get0", new TestEntityState(null), default);
214+
SyncInitEntity entity = new();
215+
216+
object? result = await entity.RunAsync(operation);
217+
218+
result.Should().BeOfType<int>().Which.Should().Be(55);
219+
entity.SyncInitCalled.Should().BeTrue();
220+
}
221+
175222
static TestState State(int value) => new() { Value = value };
176223

177224
class NullStateEntity : TestEntity
178225
{
179226
protected override TestState InitializeState(TaskEntityOperation entityOperation) => null!;
180227
}
181228

229+
class AsyncInitEntity : TestEntity
230+
{
231+
public bool InitializeAsyncCalled { get; private set; }
232+
233+
protected override async ValueTask<TestState> InitializeStateAsync(TaskEntityOperation entityOperation)
234+
{
235+
await Task.CompletedTask;
236+
this.InitializeAsyncCalled = true;
237+
return new TestState { Value = 42 };
238+
}
239+
}
240+
241+
class AsyncInitWithYieldEntity : TestEntity
242+
{
243+
public bool InitializeAsyncCalled { get; private set; }
244+
245+
protected override async ValueTask<TestState> InitializeStateAsync(TaskEntityOperation entityOperation)
246+
{
247+
await Task.Yield();
248+
this.InitializeAsyncCalled = true;
249+
return new TestState { Value = 99 };
250+
}
251+
}
252+
253+
class AsyncInitValueTaskEntity : TestEntity
254+
{
255+
protected override ValueTask<TestState> InitializeStateAsync(TaskEntityOperation entityOperation)
256+
{
257+
return new ValueTask<TestState>(new TestState { Value = 77 });
258+
}
259+
}
260+
261+
class SyncInitEntity : TestEntity
262+
{
263+
public bool SyncInitCalled { get; private set; }
264+
265+
protected override TestState InitializeState(TaskEntityOperation entityOperation)
266+
{
267+
this.SyncInitCalled = true;
268+
return new TestState { Value = 55 };
269+
}
270+
}
271+
182272
class TestEntity : TaskEntity<TestState>
183273
{
184274
readonly bool allowStateDispatch;

0 commit comments

Comments
 (0)