Skip to content

Entities

Mikołaj Milewski edited this page Oct 27, 2026 · 1 revision

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.

Define and register an Entity

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 member guides

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.

Initializing an Entity

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.

Interacting with Entities

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.

Mutate the Entity

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.

Query and watch projections

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.

Read and write fields

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.

Common Behavior operations

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.

Handling Entity Exceptions

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.

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

Clone this wiki locally