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
- Make the indexer virtual on the CLR type so the proxy can override it.
- 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
- Make indexer properties virtual when using change-tracking proxies on indexer-based entities.
- Avoid change-tracking proxies for non-virtual indexer entity types.
- Design custom property-bag types with virtual indexers if proxying is required.
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
- The type '{dictionaryType}' used for shared entity type '{en
- Entity type '{entityType}' is sealed. 'UseChangeTrackingProx
- Property '{property}' on entity type '{entityType}' is mappe
- Property '{entityType}.{property}' is not virtual. 'UseChang
- 'UseChangeTrackingProxies' and 'UseLazyLoadingProxies' each
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/16347d362f4336ae.
Report an issue: GitHub.