dotnet/efcore · error · InvalidOperationException

The type '{givenType}' cannot be mapped as a dictionary beca

Error message

The type '{givenType}' cannot be mapped as a dictionary because it does not implement '{dictionaryType}'.

What it means

Thrown by StringDictionaryComparer.Compare when the compared object is not an IReadOnlyDictionary<string, TElement>. The Cosmos provider maps dictionary properties via this comparer and expects the runtime type to implement the read-only dictionary interface; anything else is a model/type mismatch. This is an internal EF Core API, so the error usually indicates the property's CLR type is not what the model declared.

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 dbf9771522)

Solutions

  1. Ensure the property's CLR type implements IReadOnlyDictionary<string, TElement> (e.g., use Dictionary<string, T> which implements it).
  2. Align the element type T used in the model with the comparer's TElement.
  3. If the property should be a list/array, reconfigure it as a complex collection rather than a dictionary mapping.

Example fix

// before
public List<KeyValuePair<string, int>> Tags { get; set; } // mapped as dictionary

// after
public Dictionary<string, int> Tags { get; set; } // implements IReadOnlyDictionary<string,int>
Defensive patterns

Strategy: type-guard

Validate before calling

if (value is not IReadOnlyDictionary<string, TElement>)
{
    throw new InvalidOperationException($"{value?.GetType()} is not a string-keyed dictionary.");
}

Type guard

static bool IsStringDictionary<TElement>(object? value)
    => value is IReadOnlyDictionary<string, TElement>;

Prevention

When it happens

Trigger: A Cosmos-mapped property configured as a string-keyed dictionary whose runtime instance is a non-dictionary collection (e.g., a List<T>, array, or a custom type that does not implement IReadOnlyDictionary<string, T>). Also when TElement mismatches the comparer's generic argument after a model change.

Common situations: Changing a property from Dictionary<string,T> to List<T> (or vice versa) without updating the model configuration. Deserializing JSON into a POCO that does not implement the dictionary interface but is mapped as one. Version upgrades where the comparer's required interface changed.

Related errors


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/b50bbb0573de3922. Report an issue: GitHub.