Table of Contents

Unique property constraint

Adorn a property with [Unique] to enforce that the value of that property is unique across every event of this type in the event store.

Chronicle discovers [Unique]-adorned properties automatically and registers the constraints with the Kernel when the client starts.

Defining a unique property constraint

[EventType]
public record ProjectCreated([Unique] string Name, string Description);

Grouping constraints across event types

When multiple event types share the same constraint name, Chronicle groups them into a single constraint. A value introduced by any of the participating events is checked against all others:

[EventType]
public record UserRegistered([Unique(name: "UniqueEmail")] string Email, string DisplayName);

[EventType]
public record UserEmailChanged([Unique(name: "UniqueEmail")] string NewEmail);

Both events now participate in the same UniqueEmail constraint, so neither UserRegistered nor UserEmailChanged can introduce an email address that already exists.

Violation message

The message parameter is optional. Chronicle produces a default violation message when one is not supplied:

[EventType]
public record ProjectCreated([Unique(message: "A project with this name already exists.")] string Name, string Description);

Releasing a constraint

Apply [RemoveConstraint] to the event type that signals a domain object has been removed. When this event is appended, Chronicle releases the named constraint and its previously held values can be claimed again:

[EventType]
[RemoveConstraint("UniqueEmail")]
public record UserRemoved(UserId UserId);

The constraint name must exactly match the name used in the [Unique] attribute that established it.

An event type can release more than one constraint by stacking multiple attributes:

[EventType]
[RemoveConstraint("UniqueEmail")]
[RemoveConstraint("UniqueUsername")]
public record UserRemoved(UserId UserId);

How constraints are enforced

The Chronicle Kernel reads the [Unique] and [RemoveConstraint] attributes when the client connects and creates the indexes it needs. Every subsequent append is checked against those indexes server-side, so no constraint logic runs in client code.