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


# LinkData

A dependency between two tasks: which end of the source task drives which end of the target task, and the working time between them. Links go in [`ProjectData.Links`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/project-data.md>). Build one with an object initializer; `Id`, `Source` and `Target` are `required`.

## Declaration

```csharp
namespace Plen;

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

`LinkData` has no constructor that sets the required properties, so `new LinkData()` without `Id`, `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>) | required | Link id, unique among the project's links. |
| `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`. Negative values are allowed. |
| `LagUnit` | [`TimeUnit?`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/time-unit.md>) | `null` | Unit of `Lag`. `null` uses [`Config.DurationUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md#durationunit>). |

### Id

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

The key that results use to refer to this link, for example in [`Note.DroppedLink`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/note.md>), in a cycle's `Links` and in the critical path in [`Analytics`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/analytics.md>). A `string`, `int` or `double` converts to `Id` implicitly.

### Source, Target

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

Ids of the two tasks the link connects. An id that names no task, or names an unscheduled task, doesn't fail validation. The link is dropped and reported as a [`Note.DroppedLink`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/note.md>) with the reason. See [Links that get dropped](<https://docs.dhtmlx.com/planning-engine/net/scheduling/links-constraints-summaries.md#links-that-get-dropped>) for every case.

### LinkType

```csharp
public LinkType LinkType { get; set; } = LinkType.Fs;
```

`Fs` (finish-to-start), `Ss`, `Ff` or `Sf`. See [`LinkType`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/link-data.md#linktype>) below.

### Lag

```csharp
public double Lag { get; set; }
```

Working time added between the source end and the target end, in `LagUnit`. A negative value lets the target overlap the source. Must be a finite number. Lag is an absolute amount; a percentage of the predecessor's duration isn't supported.

### LagUnit

```csharp
public TimeUnit? LagUnit { get; set; }
```

Unit for this link's `Lag`. `null` uses [`SchedulingConfig.DurationUnit`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/scheduling-config.md>).

:::note
Without a per-link `LagUnit`, lag is read in `Config.DurationUnit`.
:::

## Methods

### DeepClone

```csharp
public LinkData DeepClone()
```

Returns a copy with the same property values.

### ToString

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

Returns a readable rendering of every property, for logging.

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

## LinkType

The dependency type of a link: which end of the source task drives which end of the target task.

```csharp
namespace Plen;

public enum LinkType { Fs, Ss, Ff, Sf }
```

| Value | Description |
|---|---|
| `Fs` | Finish-to-start: the target starts after the source finishes. The default. |
| `Ss` | Start-to-start: the target starts after the source starts. |
| `Ff` | Finish-to-finish: the target finishes after the source finishes. |
| `Sf` | Start-to-finish: the target finishes after the source starts. |

`Lag` is added in every case. [`LinkAdd`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-add.md>) and [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md>) use the same enum. It has no parse or spelling helpers; JSON spellings are handled by the `Plen.Wire` package.

## Errors

The snapshot is validated when it's passed to [`Engine.Schedule`](<https://docs.dhtmlx.com/planning-engine/net/api/engine-schedule.md>) or [`Project.Solve(data, …)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>). A link makes the call throw [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>) with `Kind = `[`InputErrorKind.Validation`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md#inputerrorkind>) when:

- its `Id` repeats the id of an earlier link: `Path = "links[i].id"`
- its `Lag` is `NaN` or infinite: `Path = "links[i].lag"`

[`Validation.Repair`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/validation.md>) repairs a repeated id instead of throwing: the later link replaces the earlier one at the earlier position, and a [`Note.Repaired`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/note.md#repaired>) records it.

Links added or edited after the first solve go through [`LinkAdd`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-add.md>) and [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md>).

## Example

Task 2 starts when task 1 finishes, and task 3 starts four hours after task 2 starts, while the project counts durations in days:

```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 },
        new TaskData(3) { StartDate = start, Duration = 1.0 },
    ],
    Links =
    [
        new LinkData { Id = "a", Source = 1, Target = 2 },
        new LinkData
        {
            Id = "b",
            Source = 2,
            Target = 3,
            LinkType = LinkType.Ss,
            Lag = 4.0,
            LagUnit = TimeUnit.Hour,
        },
    ],
};

var result = Engine.Schedule(data, new SolveOptions());
foreach (var update in result.Updates)
{
    Console.WriteLine(update);
}
```

## Related

- [`ProjectData`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/project-data.md>) - the snapshot that holds links
- [`LinkAdd`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-add.md>) - adds a link to a live project
- [`LinkUpdate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/changes/link-update.md>) - edits a link in a live project
- [`Note`](<https://docs.dhtmlx.com/planning-engine/net/api/core/results/note.md#droppedlink>) - reports dropped links
- [Links, constraints and summaries](<https://docs.dhtmlx.com/planning-engine/net/scheduling/links-constraints-summaries.md>) - link types, lag and dropped links
