ScheduleMany / ScheduleManyAsync

Batch-schedules multiple instances of a train from a collection. All manifests are created or updated in a single transaction. Supports automatic cleanup of stale manifests via prunePrefix.

ScheduleMany is used at startup configuration time; ScheduleManyAsync is used at runtime via ITraxScheduler.

Signatures

public SchedulerConfigurationBuilder ScheduleMany<TTrain>(
    string name,
    IEnumerable<ManifestItem> items,
    Schedule schedule,
    Action<ScheduleOptions>? options = null
)
    where TTrain : class

The name parameter automatically derives:

  • groupId = name
  • prunePrefix = "{name}-"
  • externalId = "{name}-{item.Id}" for each item

Each ManifestItem contains the item’s ID and input. The input type is inferred from TTrain’s IServiceTrain<TInput, TOutput> interface and validated at configuration time. The output type is not constrained: scheduled trains can return any output type, and the output is discarded for background jobs.

Startup: Unnamed with ManifestItem

public SchedulerConfigurationBuilder ScheduleMany<TTrain>(
    IEnumerable<ManifestItem> items,
    Schedule schedule,
    Action<ScheduleOptions>? options = null
)
    where TTrain : class

Each ManifestItem.Id is used as the full external ID (no name prefix applied).

ManifestItem

public sealed record ManifestItem(
    string Id,
    IManifestProperties Input,
    string? DependsOn = null
);
Property Type Description
Id string The item’s identifier. In name-based overloads, this becomes the suffix (full external ID = "{name}-{Id}"). In unnamed overloads, this is the full external ID.
Input IManifestProperties The train input for this item. Must match the expected input type of TTrain.
DependsOn string? The external ID of the parent manifest this item depends on. Used by IncludeMany and ThenIncludeMany. See Dependent Scheduling.

Startup: Explicit Type Parameters (Legacy)

The four-type-parameter forms are still available for backward compatibility:

// Name-based
public SchedulerConfigurationBuilder ScheduleMany<TTrain, TInput, TOutput, TSource>(
    string name,
    IEnumerable<TSource> sources,
    Func<TSource, (string Suffix, TInput Input)> map,
    Schedule schedule,
    Action<ScheduleOptions>? options = null,
    Action<TSource, ManifestOptions>? configureEach = null
)
    where TTrain : IServiceTrain<TInput, TOutput>
    where TInput : IManifestProperties

// Explicit
public SchedulerConfigurationBuilder ScheduleMany<TTrain, TInput, TOutput, TSource>(
    IEnumerable<TSource> sources,
    Func<TSource, (string ExternalId, TInput Input)> map,
    Schedule schedule,
    Action<ScheduleOptions>? options = null,
    Action<TSource, ManifestOptions>? configureEach = null
)
    where TTrain : IServiceTrain<TInput, TOutput>
    where TInput : IManifestProperties

Runtime (ITraxScheduler)

Task<IReadOnlyList<Manifest>> ScheduleManyAsync<TTrain, TInput, TOutput, TSource>(
    IEnumerable<TSource> sources,
    Func<TSource, (string ExternalId, TInput Input)> map,
    Schedule schedule,
    Action<ScheduleOptions>? options = null,
    Action<TSource, ManifestOptions>? configureEach = null,
    CancellationToken ct = default
)
    where TTrain : IServiceTrain<TInput, TOutput>
    where TInput : IManifestProperties

Type Parameters

Type Parameter Constraint Description
TTrain class The train interface type. Can implement IServiceTrain<TInput, TOutput> with any output type. The input type is inferred at configuration time. The output is discarded for background jobs.

Legacy / Runtime API

Type Parameter Constraint Description
TTrain IServiceTrain<TInput, TOutput> The train interface type. All items in the batch execute the same train. Can have any output type; the output is discarded for background jobs.
TInput IManifestProperties The input type for the train. Each item in the batch can have different input data.
TOutput (none) The output type of the train. Not constrained; any output type is accepted. The output is discarded when jobs complete.
TSource (none) The type of elements in the source collection. Can be any type and is transformed into (ExternalId, Input) pairs by the map function.

Parameters

Parameter Type Required Default Description
name string Yes (name-based) (none) The batch name. Automatically derives groupId = name, prunePrefix = "{name}-", and each external ID = "{name}-{item.Id}".
items IEnumerable<ManifestItem> Yes (none) The collection of items to create manifests from. Each item becomes one scheduled manifest.
schedule Schedule Yes (none) The schedule definition applied to all manifests in the batch. Use Every or Cron helpers.
options Action<ScheduleOptions>? No null Optional callback to configure all scheduling options. The name-based overload pre-sets Group(name) and PrunePrefix("{name}-") before invoking your callback. See ScheduleOptions.

Legacy API Parameters

Parameter Type Required Default Description
name string Yes (name-based) (none) The batch name. Automatically derives groupId = name, prunePrefix = "{name}-", and each external ID = "{name}-{suffix}".
sources IEnumerable<TSource> Yes (none) The collection of items to create manifests from. Each item becomes one scheduled manifest.
map Func<TSource, (string, TInput)> Yes (none) A function that transforms each source item into an ID/suffix and input pair.
schedule Schedule Yes (none) The schedule definition applied to all manifests in the batch.
options Action<ScheduleOptions>? No null Optional callback to configure all scheduling options. See ScheduleOptions.
configureEach Action<TSource, ManifestOptions>? No null Optional callback to set per-item manifest options. Receives both the source item and options, allowing per-item overrides of the base options set via options.
ct CancellationToken No default Cancellation token (runtime API only).

Returns

  • Startup: SchedulerConfigurationBuilder, for continued fluent chaining.
  • Runtime: Task<IReadOnlyList<Manifest>>, all created or updated manifest records.

Examples

var tables = new[] { "customers", "orders", "products" };

services.AddTrax(trax => trax
    .AddScheduler(scheduler => scheduler
        .ScheduleMany<ISyncTableTrain>(
            "sync",
            tables.Select(table => new ManifestItem(
                table,
                new SyncTableInput { TableName = table }
            )),
            Every.Minutes(5))
    )
);
// Creates: sync-customers, sync-orders, sync-products
// groupId: "sync", prunePrefix: "sync-"

Each ManifestItem contains the item’s ID (used as the suffix in name-based overloads) and the train input. No map function needed because the data is already structured.

Unnamed Batch Scheduling

var tables = new[] { "customers", "orders", "products" };

scheduler.ScheduleMany<ISyncTableTrain>(
    tables.Select(table => new ManifestItem(
        $"sync-{table}",
        new SyncTableInput { TableName = table }
    )),
    Every.Minutes(5));

With Pruning (Automatic Stale Cleanup)

The name-based overload includes pruning automatically (prunePrefix: "{name}-"):

// If "partners" was in a previous deployment but removed from this list,
// its manifest ("sync-partners") will be deleted because it starts with
// "sync-" but wasn't included in the current batch.
var tables = new[] { "customers", "orders" };

scheduler.ScheduleMany<ISyncTableTrain>(
    "sync",
    tables.Select(table => new ManifestItem(
        table,
        new SyncTableInput { TableName = table }
    )),
    Every.Minutes(5));

With Group Configuration

scheduler.ScheduleMany<ISyncTableTrain>(
    "sync",
    tables.Select(table => new ManifestItem(
        table,
        new SyncTableInput { TableName = table }
    )),
    Every.Minutes(5),
    options => options
        .Priority(10)
        .Group(group => group
            .MaxActiveJobs(5)
            .Priority(20)));

Legacy API with Map Function

The three-type-parameter form is still available and supports per-item configuration via configureEach:

scheduler.ScheduleMany<ISyncTableTrain, SyncTableInput, Unit, string>(
    tables,
    table => ($"sync-{table}", new SyncTableInput { TableName = table }),
    Every.Minutes(5),
    options => options.Priority(10),
    configureEach: (table, opts) =>
    {
        if (table == "orders")
        {
            opts.MaxRetries = 10;
            opts.Priority = 25;
        }
    });

Runtime Scheduling

public class TenantSyncService(ITraxScheduler scheduler)
{
    public async Task SyncTenants(IEnumerable<Tenant> tenants)
    {
        var manifests = await scheduler.ScheduleManyAsync<ISyncTenantTrain, SyncTenantInput, Unit, Tenant>(
            sources: tenants,
            map: tenant => (
                ExternalId: $"tenant-sync-{tenant.Id}",
                Input: new SyncTenantInput { TenantId = tenant.Id, Name = tenant.Name }
            ),
            schedule: Every.Hours(1),
            options => options
                .PrunePrefix("tenant-sync-")
                .Group("tenant-syncs"));
    }
}

Remarks

  • All manifests are created/updated in a single database transaction. If any manifest fails to save, the entire batch is rolled back.
  • Pruning runs in a separate database context after the main transaction commits. A prune failure does not roll back the upserted manifests. The failure is logged as a warning and retried on the next cycle.
  • The configureEach callback receives Action<TSource, ManifestOptions> (not Action<ManifestOptions> like Schedule), which lets you customize options based on the source item. It applies per-item overrides on top of the base options from ScheduleOptions.
  • The source collection is materialized (.ToList()) internally to avoid multiple enumeration.
  • The group is configured via .Group(...) on ScheduleOptions. Per-group settings (MaxActiveJobs, Priority, IsEnabled) can be set from code or adjusted at runtime from the dashboard. See Per-Group Dispatch Controls.
  • ScheduleMany cannot be followed by .ThenInclude(). Use IncludeMany (with dependsOn) instead for batch dependent scheduling.

Back to top

Trax - A .NET framework for Railway Oriented Programming with Effects, Scheduling, and more