dotnet/orleans · error · InvalidOperationException

Cannot get events from a half-baked {nameof(AdoNetBatchConta

Error message

Cannot get events from a half-baked {nameof(AdoNetBatchContainer)}

What it means

Thrown by AdoNetBatchContainer.GetEvents<T>() when SequenceToken is null. A container built via the public constructor (used for sending) has no sequence token; only containers materialized from a stored message via FromMessage get a SequenceToken assigned. Calling GetEvents on a send-side container is invalid because there is no offset to anchor the events to.

Source

Thrown at src/AdoNet/Orleans.Streaming.AdoNet/AdoNetBatchContainer.cs:52

    [Id(3)]
    public EventSequenceTokenV2 SequenceToken { get; internal set; } = null!;

    /// <summary>
    /// Holds the receipt for message confirmation.
    /// </summary>
    [Id(4)]
    public int Dequeued { get; internal set; }

    #endregion Serialized State

    #region Interface

    StreamSequenceToken IBatchContainer.SequenceToken => SequenceToken;

    public IEnumerable<Tuple<T, StreamSequenceToken>> GetEvents<T>()
    {
        return SequenceToken is null
            ? throw new InvalidOperationException($"Cannot get events from a half-baked {nameof(AdoNetBatchContainer)}")
            : Events
                .OfType<T>()
                .Select((e, i) => Tuple.Create<T, StreamSequenceToken>(e, SequenceToken.CreateSequenceTokenForEvent(i)));
    }

    public bool ImportRequestContext()
    {
        if (RequestContext is not null)
        {
            RequestContextExtensions.Import(RequestContext);
            return true;
        }

        return false;
    }

    #endregion Interface

View on GitHub (pinned to fca799fa70)

Solutions

  1. Only call GetEvents on containers obtained from the ADO.NET stream receiver (built via FromMessage, which sets SequenceToken).
  2. In tests, set container.SequenceToken = new EventSequenceTokenV2(...) before calling GetEvents.
  3. Do not reuse send-side containers (from ToMessagePayload) for event enumeration.

Example fix

// before (test/misuse)
var container = new AdoNetBatchContainer(streamId, events, null);
foreach (var ev in container.GetEvents<MyEvent>()) { } // SequenceToken null -> error

// after
container.SequenceToken = new EventSequenceTokenV2(0);
foreach (var ev in container.GetEvents<MyEvent>()) { }
Defensive patterns

Strategy: type-guard

Type guard

// Only enumerate events from receive-side containers (token set).
static bool IsReadyForEvents(AdoNetBatchContainer c) => c.SequenceToken is not null;

Try / catch

try { foreach (var e in container.GetEvents<T>()) { /* ... */ } }
catch (InvalidOperationException ex) when (ex.Message.Contains("half-baked"))
{
    // container was not materialized via FromMessage; log and skip.
}

Prevention

When it happens

Trigger: GetEvents<T>() is invoked on an AdoNetBatchContainer whose SequenceToken was never set — i.e., one created by ToMessagePayload/new AdoNetBatchContainer rather than deserialized and stamped via FromMessage.

Common situations: Custom streaming code or tests that construct an AdoNetBatchContainer directly and call GetEvents; reflection/serialization round-trips that bypass FromMessage; misuse of an internal type outside its receive path.

Related errors


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