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

  1. Ensure the runtime type of dictionary-mapped properties implements IReadOnlyDictionary<string, TElement> (Dictionary<string, T> satisfies this).
  2. If deserializing JSON, map into Dictionary<string, TElement> rather than a non-generic or dynamic type.
  3. 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

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


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)