{"record":{"id":"57bc65d515b6b013","repo":"dotnet/efcore","slug":"property-entitytype-property-is-not-virtual","errorCode":null,"errorMessage":"Property '{entityType}.{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/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs#L74-L110","documentation":"With UseChangeTrackingProxies enabled, a non-shadow navigation whose setter is not virtual throws NonVirtualProperty (ProxyBindingRewriter.cs:90-94). The proxy must override the setter to intercept change notifications.","triggerScenarios":"Enabling UseChangeTrackingProxies when a navigation property's setter is non-virtual.","commonSituations":"Applying change-tracking proxies to an existing model where navigations were written as plain auto-properties.","solutions":["Make the navigation property virtual (so both getter and setter can be overridden by the proxy).","Stop using change-tracking proxies for that entity type."],"exampleFix":"// before\npublic ICollection<Post> Posts { get; set; } = new List<Post>();\n// after\npublic virtual ICollection<Post> Posts { get; set; } = new List<Post>();","handlingStrategy":"validation","validationCode":"// Verify navigation setters are virtual before enabling change-tracking proxies:\nforeach (var et in modelBuilder.Model.GetEntityTypes())\nforeach (var nav in et.GetNavigations())\n{\n    var pi = nav.PropertyInfo;\n    if (pi is not null && pi.SetMethod?.IsVirtual != true)\n        throw new InvalidOperationException($\"{et.Name}.{nav.Name} setter must be virtual.\");\n}","typeGuard":"static bool IsVirtualSetter(System.Reflection.PropertyInfo? pi)\n    => pi?.SetMethod?.IsVirtual == true;","tryCatchPattern":null,"preventionTips":["Make all navigation properties virtual when using change-tracking proxies.","Audit entity classes for non-virtual navigations before enabling proxies.","Generate entities from templates that emit virtual properties by default."],"tags":["ef-core","proxies","change-tracking-proxies","navigation","virtual","model-design"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}