dotnet/orleans · error · KeyNotFoundException

Invalid option

Error message

Invalid option {optionId}

What it means

Thrown by Voting PollGrain.AddVote when optionId is outside the valid index range of the poll's Options list (optionId < 0 || optionId >= options.Count). KeyNotFoundException signals that the requested vote option does not exist on this poll.

Solutions

  1. Fetch the current PollState (GetCurrentResults) and use its Options.Count to bound optionId before voting.
  2. Validate optionId >= 0 && optionId < options.Length on the client.
  3. Catch KeyNotFoundException at the caller and present 'option no longer available' to the user.

Example fix

// before
await poll.AddVote(optionId); // throws if out of range

// after: validate against the live poll definition
var state = await poll.GetCurrentResults();
if (optionId < 0 || optionId >= state.Options.Count) return;
await poll.AddVote(optionId);
Defensive patterns

Strategy: validation

Validate before calling

// Validate the option id against the current poll before voting
var state = await poll.GetCurrentResults();
if (optionId < 0 || optionId >= state.Options.Count) return;
await poll.AddVote(optionId);

Type guard

static bool IsValidOption(int optionId, PollState state)
    => optionId >= 0 && optionId < state.Options.Count;

Try / catch

null

Prevention

When it happens

Trigger: A client calls pollGrain.AddVote(optionId) with an optionId that is negative or >= the number of options configured when the poll was created.

Common situations: Hard-coding an option id that drifts after the poll is re-created; a client reading a stale poll definition; off-by-one when the client assumes a specific option ordering.

Related errors


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

Appendix: source

Thrown at samples/Voting/Grains/PollGrain.cs:30

        [PersistentState(stateName: "pollState", storageName: "votes")]
        IPersistentState<PollState> state) => _votes = state;

    public Task<PollState> GetCurrentResults() => Task.FromResult(_votes.State);

    public async Task CreatePoll(PollState initialState)
    {
        // Set the state and persist it
        _votes.State = initialState;
        await _votes.WriteStateAsync();
    }

    public async Task<PollState> AddVote(int optionId)
    {
        // Perform input validation
        var options = _votes.State.Options;
        if (optionId < 0 || optionId >= options.Count)
        {
            throw new KeyNotFoundException($"Invalid option {optionId}");
        }

        // Add the vote & persist the updated state.
        var (option, votes) = options[optionId];
        options[optionId] = (option, votes + 1);
        await _votes.WriteStateAsync();

        // Notify the watchers.
        _pollWatchers.Notify(watcher => watcher.OnPollUpdated(_votes.State));
        return _votes.State;
    }

    private readonly ObserverManager<IPollWatcher> _pollWatchers = new(TimeSpan.FromMinutes(1));

    public Task StartWatching(IPollWatcher watcher)
    {
        _pollWatchers.Subscribe(watcher);
        return Task.CompletedTask;

View on GitHub (pinned to fca799fa70)