dotnet/orleans · error · ArgumentNullException

Value cannot be null. (Parameter 'selector')

Error message

Value cannot be null. (Parameter 'selector')

What it means

Thrown by RelationalStorage.ReadAsync<TResult> when the selector delegate (Func<IDataRecord, int, CancellationToken, Task<TResult>>) is null. The selector transforms each IDataRecord row into TResult; without it ReadAsync cannot materialize results, so the call is rejected with ArgumentNullException(nameof(selector)). parameterProvider may legitimately be null.

Source

Thrown at src/AdoNet/Shared/Storage/RelationalStorage.cs:202

        ///        return new Information
        ///        {
        ///            TABLE_CATALOG = selector.GetValueOrDefault&lt;string&gt;("TABLE_CATALOG"),
        ///            TABLE_NAME = selector.GetValueOrDefault&lt;string&gt;("TABLE_NAME")
        ///        }
        ///}).ConfigureAwait(continueOnCapturedContext: false);
        /// </code>
        /// </example>
        public async Task<IEnumerable<TResult>> ReadAsync<TResult>(string query, Action<IDbCommand>? parameterProvider, Func<IDataRecord, int, CancellationToken, Task<TResult>> selector, CommandBehavior commandBehavior = CommandBehavior.Default, CancellationToken cancellationToken = default)
        {
            //If the query is something else that is not acceptable (e.g. an empty string), there will an appropriate database exception.
            if (query == null)
            {
                throw new ArgumentNullException(nameof(query));
            }

            if (selector == null)
            {
                throw new ArgumentNullException(nameof(selector));
            }

            return (await ExecuteAsync(query, parameterProvider, ExecuteReaderAsync, selector, commandBehavior, cancellationToken).ConfigureAwait(false)).Item1;
        }


        /// <summary>
        /// Executes a given statement. Especially intended to use with <em>INSERT</em>, <em>UPDATE</em>, <em>DELETE</em> or <em>DDL</em> queries.
        /// </summary>
        /// <param name="query">The query to execute.</param>
        /// <param name="parameterProvider">Adds parameters to the query. Parameter names must match those defined in the query.</param>
        /// <param name="commandBehavior">The command behavior that should be used. Defaults to <see cref="CommandBehavior.Default"/>.</param>
        /// <param name="cancellationToken">The cancellation token. Defaults to <see cref="CancellationToken.None"/>.</param>
        /// <returns>Affected rows count.</returns>
        /// <example>This sample shows how to make a hand-tuned database call.
        /// <code>
        /// //In contract to reading, execute queries are simpler as they return only
        /// //the affected rows count if it is available.

View on GitHub (pinned to fca799fa70)

Solutions

  1. Supply a non-null selector, e.g. (record, i, ct) => Task.FromResult(new T { ... }).
  2. Use the extension ReadAsync<TResult>(storage, query, selector, parameterProvider) or the reflection overload ReadAsync<TResult>(storage, query, parameters) if you want automatic mapping.
  3. Verify the selector argument is assigned before the call in refactor reviews.
  4. Enable nullable reference types so a null selector is a compile-time warning.

Example fix

// before
var rows = await storage.ReadAsync<T>(query, null, null); // selector null -> throws

// after
var rows = await storage.ReadAsync<T>(
    query,
    cmd => {},
    (record, i, ct) => Task.FromResult(new T { Id = record.GetValueOrDefault<int>("Id") }));
Defensive patterns

Strategy: validation

Validate before calling

if (selector is null) throw new ArgumentNullException(nameof(selector));

Type guard

static bool HasSelector<TResult>(Func<IDataRecord,int,CancellationToken,Task<TResult>>? s) => s is not null;

Try / catch

try { await storage.ReadAsync<T>(query, p, selector); }
catch (ArgumentNullException ex) when (ex.ParamName == nameof(selector)) { /* supply selector */ }

Prevention

When it happens

Trigger: Calling ReadAsync with a null selector, often by omitting an argument, mis-ordering arguments, or passing a method-group that resolved to null.

Common situations: Refactors that changed the selector signature so the old reference no longer binds; copy-paste that dropped the selector; using the reflection-based ReadAsync overload by mistake.

Related errors


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