{"record":{"id":"2ca6436534a66d7a","repo":"dotnet/efcore","slug":"property-1-entitytype-0-property-is-not-virt","errorCode":null,"errorMessage":"Property '{1_entityType}.{0_property}' 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":"Property '(.+?)\\.(.+?)' 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":92,"sourceCode":"                    throw new InvalidOperationException(ProxiesStrings.ItsASeal(entityType.DisplayName()));\n                }\n\n                foreach (var navigationBase in entityType.GetDeclaredNavigations()\n                             .Concat<IConventionNavigationBase>(entityType.GetDeclaredSkipNavigations()))\n                {\n                    if (!navigationBase.IsShadowProperty())\n                    {\n                        if (_options.UseChangeTrackingProxies)\n                        {\n                            if (navigationBase.PropertyInfo == null)\n                            {\n                                throw new InvalidOperationException(\n                                    ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName()));\n                            }\n\n                            if (navigationBase.PropertyInfo.SetMethod?.IsReallyVirtual() == false)\n                            {\n                                throw new InvalidOperationException(\n                                    ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName()));\n                            }\n                        }\n\n                        if (_options.UseLazyLoadingProxies\n                            && navigationBase.LazyLoadingEnabled)\n                        {\n                            if (navigationBase.PropertyInfo == null\n                                || !navigationBase.PropertyInfo.GetMethod!.IsReallyVirtual())\n                            {\n                                if (!_options.IgnoreNonVirtualNavigations\n                                    && navigationBase is not INavigation { ForeignKey.IsOwnership: true })\n                                {\n                                    if (navigationBase.PropertyInfo == null)\n                                    {\n                                        throw new InvalidOperationException(\n                                            ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName()));\n                                    }","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs#L74-L110","documentation":"When UseChangeTrackingProxies is enabled, ProxyBindingRewriter checks each navigation's PropertyInfo setter. If the setter exists but is not virtual (SetMethod?.IsReallyVirtual() == false), it throws NonVirtualProperty. Change-tracking proxies must override the setter to intercept writes, which requires the setter to be virtual. The resource string uses numbered format parameters ({1_entityType}.{0_property}) to protect entity type names when sensitive logging is off.","triggerScenarios":"Enabling UseChangeTrackingProxies() when at least one navigation property has a non-virtual setter (i.e. the property itself is not virtual or only the getter is virtual). The throw occurs during model finalization when ProxyBindingRewriter validates navigation setters.","commonSituations":"Entity classes with auto-properties that are not marked virtual (public ICollection<Post> Posts { get; set; }). Using read-only collection properties with a non-virtual private setter. Applying change-tracking proxies to an existing model where properties were not designed for proxy overriding.","solutions":["Mark every navigation property as virtual so both getter and setter are overridable: public virtual ICollection<Post> Posts { get; set; }.","If the setter cannot be virtual, do not use UseChangeTrackingProxies for that entity.","Ensure all properties (not just navigations) are virtual when using change-tracking proxies."],"exampleFix":"// before — non-virtual navigation setter\npublic class Blog\n{\n    public int Id { get; set; }\n    public ICollection<Post> Posts { get; set; }\n}\n// UseChangeTrackingProxies() → throws NonVirtualProperty\n\n// after — mark navigation virtual\npublic class Blog\n{\n    public int Id { get; set; }\n    public virtual ICollection<Post> Posts { get; set; }\n}","handlingStrategy":"validation","validationCode":"// At startup, verify all navigation setters are virtual for change-tracking proxies.\nstatic void ValidateVirtualNavigationSetters(ModelBuilder modelBuilder)\n{\n    foreach (var entityType in modelBuilder.Model.GetEntityTypes())\n    {\n        foreach (var nav in entityType.GetDeclaredNavigations()\n                     .Concat<INavigationBase>(entityType.GetDeclaredSkipNavigations()))\n        {\n            if (!nav.IsShadowProperty()\n                && nav.PropertyInfo?.SetMethod?.IsVirtual != true)\n            {\n                throw new InvalidOperationException(\n                    $\"{entityType.DisplayName()}.{nav.Name} setter must be virtual.\");\n            }\n        }\n    }\n}","typeGuard":"// Type guard: check if a property has a virtual setter\nstatic bool HasVirtualSetter(PropertyInfo? prop)\n    => prop?.SetMethod?.IsVirtual == true && !prop.SetMethod.IsFinal;","tryCatchPattern":"try\n{\n    using var context = new MyContext(options);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"not virtual\"))\n{\n    // Mark the navigation property virtual and retry\n    logger.LogError(ex, \"Navigation setter not virtual; mark it virtual for change-tracking proxies.\");\n    throw;\n}","preventionTips":["Mark all navigation properties as virtual (public virtual ICollection<T> Nav { get; set; }).","Add a startup model validation pass that checks navigation setters are virtual.","Establish a project convention that all entity properties are virtual when proxies are used.","Review auto-generated entity classes for missing virtual modifiers."],"tags":["efcore","proxies","change-tracking","virtual","navigation","model-building"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}