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
- Use the indexer to overwrite: ((IDictionary<string,object?>)row)[key] = value; — it calls SetValue with isAdd=false and never throws on duplicates.
- Guard with ContainsKey before Add: if (!dic.ContainsKey(key)) dic.Add(key, value);
- Remove then Add when you need strict add semantics: dic.Remove(key); dic.Add(key, value);
- 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
- Prefer the indexer dic[key] = value over Add unless you genuinely need add-or-fail semantics.
- If porting Dictionary/ExpandoObject code, audit every .Add call against the row's column set.
- When projecting extra columns, copy the DapperRow into a new Dictionary and add freely.
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
- key
- type
- This operation requires an identity or a connected command
- type
- The reader has been disposed; this can happen after all data
AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13).
Data as JSON: /api/errors/1e387b1a445d49b6.
Report an issue: GitHub.