Multicast

The Multicast transformation is used to broadcast every incoming record to multiple downstream targets. It creates clones of each record, ensuring that each linked output receives an independent copy.

This is especially useful when you want to write the same data to multiple destinations, such as a database, a file, and an in-memory store simultaneously.

Buffering Behavior

  • Non-blocking transformation
  • One input buffer
  • One output buffer per target
  • Each downstream target receives a separate clone of the input row
  • Supports linking to any number of outputs

If you need to split data conditionally (e.g., based on filters), consider using predicates instead of Multicast.

By default, cloning uses a specialized mechanism that copies all public properties and fields. Non-public members are not included. For custom cloning logic, implement ICloneable or use the CustomCloningStrategy property.

Basic Example: Broadcasting

This example shows how a row is duplicated and sent to two different memory destinations:

public class MyRow {
    public int Id { get; set; }
    public string Value { get; set; }
}

public static void Main() {
    var source = new MemorySource<MyRow>();
    source.DataAsList.Add(new MyRow() { Id = 1, Value = "A" });
    source.DataAsList.Add(new MyRow() { Id = 2, Value = "B" });

    var dest1 = new MemoryDestination<MyRow>();
    var dest2 = new MemoryDestination<MyRow>();

    var multicast = new Multicast<MyRow>();
    source.LinkTo(multicast);
    multicast.LinkTo(dest1);
    multicast.LinkTo(dest2);

    Network.Execute(source);

    Console.WriteLine("Destination 1");
    foreach (var row in dest1.Data)
        Console.WriteLine($"{row.Id}{row.Value}");

    Console.WriteLine("Destination 2");
    foreach (var row in dest2.Data)
        Console.WriteLine($"{row.Id}{row.Value}");

    // Outputs:
    // Destination 1
    // 1A
    // 2B
    // Destination 2
    // 1A
    // 2B
}

Custom Cloning Strategy

If the default cloning behavior is not sufficient, you can define a custom clone function:

var multicast = new Multicast<MyRow>();
multicast.CustomCloningStrategy = input => new MyRow {
    Id = input.Id,
    Value = string.Copy(input.Value)
};

You can also implement ICloneable on your class to take full control of cloning logic.

Dynamic Object Support

The Multicast transformation also supports ExpandoObject. This allows broadcasting rows with dynamic structures:

dynamic row1 = new ExpandoObject();
row1.Id = 1;
row1.Value = "DynamicA";

dynamic row2 = new ExpandoObject();
row2.Id = 2;
row2.Value = "DynamicB";

var source = new MemorySource();
source.DataAsList.Add(row1);
source.DataAsList.Add(row2);

var multicast = new Multicast(); // default: ExpandoObject
var dest1 = new MemoryDestination();
var dest2 = new MemoryDestination();

source.LinkTo(multicast);
multicast.LinkTo(dest1);
multicast.LinkTo(dest2);

Network.Execute(source);

// Each destination will receive the full row data independently

Splitting Data with Predicates

If you want to conditionally route data to different targets (i.e., not every row to every target), use predicates with the LinkTo method instead of Multicast.

Example: Conditional Routing (Split)

public class MyRow {
    public int Id { get; set; }
    public string Value { get; set; }
}

public static void Main() {
    var source = new MemorySource<MyRow>();
    source.DataAsList.Add(new MyRow() { Id = 1, Value = "A" });
    source.DataAsList.Add(new MyRow() { Id = 2, Value = "B" });

    var dest1 = new MemoryDestination<MyRow>();
    var dest2 = new MemoryDestination<MyRow>();

    source.LinkTo(dest1, row => row.Id <= 1);
    source.LinkTo(dest2, row => row.Id >= 2);

    Network.Execute(source);

    Console.WriteLine("Destination 1");
    foreach (var row in dest1.Data)
        Console.WriteLine($"{row.Id}{row.Value}");

    Console.WriteLine("Destination 2");
    foreach (var row in dest2.Data)
        Console.WriteLine($"{row.Id}{row.Value}");

    // Outputs:
    // Destination 1
    // 1A
    // Destination 2
    // 2B
}

Make sure all rows are consumed — either by linking to a destination or by using a VoidDestination for rows you wish to discard.