dotnet/orleans · error · ArgumentException

The name must be a legal SQL table name

Error message

The name must be a legal SQL table name

What it means

Thrown by RelationalStorageExtensions.ExecuteMultipleInsertIntoAsync<T> when tableName is null, empty, or whitespace. The table name is interpolated directly into the 'INSERT INTO {0} (...)' template, so a blank value would produce invalid SQL; the extension rejects it early with ArgumentException(nameof(tableName)).

Source

Thrown at src/AdoNet/Shared/Storage/RelationalStorageExtensions.cs:57

        private const string indexedParameterTemplate = "@p{0}";

        /// <summary>
        /// Executes a multi-record insert query clause with <em>SELECT UNION ALL</em>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="storage">The storage to use.</param>
        /// <param name="tableName">The table name to against which to execute the query.</param>
        /// <param name="parameters">The parameters to insert.</param>
        /// <param name="nameMap">If provided, maps property names from <typeparamref name="T"/> to ones provided in the map.</param>
        /// <param name="onlyOnceColumns">If given, SQL parameter values for the given <typeparamref name="T"/> property types are generated only once. Effective only when <paramref name="useSqlParams"/> is <em>TRUE</em>.</param>
        /// <param name="useSqlParams"><em>TRUE</em> if the query should be in parameterized form. <em>FALSE</em> otherwise.</param>
        /// <param name="cancellationToken">The cancellation token. Defaults to <see cref="CancellationToken.None"/>.</param>
        /// <returns>The rows affected.</returns>
        public static Task<int> ExecuteMultipleInsertIntoAsync<T>(this IRelationalStorage storage, string tableName, IEnumerable<T> parameters, IReadOnlyDictionary<string, string>? nameMap = null, IEnumerable<string>? onlyOnceColumns = null, bool useSqlParams = true, CancellationToken cancellationToken = default)
        {
            if(string.IsNullOrWhiteSpace(tableName))
            {
                throw new ArgumentException("The name must be a legal SQL table name", nameof(tableName));
            }

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

            var storageConsts = DbConstantsStore.GetDbConstants(storage.InvariantName);

            var startEscapeIndicator = storageConsts.StartEscapeIndicator;
            var endEscapeIndicator = storageConsts.EndEscapeIndicator;

            //SqlParameters map is needed in case the query needs to be parameterized in order to avoid two
            //reflection passes as first a query needs to be constructed and after that when a database
            //command object has been created, parameters need to be provided to them.
            var sqlParameters = new Dictionary<string, object?>();
            const string insertIntoValuesTemplate = "INSERT INTO {0} ({1}) SELECT {2};";
            var columns = string.Empty;

View on GitHub (pinned to fca799fa70)

Solutions

  1. Pass a concrete, validated table name matching your schema (e.g. 'GrainState' or 'OrleansMembershipTable').
  2. Resolve table names from a mapping/registry and assert non-blank at startup.
  3. Use the storage-constant escape indicators correctly; the name is wrapped in start/end escape indicators by the extension.
  4. Add an integration test that inserts into each configured table to catch blank names early.

Example fix

// before
await storage.ExecuteMultipleInsertIntoAsync("", rows);

// after
const string table = "GrainState";
if (string.IsNullOrWhiteSpace(table)) throw new InvalidOperationException();
await storage.ExecuteMultipleInsertIntoAsync(table, rows);
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(tableName))
    throw new InvalidOperationException("Table name is not configured.");

Type guard

static bool IsValidTableName(string? s) => !string.IsNullOrWhiteSpace(s);

Try / catch

try { await storage.ExecuteMultipleInsertIntoAsync(table, rows); }
catch (ArgumentException ex) when (ex.ParamName == nameof(table)) { /* set table */ }

Prevention

When it happens

Trigger: Calling storage.ExecuteMultipleInsertIntoAsync("", parameters) or with a null table name; typical when the table name is computed from a grain state type name or read from options that were not bound.

Common situations: Naming convention refactor that yields empty for some state types; options binding skipped so the table name option is null; per-grain storage where a mapping function returns empty for a new grain type.

Related errors


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