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
Startup: Name-Based with ManifestItem (Recommended)
public SchedulerConfigurationBuilder ScheduleMany<TTrain>(
string name,
IEnumerable<ManifestItem> items,
Schedule schedule,
Action<ScheduleOptions>? options = null
)
where TTrain : classThe name parameter automatically derives:
groupId=nameprunePrefix="{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 : classEach 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 : IManifestPropertiesRuntime (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 : IManifestPropertiesType Parameters
ManifestItem API (Recommended)
| 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 | — | The output type of the train. Not constrained — any output type is accepted. The output is discarded when jobs complete. |
TSource | — | The type of elements in the source collection. Can be any type — it is transformed into (ExternalId, Input) pairs by the map function. |
Parameters
ManifestItem API (Recommended)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes (name-based) | — | The batch name. Automatically derives groupId = name, prunePrefix = "{name}-", and each external ID = "{name}-{item.Id}". |
items | IEnumerable<ManifestItem> | Yes | — | The collection of items to create manifests from. Each item becomes one scheduled manifest. |
schedule | Schedule | Yes | — | 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) | — | The batch name. Automatically derives groupId = name, prunePrefix = "{name}-", and each external ID = "{name}-{suffix}". |
sources | IEnumerable<TSource> | Yes | — | The collection of items to create manifests from. Each item becomes one scheduled manifest. |
map | Func<TSource, (string, TInput)> | Yes | — | A function that transforms each source item into an ID/suffix and input pair. |
schedule | Schedule | Yes | — | 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
Basic Batch Scheduling with ManifestItem (Recommended)
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 — 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
configureEachcallback receivesAction<TSource, ManifestOptions>(notAction<ManifestOptions>likeSchedule) — this lets you customize options based on the source item. It applies per-item overrides on top of the base options fromScheduleOptions. - The source collection is materialized (
.ToList()) internally to avoid multiple enumeration. - The group is configured via
.Group(...)onScheduleOptions. Per-group settings (MaxActiveJobs, Priority, IsEnabled) can be set from code or adjusted at runtime from the dashboard. See Per-Group Dispatch Controls. ScheduleManycannot be followed by.ThenInclude()— use IncludeMany (withdependsOn) instead for batch dependent scheduling.