Repository navigation
Entities
An Entity is a persistent, addressable Stateflows behavior for domain data and operations. Define its template as an interface whose members describe stored and computed fields, typed mutation handlers, and read-only projections.
Use an Entity when the main model is durable data changed through domain events and read through fields or projections. Use a State Machine when behavior depends on lifecycle states and transitions, or an Activity when work is best expressed as a token-processing graph. Entities share the common behavior hosting, identity, event delivery, and persistence model.
Entity templates are interfaces. Attribute-based definitions use default interface implementations for computations, mutations, projections, and initializers:
using Stateflows.Entities.Attributes;
public sealed record RenameCustomer(string Name);
public sealed record CustomerView(string Name, int NameLength);
public interface ICustomer
{
[Field]
string Name { get; set; }
[Field]
int NameLength => Name.Length;
[Mutation]
void Rename(RenameCustomer mutation) => Name = mutation.Name;
[Projection]
CustomerView View => new(Name, NameLength);
}Register the template with AddEntity<TTemplate>:
builder.Services.AddStateflows(stateflows => stateflows
.AddEntities(entities => entities
.AddEntity<ICustomer>("Customer")));The entity name is its behavior class name. If omitted, Stateflows derives one from the template type. Entity templates can also be configured with AddField, AddMutation, and AddProjection on the entity builder. See Interacting with Entities for locating and using Entity behavior handles.
Entity templates are runtime proxies, not constructor-injected service classes. Typed Entity observers, interceptors, and exception handlers can inject application services and common behavior/execution contexts, including available parent/owner contexts. This does not add arbitrary DI parameters to template mutation or projection methods. See Dependency Injection for the distinction and Entity Fields for injecting Entity data into other runtime elements.
| Member | Purpose | Guide |
|---|---|---|
| Field | Stored state, optionally computed and selectively accessible directly | Fields and computed fields |
| Mutation | Change stored state in response to a typed event | Mutations |
| Projection | Produce read models, query them, and optionally publish changed values | Projections and publish scopes |
An initializer sets initial Entity state; it is not a mutation. See Initializing an Entity. Entities also support observers, interceptors, and exception handlers; see Observability and Handling Entity Exceptions.
When an Entity belongs to a coordinated group of behaviors, projection publication can target the Entity itself, its parent, or its owner. This is explained in Projection publish scopes and Behavioral Groups.
Use a default initializer for parameterless initialization, or an event-specific initializer when creation requires a payload. Both are default interface methods returning void; the default form has no parameters and the event-specific form takes one event:
using Stateflows.Entities.Attributes;
public sealed record CreateCustomer(string Name);
public interface ICustomer
{
[Field]
string Name { get; set; }
[DefaultInitializer]
void InitializeDefault() => Name = "Unnamed";
[Initializer]
void Initialize(CreateCustomer creation) => Name = creation.Name;
}This is an alternative complete template, not another declaration to append to the earlier ICustomer. Register it with the same AddEntity<ICustomer>("Customer") call.
For a fresh instance, SendAsync(new CreateCustomer("Ada")) invokes the matching event-specific initializer and returns EventStatus.Initialized on success. Explicit Initialize or the default initialization path invokes default initializers instead. Event-specific initialization does not additionally execute the default initializer; establish all required starting values in the selected path. Stored field defaults are applied before either path, then computed fields and projections are initialized.
Initializers run only while the Entity is not initialized. They are not replacement mutation handlers for an existing instance; use a separate mutation event for later changes. Initialization failure does not commit partial state. Default projection-cache population does not itself publish projection-change notifications.
IEntityLocator locates an Entity behavior handle by its name and instance:
using Stateflows;
using Stateflows.Entities;
if (!entityLocator.TryLocateEntity(
new EntityId("Customer", "customer-42"), out var entity))
{
throw new InvalidOperationException("Customer 'customer-42' could not be located.");
}EntityId contains the Entity name and string instance ID. Entity handles implement IEntityBehavior, which extends the common IBehavior API. The handle is a communication endpoint, not the Entity's in-memory template object; interact with it using events and requests.
Send a mutation event using SendAsync, or use TryMutateAsync when a consumed/not-consumed result is sufficient:
bool consumed = await entity.TryMutateAsync(new RenameCustomer("Ada"));Use SendAsync instead when the caller needs the full SendResult, including the event status.
TryMutateAsync<TEvent> returns true when the event's status is Consumed. A failed or undelivered event is not reported as consumed. A mutation is registered for its exact event type.
See Entity Mutations for registration, event contracts, and reactive field updates.
Use TryGetProjectionAsync<TProjection> to request a projection value:
var (success, view) = await entity.TryGetProjectionAsync<CustomerView>();
if (success)
{
Console.WriteLine(view.Name);
}The Entity template's projection property determines the result type and contents. To receive updates, use WatchProjectionAsync<TProjection>:
await using var watcher = await entity.WatchProjectionAsync<CustomerView>(
view => Console.WriteLine(view.Name));For the Entity to publish projection changes to watchers, configure a non-None PublishScope on the projection. Self publishes from the Entity, while Parent and Owner publish through the corresponding behavior relationship when present.
See Entity Projections for dependency refresh and publish-scope semantics.
Fields are not externally readable or writable unless their access flags allow it. FieldAccess.Get enables external reads; FieldAccess.Set enables external writes. The default is FieldAccess.None. Mutations and initializers operate on the template internally and do not require external field access.
For example, an Entity template can expose a directly accessible field:
using Stateflows.Entities.Attributes;
public interface ICustomer
{
[Field(FieldAccess.Get | FieldAccess.Set)]
string DisplayName { get; set; }
}Read and write the field through the located handle:
var (found, name) = await entity.TryGetAsync<string>("DisplayName");
var written = await entity.TrySetAsync("DisplayName", "Ada");TryGetAsync<T> succeeds only when a field with that name and exact value type exists and allows reads. TrySetAsync<T> requires a matching writable field and value type. Both return a success flag; a false result means the operation was not consumed. A successful direct write also refreshes dependent computed fields and projections. Prefer mutation events when field changes need domain rules or side effects, because direct writes do not invoke mutation handlers.
See Entity Fields for computed fields, access control, direct field APIs, and [FromField] constructor injection.
As an IBehavior, an Entity handle also supports:
| Operation | Purpose |
|---|---|
SendAsync(event) |
Send an event and inspect its SendResult. |
RequestAsync<T>(request) |
Send a request expecting a typed response. |
GetStatusAsync() |
Request behavior status and expected events. |
WatchStatusAsync(handler) |
Observe status changes. |
ResetAsync(mode) |
Reset the behavior according to a reset mode. |
FinalizeAsync(mode) |
Request behavior finalization. |
WatchAsync(handler) |
Subscribe to published notifications. |
Watch methods return an IWatcher; dispose it when the subscription is no longer needed. Event statuses and lifecycle behavior are shared with the other Behavior types. See Messaging Patterns.
Register IEntityExceptionHandler implementations globally or for one Entity to observe failures during initialization, mutations, computed fields, and projections. Handlers classify exceptions but do not suppress the original failure or commit partial Entity state.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026
Overview
Installation
Runtimes
Orleans runtime
Concepts
Events and Structural Events
Messaging Patterns
Validation
Scheduling
Observability
Behaviors
Behavior Values
Dependency Injection
Behavioral Groups
State Machines
Concepts
State Identity
In-State Event Deferrals
Evaluation of Transitions
Exception Handling
Building blocks
Regions
States
State
Composite State
Orthogonal State
Final State
Pseudostates
Choice
Junction
Fork
Join
History
Transitions
Transition
Default Transition
Internal Transition
Activities
Concepts
Exception Handling
Implicit Fork and Join
Tokens
Building blocks
Nodes
Action Node
Decision Node
Merge Node
Initial Node
Final Node
Input Node
Output Node
Fork Node
Join Node
Accept Event Action Node
Send Event Action Node
Publish Event Action Node
Time Event Action Node
Data Store Node
Structured Activity Node
Iterative Activity Node
Parallel Activity Node
Flows
Data Flow
Control Flow
Actions
Entities
Fields
Mutations
Projections
Extensions
ClearScript
FluentValidation
JSON definitions
Minimal APIs
OneOf
OpenTelemetry
PlantUML
Scheduling