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
- 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<,>.
- If the shared entity has only PK properties, the indexer issue is not triggered — reduce the mapped properties to PK-only.
- 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
- Do not combine Dictionary<string, T> shared entities having non-PK properties with change-tracking proxies.
- Create a custom type implementing IDictionary<string, T> with a virtual indexer for proxy support.
- Reduce shared entity properties to PK-only if Dictionary proxy support is needed.
- Validate shared entity CLR types at startup against proxy requirements.
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
- The mapped indexer property on entity type
- Property ' . ' is not virtual. 'UseChangeTrackingProxies'…
- Property ' ' on entity type ' ' is mapped without a CLR…
- Entity type ' ' is sealed. 'UseChangeTrackingProxies'…
- The entity type ' ' was not found. Ensure that the entity…
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)