dotnet/orleans · error · ArgumentException

{nameof(AdoNetQueueAdapter)} does not support a user supplie

Error message

{nameof(AdoNetQueueAdapter)} does not support a user supplied {nameof(StreamSequenceToken)}.

What it means

Thrown by AdoNetQueueAdapter.QueueMessageBatchAsync when the caller supplies a non-null StreamSequenceToken. The ADO.NET streaming provider is non-rewindable: it cannot replay from an arbitrary offset, so user-supplied tokens are unsupported and rejected upfront rather than silently ignored.

Source

Thrown at src/AdoNet/Orleans.Streaming.AdoNet/AdoNetQueueAdapter.cs:39

    /// The ADO.NET provider works both ways.
    /// </summary>
    public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite;

    public IQueueAdapterReceiver CreateReceiver(QueueId queueId)
    {
        // map the queue id
        var adoNetQueueId = mapper.GetAdoNetQueueId(queueId);

        // create the receiver
        return ReceiverFactory(serviceProvider, [Name, adoNetQueueId, streamOptions, clusterOptions, cacheOptions, queries]);
    }

    public async Task QueueMessageBatchAsync<T>(StreamId streamId, IEnumerable<T> events, StreamSequenceToken? token, Dictionary<string, object>? requestContext)
    {
        // the ADO.NET provider is not rewindable so we do not support user supplied tokens
        if (token is not null)
        {
            throw new ArgumentException($"{nameof(AdoNetQueueAdapter)} does not support a user supplied {nameof(StreamSequenceToken)}.");
        }

        // map the Orleans stream id to the corresponding queue id
        var queueId = mapper.GetAdoNetQueueId(streamId);

        // create the payload from the events
        var payload = AdoNetBatchContainer.ToMessagePayload(serializer, streamId, events.Cast<object>().ToList(), requestContext);

        // we can enqueue the message now
        try
        {
            await queries.QueueStreamMessageAsync(clusterOptions.ServiceId, Name, queueId, payload, streamOptions.ExpiryTimeout.TotalSecondsCeiling());
        }
        catch (Exception ex)
        {
            LogFailedToQueueStreamMessage(ex, clusterOptions.ServiceId, Name, queueId);
            throw;
        }

View on GitHub (pinned to fca799fa70)

Solutions

  1. Pass null for the StreamSequenceToken when producing to the ADO.NET adapter.
  2. If you need rewindability, switch to a rewindable streaming provider instead of ADO.NET.
  3. Audit any custom producer/adapter wrapper to ensure it forwards null tokens to ADO.NET.

Example fix

// before
await adapter.QueueMessageBatchAsync(streamId, events, new EventSequenceTokenV2(5), requestContext);

// after
await adapter.QueueMessageBatchAsync(streamId, events, token: null, requestContext);
Defensive patterns

Strategy: type-guard

Type guard

// ADO.NET adapter is non-rewindable; only null tokens are valid.
static bool IsValidTokenForAdoNet(StreamSequenceToken? token) => token is null;

Try / catch

try { await adapter.QueueMessageBatchAsync(streamId, events, token, ctx); }
catch (ArgumentException ex) when (ex.Message.Contains("does not support a user supplied"))
{
    // drop the token and retry with null.
}

Prevention

When it happens

Trigger: QueueMessageBatchAsync is called with a non-null token argument. In normal Orleans usage the streaming runtime passes null for the token on a non-rewindable provider, so this fires only if custom code drives the adapter directly with a token.

Common situations: Custom producer code calling the queue adapter with a checkpoint token; porting rewindable-provider code (e.g. Azure Event Hubs) to ADO.NET without dropping the token; an IStreamQueueMapper/adapter wrapper forwarding tokens.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/8602eb58147b05cb. Report an issue: GitHub.