dotnet/efcore · error · InvalidOperationException

The type '{dictionaryType}' used for shared entity type '{en

Error message

The type '{dictionaryType}' used for shared entity type '{entityType}' is not suitable for use as a change-tracking proxy because its indexer property is not virtual. Consider using an implementation of '{interfaceType}' that allows overriding of the indexer.

What it means

With UseChangeTrackingProxies enabled, a shared-type entity whose CLR type is Dictionary<string,TValue> has a non-virtual indexer; since there are non-primary-key properties the proxy must intercept the indexer, but Dictionary's indexer cannot be overridden, so DictionaryCannotBeProxied is thrown (ProxyBindingRewriter.cs:167-179). It suggests using an IDictionary<,> implementation that allows overriding the indexer.

Source

Thrown at src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs:173

                    var indexerChecked = false;
                    foreach (var property in entityType.GetDeclaredProperties()
                                 .Where(p => !p.IsShadowProperty()))
                    {
                        if (property.IsIndexerProperty())
                        {
                            if (!indexerChecked)
                            {
                                indexerChecked = true;

                                if (!property.PropertyInfo!.SetMethod!.IsReallyVirtual())
                                {
                                    if (clrType.IsGenericType
                                        && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
                                        && clrType.GenericTypeArguments[0] == typeof(string))
                                    {
                                        if (entityType.GetProperties().Any(p => !p.IsPrimaryKey()))
                                        {
                                            throw new InvalidOperationException(
                                                ProxiesStrings.DictionaryCannotBeProxied(
                                                    clrType.ShortDisplayName(),
                                                    entityType.DisplayName(),
                                                    typeof(IDictionary<,>).MakeGenericType(clrType.GenericTypeArguments)
                                                        .ShortDisplayName()));
                                        }
                                    }
                                    else
                                    {
                                        throw new InvalidOperationException(
                                            ProxiesStrings.NonVirtualIndexerProperty(entityType.DisplayName()));
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (property.PropertyInfo == null)

View on GitHub (pinned to dbf9771522)

Solutions

  1. Use a custom type implementing IDictionary<string,TValue> whose indexer is virtual so it can be proxied.
  2. Disable change-tracking proxies when using dictionary-backed shared entities.
  3. Reduce the shared entity to key-only properties so no indexer interception is required.

Example fix

// before
// shared entity CLR type is Dictionary<string, object>
modelBuilder.SharedTypeEntity<Dictionary<string, object>>("Dyn");
options.UseChangeTrackingProxies();
// after
public class DynDict : Dictionary<string, object>
{
    public virtual new object? this[string key]
    { get => base[key]; set => base[key] = value; }
}
// use DynDict as the shared entity CLR type
Defensive patterns

Strategy: validation

Validate before calling

// Avoid change-tracking proxies on Dictionary-backed shared types with non-key properties,
// or use a custom IDictionary implementation with a virtual indexer:
if (clrType.IsGenericType
    && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
    && clrType.GenericTypeArguments[0] == typeof(string))
    throw new InvalidOperationException("Dictionary shared types cannot be change-track-proxied; use a virtual-indexer IDictionary impl.");

Type guard

static bool IsProxyableDictionary(Type t)
    => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))
       && t.GetProperty("Item")?.SetMethod?.IsVirtual == true;

Prevention

When it happens

Trigger: Enabling UseChangeTrackingProxies with a shared entity type (keyless/queryable property) backed by Dictionary<string,TValue> that has properties beyond the primary key.

Common situations: Using shared-type entities (e.g. for JSON/dynamic columns or queryable properties) with change-tracking proxies enabled.

Related errors


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