{"record":{"id":"16347d362f4336ae","repo":"dotnet/efcore","slug":"the-mapped-indexer-property-on-entity-type-entit","errorCode":null,"errorMessage":"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.","messagePattern":"The mapped indexer property on entity type '(.+?)' 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\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs","lineNumber":183,"sourceCode":"                                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)\n                            {\n                                throw new InvalidOperationException(\n                                    ProxiesStrings.FieldProperty(property.Name, entityType.DisplayName()));\n                            }\n\n                            if (property.PropertyInfo.SetMethod?.IsReallyVirtual() == false)\n                            {\n                                throw new InvalidOperationException(\n                                    ProxiesStrings.NonVirtualProperty(property.Name, entityType.DisplayName()));\n                            }","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs#L165-L201","documentation":"ProxyBindingRewriter checks the indexer property of entity types when UseChangeTrackingProxies is enabled. If the indexer's setter is not virtual and the CLR type is NOT a Dictionary<string, T> (i.e. a custom indexer type), it throws NonVirtualIndexerProperty. Change-tracking proxies must override the indexer setter to intercept writes to indexer-mapped properties, which requires the indexer to be virtual. The else-branch covers all indexer-bearing types that are not Dictionary<,>.","triggerScenarios":"Using an entity CLR type that has an indexer property (e.g. a custom dynamic bag class with this[string key]) with indexer-mapped properties, while UseChangeTrackingProxies is enabled and the indexer setter is non-virtual. The throw occurs during model finalization.","commonSituations":"Custom dynamic/expando entity types with indexers for flexible property mapping. Shared-type entities using a custom collection-like CLR type with an indexer instead of Dictionary. Combining indexer-based property mapping with change-tracking proxies.","solutions":["Make the indexer property virtual (both getter and setter) on your custom CLR type so the proxy can override it.","If the indexer cannot be virtual, do not enable UseChangeTrackingProxies for entities using that CLR type.","Use a different property mapping strategy (explicit CLR properties) instead of indexer properties when proxies are required."],"exampleFix":"// before — non-virtual indexer\npublic class DynamicEntity\n{\n    public object this[string key]\n    {\n        get => _data[key];\n        set => _data[key] = value;\n    }\n    private readonly Dictionary<string, object> _data = new();\n}\n// UseChangeTrackingProxies() → throws NonVirtualIndexerProperty\n\n// after — virtual indexer\npublic class DynamicEntity\n{\n    public virtual object this[string key]\n    {\n        get => _data[key];\n        set => _data[key] = value;\n    }\n    private readonly Dictionary<string, object> _data = new();\n}","handlingStrategy":"validation","validationCode":"// At startup, verify indexer properties on entity types are virtual\n// when change-tracking proxies are enabled.\nstatic void ValidateVirtualIndexers(ModelBuilder modelBuilder)\n{\n    foreach (var entityType in modelBuilder.Model.GetEntityTypes())\n    {\n        foreach (var prop in entityType.GetDeclaredProperties().Where(p => !p.IsShadowProperty()))\n        {\n            if (prop.IsIndexerProperty()\n                && prop.PropertyInfo?.SetMethod?.IsVirtual != true)\n            {\n                throw new InvalidOperationException(\n                    $\"{entityType.DisplayName()} indexer must be virtual for change-tracking proxies.\");\n            }\n        }\n    }\n}","typeGuard":"// Type guard: check if a type has a virtual indexer suitable for proxying\nstatic bool HasVirtualIndexer(Type clrType)\n{\n    foreach (var prop in clrType.GetProperties())\n    {\n        if (prop.GetIndexParameters().Length > 0\n            && (prop.SetMethod?.IsVirtual != true || prop.SetMethod.IsFinal))\n            return false;\n    }\n    return true;\n}","tryCatchPattern":"try\n{\n    using var context = new MyContext(options);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"indexer property\") && ex.Message.Contains(\"not virtual\"))\n{\n    // Make the indexer virtual on the custom CLR type, or disable change-tracking proxies\n    logger.LogError(ex, \"Indexer not virtual; make it virtual for change-tracking proxies.\");\n    throw;\n}","preventionTips":["Make indexer properties virtual (both getter and setter) on custom entity CLR types.","Avoid indexer-based property mapping when change-tracking proxies are enabled unless the indexer is virtual.","Add a startup model validation pass that checks indexer properties are virtual.","Use explicit CLR properties instead of indexers when proxy interception is required."],"tags":["efcore","proxies","change-tracking","indexer","model-building","virtual"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}