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
- Only call GetEvents on containers obtained from the ADO.NET stream receiver (built via FromMessage, which sets SequenceToken).
- In tests, set container.SequenceToken = new EventSequenceTokenV2(...) before calling GetEvents.
- 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
- Treat AdoNetBatchContainer as receive-only for GetEvents; obtain it from the stream receiver.
- In tests, always set SequenceToken before GetEvents.
- Never reuse a send-side container (from ToMessagePayload) for enumeration.
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
- {nameof(AdoNetQueueAdapter)} does not support a user supplie
- {nameof(options.DeleteStateOnClear)}=true is not supported.
- Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Str
- Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Str
- Invalid {nameof(AdoNetStreamOptions)} values for ADO.NET Str
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/1997d6dac1c58f9a.
Report an issue: GitHub.