Project.Apply#

Writes edits into the accepted state of a Project and marks it unsolved. It doesn't schedule anything: follow it with Solve(null, …) or Level to get the new dates.

Signature#

public ApplyReport Apply(SchedulingChanges changes)

Synchronous and CPU-only. Changes the project's state, so don't call it while another thread uses the same Project.

Parameters#

Name Type Description
changes SchedulingChanges The edits to commit

Behavior#

  • Operations are ordered and partially accepted. A rejected operation goes into ApplyReport.Rejected and isn't thrown.
  • Any rejection sets NeedsResync. After that, Solve(null, …), Apply and Level throw until a full Solve(data, …).
  • The state is unsolved afterwards, so Preview, LoadProfile, Evaluate, SuggestAssignments and Roster need a solve first.
  • The next acyclic Solve(null, …) or Level reports the tasks that successful operations added or updated.
  • When Apply throws, nothing is applied and the project keeps its state.

Returns#

An ApplyReport with the applied and rejected operations and their side effects.

Errors#

Error When
ProjectException with Code = ProjectError.NeedsResync An earlier Apply rejected an operation and no Solve(data, …) followed. Checked before changes.BaselineId, so it wins over StaleBaseline
ProjectException with Code = ProjectError.StaleBaseline changes.BaselineId is set and doesn't name the current baseline. After an Apply with no solve since, the project has no baseline, so any non-null value fails
ArgumentNullException changes is null

Example#

Commit an edit, resync with full data when part of it is rejected, and reschedule.

using Plen;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");
var data = new ProjectData
{
    Tasks = [new TaskData(1) { StartDate = monday, Duration = 2.0 }],
};

var project = new Project();
var solved = project.Solve(data, new SolveOptions());

var changes = new SchedulingChanges { BaselineId = solved.BaselineId };
changes.Tasks.Update.Add(new TaskUpdate(1) { Duration = 3.0 });

var report = project.Apply(changes);
foreach (var rejected in report.Rejected)
{
    Console.WriteLine($"rejected at {rejected.Path}: {rejected.Message}");
}

var result = report.NeedsResync
    ? project.Solve(data, new SolveOptions())
    : project.Solve(null, new SolveOptions());
Console.WriteLine($"{result.Updates.Count} task(s) moved");