[Canonical page](<https://docs.dhtmlx.com/planning-engine/net/live-projects/editing-a-project/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Editing a Project

This guide covers how to change a live [`Project`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/project.md>) as the user works: adding, updating and removing tasks, links, resources and assignments, rescheduling after the edit, and recovering when part of an edit is refused.

## How editing works

A [`Project`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/project.md>) holds one accepted state: the data from the last [`Solve(data, …)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) plus every edit since, with the dates the last solve accepted. You don't send the whole plan again for each change. You describe the change as a [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md>) object and pass it to [`Project.Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>).

Editing takes two separate calls:

1. `Apply(changes)` writes the edit into the accepted state and marks the project unsolved. It doesn't schedule anything.
2. `Solve(null, options)` schedules the edited state, accepts the new dates and returns what moved.

Between the two calls the project has no baseline. [`Preview`](<https://docs.dhtmlx.com/planning-engine/net/api/project-preview.md>), [`LoadProfile`](<https://docs.dhtmlx.com/planning-engine/net/api/project-load-profile.md>), [`Evaluate`](<https://docs.dhtmlx.com/planning-engine/net/api/project-evaluate.md>), [`SuggestAssignments`](<https://docs.dhtmlx.com/planning-engine/net/api/project-suggest-assignments.md>) and [`Roster`](<https://docs.dhtmlx.com/planning-engine/net/api/project-roster.md>) throw [`ProjectException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/project-exception.md>) with `Code == ProjectError.NoBaseline` until you solve. You can call `Apply` several times before one `Solve`, and the edits accumulate.

`Apply` accepts what it can. Each operation in the change set is applied or rejected on its own. A rejection doesn't throw: it's listed in the returned [`ApplyReport`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md>) and sets [`NeedsResync`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#needsresync>). From then on, `Solve(null, …)`, `Apply` and [`Level`](<https://docs.dhtmlx.com/planning-engine/net/api/project-level.md>) throw `ProjectException` with [`ProjectError.NeedsResync`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/project-exception.md#projecterror>). Your model and the project's state may now disagree, and the only way back is to send the full data with `Solve(data, …)`.

When `Apply` itself throws, the call didn't fit the project's state: `NeedsResync` from an earlier rejection, or `StaleBaseline` when the change set names a baseline that isn't current anymore. In both cases nothing was applied. See [Error handling](<https://docs.dhtmlx.com/planning-engine/net/error-handling.md>).

All calls are synchronous. A `Project` isn't thread-safe, so let one thread at a time edit a shared instance.

## Change sets

A [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md>) groups the edits by record kind. Each group is a [`Changes<TAdd, TUpdate>`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/changes.md>) with three lists: `Add`, `Update` and `Remove`. All of them start empty.

| Group | `Add` | `Update` | `Remove` |
|---|---|---|---|
| `Tasks` | [`TaskData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/task-data.md>) | [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/task-update.md>) | task [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) |
| `Links` | [`LinkAdd`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-add.md>) | [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md>) | link `Id` |
| `Resources` | [`ResourceData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/resource-data.md>) | [`ResourceUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/resource-update.md>) | resource `Id` |
| `Assignments` | [`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/assignment-data.md>) | [`AssignmentUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/assignment-update.md>) | assignment `Id` |

Additions use the same record types as [`ProjectData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/project-data.md>), except links: `LinkAdd` has a nullable `Id`. Leave a link or assignment `Id` as `null` and [`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) generates one (`$generated_link_1`, `$generated_assignment_1`, ...). Generated ids come back in [`ApplyReport.Effects`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#effects>), so you can store them in your model.

```csharp
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 },
        new TaskData(2) { StartDate = monday, Duration = 3.0 },
    ],
    Links = [new LinkData { Id = 1, Source = 1, Target = 2 }],
};
var project = new Project();
var options = new SolveOptions();
project.Solve(data, options);

var changes = new SchedulingChanges();
// A new task, linked after task 2. The link has no id: Apply generates one.
changes.Tasks.Add.Add(new TaskData(3) { StartDate = monday, Duration = 1.0 });
changes.Links.Add.Add(new LinkAdd { Source = 2, Target = 3 });
// Task 1 now takes three days.
changes.Tasks.Update.Add(new TaskUpdate(1) { Duration = 3.0 });

var report = project.Apply(changes);
foreach (var op in report.Applied)
    Console.WriteLine($"applied {op.Path}");
Console.WriteLine($"generated links: {string.Join(", ", report.Effects.GeneratedLinks)}");

var result = project.Solve(null, options);
foreach (var update in result.Updates)
    Console.WriteLine($"task {update.Id}: {update.StartDate} .. {update.EndDate}");
```

The report lists `tasks.add[0]`, `tasks.update[0]` and `links.add[0]`, and the new link is `$generated_link_1`. After the solve, task 1 runs until `2026-08-13 08:00`, task 2 moves to `2026-08-13 08:00 .. 2026-08-18 08:00`, and task 3 starts when task 2 ends.

Operations don't run in the order you added them. `Apply` runs them in a fixed order, so every reference comes after the adds it may need and every removal comes before the edits it could break:

1. tasks: remove, add, update
2. links: remove, update, add
3. resources: remove, add, update
4. assignments: remove, update, add

An operation is named by its group, list and index, such as `tasks.update[0]`. The same path shows up in [`ApplyReport.Applied`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#applied>) and [`ApplyReport.Rejected`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#rejected>).

## Update records and Patch

Update records ([`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/task-update.md>), [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md>), [`ResourceUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/resource-update.md>), [`AssignmentUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/assignment-update.md>)) name the record by [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) and carry every other field as a [`Patch<T>`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md>). A patch has three states:

- [`Patch<T>.Missing`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#missing>) (the default) leaves the field as it is.
- [`Patch<T>.Of(value)`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#of>) sets it. A plain value converts implicitly, so `Duration = 3.0` works.
- [`Patch<T>.Null`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#null>) clears an optional field, or restores the default of a field that always has a value: `AutoScheduling` goes back to `true`, `Unscheduled` to `false`, `Progress` to `0`, `Priority` to `500`, `LevelingDelay` to `0`, `Pauses` to empty, `LinkType` to `Fs` and `Lag` to `0`.

[`new TaskUpdate(id)`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/task-update.md>) and the other update constructors leave every field `Missing`, so you only touch the fields you set in the initializer.

```csharp
using Plen;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");
var thursday = Moment.FromYmdHm(2026, 8, 13, 8, 0) ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks = [new TaskData("review") { StartDate = monday, Duration = 1.0 }],
};
data.Config.ApplyConstraints = true;

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

// A value sets a field: "start no earlier than Thursday".
var pin = new SchedulingChanges();
pin.Tasks.Update.Add(new TaskUpdate("review")
{
    ConstraintType = ConstraintType.Snet,
    ConstraintDate = thursday,
});
project.Apply(pin);
foreach (var update in project.Solve(null, options).Updates)
    Console.WriteLine($"pinned: {update.Id} starts {update.StartDate}");

// Null clears the constraint; StartDate moves the task back.
// Every field left Missing keeps its value.
var unpin = new SchedulingChanges();
unpin.Tasks.Update.Add(new TaskUpdate("review")
{
    ConstraintType = Patch<ConstraintType>.Null,
    ConstraintDate = Patch<Moment>.Null,
    StartDate = monday,
});
project.Apply(unpin);
foreach (var update in project.Solve(null, options).Updates)
    Console.WriteLine($"unpinned: {update.Id} starts {update.StartDate}");
```

The first solve moves the task to `2026-08-13 08:00`. The second puts it back on Monday. Clearing the constraint alone wouldn't do that: the accepted state already holds the Thursday dates, so the update also sets `StartDate`.

An `Id` patch takes one implicit conversion only, so `Parent = "phase"` doesn't compile. Write `Parent = Id.Str("phase")` or `Patch<Id>.Of("phase")`.

### Which dates a task update keeps

A task has three linked date fields: `StartDate`, `EndDate` and `Duration`. When an update sets some of them, [`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) keeps what you sent and derives the rest:

- `StartDate` alone keeps the duration and recomputes the end (a drag).
- `EndDate` alone keeps the start and recomputes the duration (a resize from the right edge).
- `Duration` alone keeps the start and recomputes the end.
- `StartDate` and `EndDate` together recompute the duration.
- A new `CalendarId`, or a new `Parent` while calendars are inherited ([`CalendarMode.TaskInherited`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/calendar-mode.md>)), keeps the start and duration and recomputes the end on the new calendar. Under an inherited calendar, the descendants that inherit it re-derive their ends too.

An explicit `StartDate` that falls on non-working time is kept as sent when the task has no predecessor. The end is computed forward from it on the working calendar, so only the start sits outside working time. Snap the date in your UI if you don't want that.

### Feeding results back

A [`Mutation`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/mutation.md>) from a result uses the same field names as [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/task-update.md>), so a result entry maps field by field onto an update. That's useful when you want to commit dates you got from [`Preview`](<https://docs.dhtmlx.com/planning-engine/net/api/project-preview.md>) or a leveled clone:

```csharp
using Plen;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");
var wednesday = Moment.FromYmdHm(2026, 8, 12, 8, 0) ?? throw new InvalidOperationException("invalid date");

var project = new Project();
project.Solve(
    new ProjectData
    {
        Tasks =
        [
            new TaskData(1) { StartDate = monday, Duration = 2.0 },
            new TaskData(2) { StartDate = monday, Duration = 3.0 },
        ],
        Links = [new LinkData { Id = 1, Source = 1, Target = 2 }],
    },
    new SolveOptions());

// The user drags task 1 to Wednesday and sees the preview.
var drag = new SchedulingChanges();
drag.Tasks.Update.Add(new TaskUpdate(1) { StartDate = wednesday });
var preview = project.Preview(drag, new PreviewOptions());

// They confirm: commit the previewed dates as they are.
var commit = new SchedulingChanges { BaselineId = preview.BaselineId };
commit.Tasks.Update.AddRange(preview.Updates.Select(ToUpdate));
var report = project.Apply(commit);
Console.WriteLine($"applied {report.Applied.Count}, rejected {report.Rejected.Count}");

static Patch<T> Set<T>(T? value) where T : struct =>
    value is T v ? v : Patch<T>.Missing;

static TaskUpdate ToUpdate(Mutation mutation) => new(mutation.Id)
{
    StartDate = Set(mutation.StartDate),
    EndDate = Set(mutation.EndDate),
    Duration = Set(mutation.Duration),
    LevelingDelay = Set(mutation.LevelingDelay),
    // The result is shared with the project: give the update its own copy.
    Pauses = mutation.Pauses is { } pauses
        ? pauses.ConvertAll(p => p.DeepClone())
        : Patch<List<Pause>>.Missing,
};
```

The preview moves task 1 to Wednesday and task 2 after it, so the commit applies two updates. `Mutation` sets only the fields that changed, and a `null` field becomes [`Patch<T>.Missing`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#missing>). Results are shared with the project, which is why the pauses are deep-cloned before they go into the update.

`Pauses` replaces the whole list. Set it to an empty list or [`Patch<List<Pause>>.Null`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#null>) to remove every pause. See [Clear leveling](<https://docs.dhtmlx.com/planning-engine/net/how-to/clear-leveling.md>).

## Applying

[`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) returns an [`ApplyReport`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md>):

- [`Applied`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#applied>) lists the path of every accepted operation.
- [`Rejected`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#rejected>) lists the path and message of every refused one.
- [`Effects`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#effects>) lists what the edit changed beyond the records you named (below).
- [`NeedsResync`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#needsresync>) is `true` when anything was rejected.
- [`Revision`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#revision>) is the project's revision number after this edit.

Removals cascade, and `Effects` tells you what went with them. Removing a task removes its descendants, every link that touches any of them and every assignment on them. Removing a resource removes its assignments.

```csharp
using Plen;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks =
    [
        new TaskData("phase") { TaskType = TaskType.Summary },
        new TaskData("spec") { Parent = "phase", StartDate = monday, Duration = 2.0 },
        new TaskData("release") { StartDate = monday, Duration = 1.0 },
    ],
    Links = [new LinkData { Id = "spec-release", Source = "spec", Target = "release" }],
    Resources = [new ResourceData { Id = "ann" }],
    Assignments = [new AssignmentData { Id = "ann-spec", Task = "spec", Resource = "ann", Value = 1.0 }],
};
var project = new Project();
project.Solve(data, new SolveOptions());

var changes = new SchedulingChanges();
changes.Tasks.Remove.Add("phase");
var report = project.Apply(changes);

var effects = report.Effects;
Console.WriteLine($"tasks: {string.Join(", ", effects.RemovedTasks)}");
Console.WriteLine($"links: {string.Join(", ", effects.RemovedLinks)}");
Console.WriteLine($"assignments: {string.Join(", ", effects.RemovedAssignments)}");
```

Removing the summary `phase` also removes `spec`, the link `spec-release` and the assignment `ann-spec`. Delete the same records from your own model.

The other `Effects` lists:

- [`GeneratedLinks`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#changeeffects>) and `GeneratedAssignments` hold the ids `Apply` made up for additions without one.
- `DroppedLinks` and `DroppedAssignments` hold records that were accepted but are now inactive, for example a link to an unscheduled task. They stay in the project and come back into play when the task does.

`Apply` edits the project in place. Clones made earlier don't see the edit, so you can [`Clone()`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/project.md#clone>) a project, apply a multi-step edit to the clone and drop it if the user cancels. For a single edit, [`Preview`](<https://docs.dhtmlx.com/planning-engine/net/api/project-preview.md>) does the same job without a clone. See [Previewing changes](<https://docs.dhtmlx.com/planning-engine/net/live-projects/previewing-changes.md>).

Field-only task edits and assignment updates take a fast path that patches the affected records without rebuilding the whole model. Frequent small edits, like drags, stay cheap.

## Rescheduling

After [`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>), call [`Solve(null, options)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>). It schedules the edited state, accepts the dates as the new baseline and returns the updates relative to the previous one.

Tasks that a successful operation added or updated are always reported, even when the planner leaves them exactly where the edit put them. The same goes for tasks whose working calendars changed because of a resource or assignment edit. Your model gets the derived end or duration for every task you edited, not only for the ones that moved. These reported tasks accumulate across several `Apply` calls until the next acyclic `Solve(null, …)` or [`Level`](<https://docs.dhtmlx.com/planning-engine/net/api/project-level.md>). A cyclic solve keeps them pending.

### Guarding against stale edits

A UI usually edits the dates it showed the user. If the project changed in between (an edit from another tab, a leveling run), applying the edit can do the wrong thing. Set [`SchedulingChanges.BaselineId`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md#baselineid>) to the token of the result the user saw, and [`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) refuses the whole change set with [`ProjectError.StaleBaseline`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/project-exception.md#projecterror>) when that token isn't current.

```csharp
using Plen;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");

var project = new Project();
var options = new SolveOptions();
var loaded = project.Solve(
    new ProjectData { Tasks = [new TaskData(1) { StartDate = monday, Duration = 2.0 }] },
    options);
var seen = loaded.BaselineId;

// The edit was made against the dates the user saw.
var changes = new SchedulingChanges { BaselineId = seen };
changes.Tasks.Update.Add(new TaskUpdate(1) { Duration = 3.0 });
project.Apply(changes);

// A second edit still carrying the old token is refused as a whole.
try
{
    project.Apply(changes);
}
catch (ProjectException e) when (e.Code == ProjectError.StaleBaseline)
{
    Console.WriteLine("stale: reload first");
}

var solved = project.Solve(null, options);
Console.WriteLine($"new baseline differs: {solved.BaselineId != seen}");
```

A token names a baseline only while the project is solved and clean. After an `Apply`, any `BaselineId` is stale until the next solve, so take a fresh token from each result. Tokens are opaque and unique to the process: don't store them or compare them across runs. `null` (the default) skips the check.

## Rejections and resync

An operation is rejected when it can't apply: an unknown id in `Update` or `Remove`, a duplicate id in `Add`, [`Patch<T>.Null`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#null>) on a required field (a link's `Source` or `Target`, an assignment's `Task`, `Resource` or `Value`), or a record that fails validation after the edit, such as an invalid pause or assignment slice. Each operation is tried on its own copy, so a rejected operation leaves no partial edit behind. The operations around it still apply.

```csharp
using Plen;

var project = new Project();
var options = new SolveOptions();
project.Solve(Load(), options);

var changes = new SchedulingChanges();
changes.Tasks.Remove.Add("deleted-elsewhere");
changes.Tasks.Update.Add(new TaskUpdate("design") { Duration = 4.0 });

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

if (report.NeedsResync)
{
    // Solve(null, …), Apply and Level now throw until the full data is sent again.
    try
    {
        project.Solve(null, options);
    }
    catch (ProjectException e) when (e.Code == ProjectError.NeedsResync)
    {
        project.Solve(Load(), options);
    }
}

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

The update applies and the removal is rejected with `rejected tasks.remove[0]: unknown task id`. The project now holds the update but not the removal, which is probably not what your model holds. That's why any rejection blocks further edits: `Solve(null, …)`, `Apply` and [`Level`](<https://docs.dhtmlx.com/planning-engine/net/api/project-level.md>) throw [`ProjectException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/project-exception.md>) with [`ProjectError.NeedsResync`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/project-exception.md#projecterror>) until a successful `Solve(data, …)` replaces the whole state with data you know is right.

The resync data must pass validation. If it doesn't, `Solve` throws [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>), and the project still needs a resync. See [Recover from rejected changes](<https://docs.dhtmlx.com/planning-engine/net/how-to/recover-from-rejected-changes.md>).

[`Evaluate`](<https://docs.dhtmlx.com/planning-engine/net/api/project-evaluate.md>) and [`SuggestAssignments`](<https://docs.dhtmlx.com/planning-engine/net/api/project-suggest-assignments.md>) run the same apply on disposable copies, so a rejected candidate shows up as its [`CandidateResult.Error`](<https://docs.dhtmlx.com/planning-engine/net/api/core/evaluate/candidate-result.md>) (a [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/rejected-operation.md>)) instead of blocking the project. See [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/net/resources-and-assignment/evaluating-alternatives.md>).

## What change sets can't do

Some edits have no operation. For these, rebuild the data in your model and send it with [`Solve(data, …)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>):

- Calendars can't be added, changed or removed. Tasks and resources can switch to another existing calendar through `CalendarId`.
- [`SchedulingConfig`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md>) can't change: [`DurationUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#durationunit>), [`UseCalendars`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#usecalendars>), [`CalendarMode`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#calendarmode>), [`ApplyConstraints`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#applyconstraints>) and the other settings are fixed until the next full solve.
- A task's [`TaskType`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/task-data.md#tasktype>) and `DurationUnit` aren't in [`TaskUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/task-update.md>). To turn a task into a summary or a milestone, remove it and add it again (its links and assignments go with the removal), or resync.
- An id can't be renamed. An update finds the record by its [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) and never changes it.
- [`ResourceUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/resource-update.md>) changes only `Capacity` and `CalendarId`.

Calls that don't need a change set don't take one. [`Level`](<https://docs.dhtmlx.com/planning-engine/net/api/project-level.md>) writes its own `LevelingDelay` and `Pauses` edits and accepts the result. See [Leveling resources](<https://docs.dhtmlx.com/planning-engine/net/resources-and-assignment/leveling-resources.md>).
