A Changeset<T> is an immutable description of an attempted insert or update.
It contains the original input, successfully converted changes, and any errors
found along the way.
untrusted input
|
v
Cast -----> cast errors
|
v
Validate ----> validation errors
|
v
Inspect
|
v
Apply / ToResult
Each validation call returns either the same instance or a copied record with additional errors. Your model remains untouched until the final apply step.
| Member | Meaning |
|---|---|
Data |
The existing model for an update, or null for an insert |
Params |
A snapshot of the original input dictionary |
Changes |
Successfully cast values that differ from the existing model |
CastFields |
Property names represented in Changes after casting |
Errors |
Cast and validation errors accumulated so far |
Action |
ChangesetAction.Insert or ChangesetAction.Update |
IsValid |
true exactly when Errors is empty |
For nested associations, a value in Changes can itself be a child changeset.
An insert starts without existing data:
var insert = Changeset<User>.Cast(parameters, user => new { user.Name });
insert.Action == ChangesetAction.Insert;
insert.Data is null;An update holds the existing object and drops successfully cast values that are equal to the current property value:
var update = Changeset<User>.Cast(
existing,
parameters,
user => new { user.Name });
update.Action == ChangesetAction.Update;
ReferenceEquals(update.Data, existing);This lets validators and EF Core integration focus on actual changes.
Changesets are records with immutable collections. Adding an error produces a new logical value:
var invalid = changeset.AddError(
"Email",
"is already registered",
"uniqueness");
changeset.IsValid; // remains unchanged
invalid.IsValid; // falseThe Data object itself is not deep-cloned when the changeset is created.
Treat the existing model and values stored in Params as application-owned
objects; changeset immutability does not recursively freeze them.
Bad user input produces errors with a field, message, stable code, and optional metadata:
foreach (var error in changeset.Errors)
{
Console.WriteLine(error.Field);
Console.WriteLine(error.Message);
Console.WriteLine(error.Code);
Console.WriteLine(error.Metadata);
}This applies to conversion and validation failures. API misuse can still throw:
invalid field-selector expressions throw ArgumentException, null arguments
can throw ArgumentNullException, and applying an invalid changeset throws
InvalidOperationException.
Use IsValid when ordinary control flow is clearest:
if (!changeset.IsValid)
return errors;
var model = changeset.ApplyChanges();Use ToResult when callers benefit from an explicit discriminated result:
return changeset.ToResult() switch
{
ChangesetResult<User>.Valid valid => Save(valid.Value),
ChangesetResult<User>.Invalid invalid => Reject(invalid.Errors),
_ => throw new UnreachableException()
};Read Errors for inspection helpers and Applying Changes for materialization semantics.