dotnet/efcore · error · InvalidOperationException

The type ' ' used for shared entity type ' ' is not…

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

ProxyBindingRewriter checks the indexer property of shared entity types (e.g. Dictionary<string, TValue> used via Shared-type entities) when UseChangeTrackingProxies is enabled. If the indexer's setter is not virtual and the CLR type is a generic Dictionary<string, T> with non-PK properties, it throws DictionaryCannotBeProxied. Dictionary<,> has a non-virtual indexer so it cannot be subclassed by Castle DynamicProxy. The error suggests implementing IDictionary<TKey, TValue> with a virtual indexer instead.

Solutions

  1. Create a custom class implementing IDictionary<string, TValue> (or IReadOnlyDictionary) with a virtual indexer and use that as the shared entity CLR type instead of Dictionary<,>.
  2. If the shared entity has only PK properties, the indexer issue is not triggered — reduce the mapped properties to PK-only.
  3. Do not enable UseChangeTrackingProxies when using Dictionary<string, T> shared entities with extra properties.

Example fix

// before — Dictionary<string, object> shared entity + change-tracking proxies
options.UseChangeTrackingProxies();
modelBuilder.Entity<Dictionary<string, object>>("DynamicEntity", b =>
{
    b.IndexerProperty<int>("Id");
    b.IndexerProperty<string>("Name"); // non-PK → throws
});

// after — custom type with virtual indexer
public class DynamicEntity : Dictionary<string, object>
{
    public virtual new object this[string key]
    {
        get => base[key];
        set => base[key] = value;
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// At startup, verify shared entity types using Dictionary<string, T> with non-PK
// properties are not combined with change-tracking proxies.
static void ValidateSharedEntityProxies(ModelBuilder modelBuilder)
{
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        var clrType = entityType.ClrType;
        if (clrType.IsGenericType
            && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
            && clrType.GenericTypeArguments[0] == typeof(string)
            && entityType.GetProperties().Any(p => !p.IsPrimaryKey()))
        {
            throw new InvalidOperationException(
                $"{entityType.DisplayName()} uses Dictionary<{clrType.GenericTypeArguments[1].Name}> " +
                "with non-PK properties; change-tracking proxies cannot proxy it.");
        }
    }
}

Type guard

// Type guard: check if a CLR type can be proxied as a shared entity
static bool IsProxiableSharedType(Type clrType, bool hasNonPkProperties)
{
    if (clrType.IsGenericType
        && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)
        && clrType.GenericTypeArguments[0] == typeof(string))
    {
        return !hasNonPkProperties;
    }
    return true;
}

Try / catch

try
{
    using var context = new MyContext(options);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("not suitable for use as a change-tracking proxy"))
{
    // Use a custom IDictionary implementation with a virtual indexer, or disable proxies
    logger.LogError(ex, "Dictionary shared entity cannot be proxied; use a custom type.");
    throw;
}

Prevention

When it happens

Trigger: Using shared-type entities (entity types sharing a common CLR type, typically Dictionary<string, object>) configured via .Entity<MySharedType>() or .Entity("MyShared") patterns, where the CLR type is Dictionary<string, TValue>, the entity has properties beyond its primary key, and UseChangeTrackingProxies is enabled. The throw occurs during model finalization when the indexer is checked.

Common situations: Dynamic or multi-tenant models that use Dictionary<string, object> as the entity CLR type and attempt to combine this with change-tracking proxies. Shared-type entities with additional properties (not just PK) that need proxy interception.

Related errors


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

Appendix: 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 3a2006ef56)