{"record":{"id":"c9850d6025acb7e2","repo":"dotnet/efcore","slug":"the-type-dictionarytype-used-for-shared-entity","errorCode":null,"errorMessage":"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.","messagePattern":"The type '(.+?)' used for shared entity type '(.+?)' is not suitable for use as a change-tracking proxy because its indexer property is not virtual\\. Consider using an implementation of '(.+?)' that allows overriding of the indexer\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs","lineNumber":173,"sourceCode":"                    var indexerChecked = false;\n                    foreach (var property in entityType.GetDeclaredProperties()\n                                 .Where(p => !p.IsShadowProperty()))\n                    {\n                        if (property.IsIndexerProperty())\n                        {\n                            if (!indexerChecked)\n                            {\n                                indexerChecked = true;\n\n                                if (!property.PropertyInfo!.SetMethod!.IsReallyVirtual())\n                                {\n                                    if (clrType.IsGenericType\n                                        && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)\n                                        && clrType.GenericTypeArguments[0] == typeof(string))\n                                    {\n                                        if (entityType.GetProperties().Any(p => !p.IsPrimaryKey()))\n                                        {\n                                            throw new InvalidOperationException(\n                                                ProxiesStrings.DictionaryCannotBeProxied(\n                                                    clrType.ShortDisplayName(),\n                                                    entityType.DisplayName(),\n                                                    typeof(IDictionary<,>).MakeGenericType(clrType.GenericTypeArguments)\n                                                        .ShortDisplayName()));\n                                        }\n                                    }\n                                    else\n                                    {\n                                        throw new InvalidOperationException(\n                                            ProxiesStrings.NonVirtualIndexerProperty(entityType.DisplayName()));\n                                    }\n                                }\n                            }\n                        }\n                        else\n                        {\n                            if (property.PropertyInfo == null)","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs#L155-L191","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — Dictionary<string, object> shared entity + change-tracking proxies\noptions.UseChangeTrackingProxies();\nmodelBuilder.Entity<Dictionary<string, object>>(\"DynamicEntity\", b =>\n{\n    b.IndexerProperty<int>(\"Id\");\n    b.IndexerProperty<string>(\"Name\"); // non-PK → throws\n});\n\n// after — custom type with virtual indexer\npublic class DynamicEntity : Dictionary<string, object>\n{\n    public virtual new object this[string key]\n    {\n        get => base[key];\n        set => base[key] = value;\n    }\n}","handlingStrategy":"validation","validationCode":"// At startup, verify shared entity types using Dictionary<string, T> with non-PK\n// properties are not combined with change-tracking proxies.\nstatic void ValidateSharedEntityProxies(ModelBuilder modelBuilder)\n{\n    foreach (var entityType in modelBuilder.Model.GetEntityTypes())\n    {\n        var clrType = entityType.ClrType;\n        if (clrType.IsGenericType\n            && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)\n            && clrType.GenericTypeArguments[0] == typeof(string)\n            && entityType.GetProperties().Any(p => !p.IsPrimaryKey()))\n        {\n            throw new InvalidOperationException(\n                $\"{entityType.DisplayName()} uses Dictionary<{clrType.GenericTypeArguments[1].Name}> \" +\n                \"with non-PK properties; change-tracking proxies cannot proxy it.\");\n        }\n    }\n}","typeGuard":"// Type guard: check if a CLR type can be proxied as a shared entity\nstatic bool IsProxiableSharedType(Type clrType, bool hasNonPkProperties)\n{\n    if (clrType.IsGenericType\n        && clrType.GetGenericTypeDefinition() == typeof(Dictionary<,>)\n        && clrType.GenericTypeArguments[0] == typeof(string))\n    {\n        return !hasNonPkProperties;\n    }\n    return true;\n}","tryCatchPattern":"try\n{\n    using var context = new MyContext(options);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"not suitable for use as a change-tracking proxy\"))\n{\n    // Use a custom IDictionary implementation with a virtual indexer, or disable proxies\n    logger.LogError(ex, \"Dictionary shared entity cannot be proxied; use a custom type.\");\n    throw;\n}","preventionTips":["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."],"tags":["efcore","proxies","change-tracking","shared-type","indexer","dictionary","model-building"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}