dotnet/efcore · error · InvalidOperationException

The mapped indexer property on entity type '{entityType}' is

Error message

The mapped indexer property on entity type '{entityType}' is not virtual. 'UseChangeTrackingProxies' requires all entity types to be public, unsealed, have virtual properties, and have a public or protected constructor. 'UseLazyLoadingProxies' requires only the navigation properties be virtual.

What it means

With UseChangeTrackingProxies enabled, an entity whose CLR type has a non-virtual indexer property (not the special Dictionary<string,> case handled separately) throws NonVirtualIndexerProperty (ProxyBindingRewriter.cs:181-185). The proxy must override the indexer setter to intercept changes.

Source

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

                                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)
                            {
                                throw new InvalidOperationException(
                                    ProxiesStrings.FieldProperty(property.Name, entityType.DisplayName()));
                            }

                            if (property.PropertyInfo.SetMethod?.IsReallyVirtual() == false)
                            {
                                throw new InvalidOperationException(
                                    ProxiesStrings.NonVirtualProperty(property.Name, entityType.DisplayName()));
                            }

View on GitHub (pinned to dbf9771522)

Solutions

  1. Make the indexer virtual on the CLR type so the proxy can override it.
  2. Stop using change-tracking proxies for indexer-based entity types.

Example fix

// before
public class DynBag
{
    public object? this[string key] { get; set; }
}
// after
public class DynBag
{
    public virtual object? this[string key] { get; set; }
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify indexer setters are virtual before enabling change-tracking proxies:
foreach (var pi in clrType.GetProperties())
{
    if (pi.GetIndexParameters().Length > 0 && pi.SetMethod?.IsVirtual != true)
        throw new InvalidOperationException($"{clrType.Name} indexer must be virtual for change-tracking proxies.");
}

Type guard

static bool IsVirtualIndexer(System.Reflection.PropertyInfo? pi)
    => pi is not null && pi.GetIndexParameters().Length > 0 && pi.SetMethod?.IsVirtual == true;

Prevention

When it happens

Trigger: Enabling UseChangeTrackingProxies on an entity type that uses a custom indexer property whose indexer is non-virtual.

Common situations: Custom indexer-based entity types (e.g. dynamic property bags) used with change-tracking proxies.

Related errors


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