dotnet/efcore · error · InvalidOperationException
The type ' ' cannot be mapped as a dictionary because it…
Error message
The type '{givenType}' cannot be mapped as a dictionary because it does not implement '{dictionaryType}'. What it means
The StringDictionaryComparer<TDictionary, TElement>.Compare method throws when neither 'a' nor 'b' implements IReadOnlyDictionary<string, TElement?>. This is an internal change-tracking comparer used by the Cosmos provider for properties mapped as string-keyed dictionaries. The error indicates the runtime type assigned to a dictionary-mapped property does not satisfy the required generic dictionary interface.
Solutions
- Ensure the runtime type of dictionary-mapped properties implements IReadOnlyDictionary<string, TElement> (Dictionary<string, T> satisfies this).
- If deserializing JSON, map into Dictionary<string, TElement> rather than a non-generic or dynamic type.
- Check that TElement in the model matches the actual element type stored at runtime.
Example fix
// before — property type is non-generic IDictionary
public IDictionary Tags { get; set; } = new Hashtable();
// after — property type implements IReadOnlyDictionary<string, T>
public Dictionary<string, string> Tags { get; set; } = new(); Defensive patterns
Strategy: type-guard
Type guard
// Ensure dictionary-mapped properties implement IReadOnlyDictionary<string, T>
static bool IsValidStringDictionary<TElement>(object? value)
=> value is null || value is IReadOnlyDictionary<string, TElement>;
// Usage before assigning a value to a Cosmos dictionary-mapped property
if (!IsValidStringDictionary<string>(entity.Tags))
{
throw new InvalidOperationException("Tags must be a Dictionary<string, string>.");
} Prevention
- Always use Dictionary<string, T> as the property type for Cosmos dictionary-mapped properties.
- Ensure custom JSON converters deserialize into Dictionary<string, T>, not JObject or Hashtable.
- Avoid assigning non-generic IDictionary or other incompatible collection types to dictionary-mapped properties.
When it happens
Trigger: A Cosmos entity property is mapped as a dictionary (e.g., Dictionary<string, int>), but at runtime the actual value is a type that does not implement IReadOnlyDictionary<string, TElement?> — for example a non-generic IDictionary, a JObject, or a custom collection that lacks the IReadOnlyDictionary<string, TElement> implementation. The Compare method is called during change tracking when EF compares the snapshot value to the current value.
Common situations: Deserializing JSON into a non-generic or weakly-typed container (e.g., Newtonsoft JObject, System.Text.Json JsonElement) that is assigned to a dictionary-mapped property; using a custom collection type that implements IDictionary but not IReadOnlyDictionary<string, TElement>; a model where TElement type inference mismatches the runtime element type.
Related errors
- An Azure Cosmos DB container name is defined on entity type
- An error occurred while reading a database value. The…
- Cosmos-specific methods can only be used when the context…
- Specified argument was out of the range of valid values…
- The discriminator value for
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/b50bbb0573de3922.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs:128
{
if (aDictionary.Count != bDictionary.Count)
{
return false;
}
foreach (var pair in aDictionary)
{
if (!bDictionary.TryGetValue(pair.Key, out var bValue)
|| !elementCompare(pair.Value, bValue))
{
return false;
}
}
return true;
}
throw new InvalidOperationException(
CosmosStrings.BadDictionaryType(
(a is IDictionary<string, TElement?> ? b : a).GetType().ShortDisplayName(),
typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName()));
}
private static int GetHashCode(IEnumerable source, Func<TElement?, int> elementGetHashCode)
{
if (source is not IReadOnlyDictionary<string, TElement?> sourceDictionary)
{
throw new InvalidOperationException(
CosmosStrings.BadDictionaryType(
source.GetType().ShortDisplayName(),
typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName()));
}
var hash = new HashCode();
foreach (var pair in sourceDictionary)View on GitHub (pinned to 3a2006ef56)