[Canonical page](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# LinkUpdate

A partial edit of one existing link. Only the properties you set change; the rest keep their current values. Add it to `SchedulingChanges.Links.Update` (see [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md>)) and pass the change set to [`Project.Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>), [`Project.Preview`](<https://docs.dhtmlx.com/planning-engine/net/api/project-preview.md>) or [`Project.Evaluate`](<https://docs.dhtmlx.com/planning-engine/net/api/project-evaluate.md>). Create one with [`new LinkUpdate(id)`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md#constructors>) and set the properties to change in an object initializer.

## Declaration

```csharp
namespace Plen;

public sealed class LinkUpdate : IDeepCloneable<LinkUpdate>
```

## Constructors

### LinkUpdate(Id)

```csharp
[SetsRequiredMembers]
public LinkUpdate(Id id)
```

Creates an update for the link with this id. Every other property is [`Patch<T>.Missing`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md>), so the update changes nothing until a property is set.

**Parameters**

| Name | Type | Description |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | Id of the link to edit. A `string`, `int` or `double` converts to `Id` implicitly. |

The class has no parameterless constructor.

## Properties

Each editable property is a [`Patch<T>`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md>). A plain value converts to `Patch<T>` implicitly, so `Lag = 2.0` sets the lag. The table shows what `Patch<T>.Null` does to each property. The properties mean the same as on [`LinkData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md>).

| Property | Type | `Patch<T>.Null` |
|---|---|---|
| `Id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | Not a patch. Selects the link to edit. |
| `Source` | `Patch<`[`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>)`>` | Rejected: a link always has a source. |
| `Target` | `Patch<`[`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>)`>` | Rejected: a link always has a target. |
| `LinkType` | `Patch<`[`LinkType`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md#linktype>)`>` | Resets to `LinkType.Fs`. |
| `Lag` | `Patch<double>` | Resets to `0.0`. |
| `LagUnit` | `Patch<`[`TimeUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/time-unit.md>)`>` | Clears the unit, so `Lag` is read in [`SchedulingConfig.DurationUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md>). |

### Id

```csharp
public required Id Id { get; set; }
```

Id of an existing link. The update edits that link and can't change its id.

### Source, Target

```csharp
public Patch<Id> Source { get; set; }
public Patch<Id> Target { get; set; }
```

A value moves that end of the link to another task. If the new task doesn't exist or is unscheduled, the update is still applied, but the link is dropped from scheduling and its id is listed in [`ApplyReport.Effects.DroppedLinks`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#changeeffects>).

`Id` to `Patch<Id>` is a single implicit conversion, but `int` or `string` to `Patch<Id>` isn't. Write `Source = (Id)3` or `Source = Patch<Id>.Of(3)`.

### LinkType

```csharp
public Patch<LinkType> LinkType { get; set; }
```

Which end of `Source` drives which end of `Target`.

### Lag

```csharp
public Patch<double> Lag { get; set; }
```

Working time between the two ends, in the link's lag unit. Negative values are allowed. The value must be finite.

### LagUnit

```csharp
public Patch<TimeUnit> LagUnit { get; set; }
```

Unit of `Lag` for this link only.

## Methods

### DeepClone

```csharp
public LinkUpdate DeepClone()
```

Returns a copy with the same property values.

### ToString

```csharp
public override string ToString()
```

Returns a readable rendering of every property, for logging.

`LinkUpdate` doesn't override `Equals`; two instances compare by reference.

## Errors

An invalid update doesn't throw. It's listed in [`ApplyReport.Rejected`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md>) as a [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/rejected-operation.md>) with `Path = "links.update[i]"`, and the link stays as it was. An update is rejected when:

- `Id` names no link in the project: `unknown link id`
- `Source` or `Target` is [`Patch<Id>.Null`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md#null>): `required field cannot be null`
- `Lag` is `NaN` or infinite: `lag must be finite`

A rejection sets `ApplyReport.NeedsResync`. See [`Project.Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) for what that means for later calls.

## Example

Turn link `"a"` into a start-to-start link with a two-hour lag and leave its tasks as they are:

```csharp
using Plen;

var start = Moment.FromYmdHm(2026, 4, 6, 8, 0)
    ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks =
    [
        new TaskData(1) { StartDate = start, Duration = 2.0 },
        new TaskData(2) { StartDate = start, Duration = 1.0 },
    ],
    Links = [new LinkData { Id = "a", Source = 1, Target = 2 }],
};

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

var changes = new SchedulingChanges { BaselineId = solved.BaselineId };
changes.Links.Update.Add(new LinkUpdate("a")
{
    LinkType = LinkType.Ss,
    Lag = 2.0,
    LagUnit = TimeUnit.Hour,
});

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

var result = project.Solve(null, options);
foreach (var update in result.Updates)
{
    Console.WriteLine(update);
}
```

## Related

- [`LinkData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md>) - the record being edited
- [`LinkAdd`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-add.md>) - adds a new link
- [`Patch<T>`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/patch.md>) - the three-state field type
- [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md>) - the change set that carries the update
- [Editing a project](<https://docs.dhtmlx.com/planning-engine/net/live-projects/editing-a-project.md>) - the full edit cycle
