DapperLib/Dapper · error · ArgumentException

An item with the same key has already been added

Error message

An item with the same key has already been added

What it means

DapperRow implements IDictionary<string,object?>. Its private SetValue(key, value, isAdd) enforces add-semantics: when isAdd is true and the key already maps to a live (non-DeadValue) slot, it throws ArgumentException. The public path is IDictionary<string,object?>.Add, which calls SetValue(key, value, true) at SqlMapper.DapperRow.cs:147. This mirrors Dictionary<TKey,TValue>.Add rejecting duplicate keys.

Source

Thrown at Dapper/SqlMapper.DapperRow.cs:182

            }

            public object? SetValue(string key, object? value)
            {
                return SetValue(key, value, false);
            }

            private object? SetValue(string key, object? value, bool isAdd)
            {
                if (key is null) throw new ArgumentNullException(nameof(key));
                int index = table.IndexOfName(key);
                if (index < 0)
                {
                    index = table.AddField(key);
                }
                else if (isAdd && index < values.Length && !(values[index] is DeadValue))
                {
                    // then semantically, this value already exists
                    throw new ArgumentException("An item with the same key has already been added", nameof(key));
                }
                return SetValue(index, value);
            }
            internal object? SetValue(int index, object? value)
            {
                int oldLength = values.Length;
                if (oldLength <= index)
                {
                    // we'll assume they're doing lots of things, and
                    // grow it to the full width of the table
                    Array.Resize(ref values, table.FieldCount);
                    for (int i = oldLength; i < values.Length; i++)
                    {
                        values[i] = DeadValue.Default;
                    }
                }
                return values[index] = value;
            }

View on GitHub (pinned to 72a54c475f)

Solutions

  1. Use the indexer to overwrite: ((IDictionary<string,object?>)row)[key] = value; — it calls SetValue with isAdd=false and never throws on duplicates.
  2. Guard with ContainsKey before Add: if (!dic.ContainsKey(key)) dic.Add(key, value);
  3. Remove then Add when you need strict add semantics: dic.Remove(key); dic.Add(key, value);
  4. If you need overwrite-everywhere semantics, copy the DapperRow into a new Dictionary<string,object?> and manipulate that.

Example fix

// before
var dic = (IDictionary<string, object?>)row;
dic.Add("Id", 42); // throws if Id column already present

// after
var dic = (IDictionary<string, object?>)row;
dic["Id"] = 42; // indexer: overwrite, never throws
Defensive patterns

Strategy: validation

Validate before calling

IDictionary<string, object?> dic = (IDictionary<string, object?>)row;
if (dic.ContainsKey(key)) throw new InvalidOperationException($"Key already present: {key}");
dic.Add(key, value);

Prevention

When it happens

Trigger: Cast a Dapper query result (dynamic or the underlying DapperRow) to IDictionary<string,object?> and call .Add(existingColumn, value); or use ICollection<KeyValuePair>.Add which forwards to IDictionary.Add. Only the Add path sets isAdd=true; the indexer setter (this[key]=value) and public SetValue use isAdd=false and never throw.

Common situations: Materializing a row then injecting extra computed columns via dictionary Add instead of the indexer; porting ExpandoObject/Dictionary code that assumed overwrite; queries returning two columns with the same name so the second already occupies a slot when re-added.

Related errors


AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13). Data as JSON: /api/errors/1e387b1a445d49b6. Report an issue: GitHub.