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


# LinkAdd

A new dependency between two tasks, added to a live project through [`SchedulingChanges.Links`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md>)`.Add`. It has the same properties as [`LinkData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md>), except that `Id` is nullable. Build one with an object initializer; `Source` and `Target` are `required`.

## Declaration

```csharp
namespace Plen;

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

`LinkAdd` has no constructor that sets the required properties, so `new LinkAdd()` without `Source` and `Target` doesn't compile.

## Properties

| Property | Type | Default | Description |
|---|---|---|---|
| `Id` | [`Id?`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | `null` | Id of the new link. `null` makes `Apply` generate one. |
| `Source` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | required | Id of the predecessor task. |
| `Target` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | required | Id of the successor task. |
| `LinkType` | [`LinkType`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md#linktype>) | `LinkType.Fs` | Which end of `Source` drives which end of `Target`. |
| `Lag` | `double` | `0.0` | Working time between the two ends, in `LagUnit`. Must be finite. |
| `LagUnit` | [`TimeUnit?`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/time-unit.md>) | `null` | Unit of `Lag`. `null` uses [`SchedulingConfig.DurationUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md>). |

`Source`, `Target`, `LinkType`, `Lag` and `LagUnit` mean the same as on [`LinkData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md>), including what happens when an endpoint names no task. A `string`, `int` or `double` converts to `Id` implicitly.

### Id

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

A non-null id stores the link under that id. It must not repeat the id of a link already in the project.

`null` makes [`Project.Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) pick the first free id of the form `$generated_link_1`, `$generated_link_2`, and so on. The generated id is listed in [`ApplyReport.Effects.GeneratedLinks`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#changeeffects>), so you can store it for later updates or removals.

## Methods

### DeepClone

```csharp
public LinkAdd DeepClone()
```

Returns a copy with the same property values.

### ToString

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

Returns a readable rendering of every property, for logging.

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

## Errors

A `LinkAdd` is one operation, named `links.add[i]`. It's rejected, and listed as a [`RejectedOperation`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/rejected-operation.md>) in [`ApplyReport.Rejected`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md>) instead of applied, when:

- `Id` is non-null and a link with that id already exists: `duplicate link id`
- `Lag` is `NaN` or infinite: `lag must be finite`

A rejection isn't thrown. [`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) reports it and keeps going.

An endpoint that names no task, or names an unscheduled task, doesn't reject the operation; see [`ChangeEffects.DroppedLinks`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md#changeeffects>).

A `LinkAdd` can reuse the id of a link removed in the same change set, because link removals run before link adds; see [operation order](<https://docs.dhtmlx.com/planning-engine/net/live-projects/editing-a-project.md#change-sets>).

## Example

Link task 1 to task 2 in a solved project, and read back the generated link id:

```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 = 3.0 },
    ],
};

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

var changes = new SchedulingChanges();
changes.Links.Add.Add(new LinkAdd { Source = 1, Target = 2, LinkType = LinkType.Fs });

var report = project.Apply(changes);
Console.WriteLine($"new link ids: {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}");
```

## Related

- [`LinkData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md>) - the same link in project data
- [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md>) - edits an existing link
- [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/scheduling-changes.md>) - the change set that carries the add
- [`ApplyReport`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/apply-report.md>) - generated ids and rejections
- [Editing a project](<https://docs.dhtmlx.com/planning-engine/net/live-projects/editing-a-project.md>) - the full edit cycle
