dotnet/efcore · error · InvalidOperationException
Property '{entityType}.{property}' is not virtual. 'UseChang
Error message
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. What it means
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.
Source
Thrown at src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs:92
throw new InvalidOperationException(ProxiesStrings.ItsASeal(entityType.DisplayName()));
}
foreach (var navigationBase in entityType.GetDeclaredNavigations()
.Concat<IConventionNavigationBase>(entityType.GetDeclaredSkipNavigations()))
{
if (!navigationBase.IsShadowProperty())
{
if (_options.UseChangeTrackingProxies)
{
if (navigationBase.PropertyInfo == null)
{
throw new InvalidOperationException(
ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName()));
}
if (navigationBase.PropertyInfo.SetMethod?.IsReallyVirtual() == false)
{
throw new InvalidOperationException(
ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName()));
}
}
if (_options.UseLazyLoadingProxies
&& navigationBase.LazyLoadingEnabled)
{
if (navigationBase.PropertyInfo == null
|| !navigationBase.PropertyInfo.GetMethod!.IsReallyVirtual())
{
if (!_options.IgnoreNonVirtualNavigations
&& navigationBase is not INavigation { ForeignKey.IsOwnership: true })
{
if (navigationBase.PropertyInfo == null)
{
throw new InvalidOperationException(
ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName()));
}View on GitHub (pinned to dbf9771522)
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.
Example fix
// before
public ICollection<Post> Posts { get; set; } = new List<Post>();
// after
public virtual ICollection<Post> Posts { get; set; } = new List<Post>(); Defensive patterns
Strategy: validation
Validate before calling
// Verify navigation setters are virtual before enabling change-tracking proxies:
foreach (var et in modelBuilder.Model.GetEntityTypes())
foreach (var nav in et.GetNavigations())
{
var pi = nav.PropertyInfo;
if (pi is not null && pi.SetMethod?.IsVirtual != true)
throw new InvalidOperationException($"{et.Name}.{nav.Name} setter must be virtual.");
} Type guard
static bool IsVirtualSetter(System.Reflection.PropertyInfo? pi)
=> pi?.SetMethod?.IsVirtual == true; Prevention
- 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.
When it happens
Trigger: Enabling UseChangeTrackingProxies when a navigation property's setter is non-virtual.
Common situations: Applying change-tracking proxies to an existing model where navigations were written as plain auto-properties.
Related errors
- Property '{property}' on entity type '{entityType}' is mappe
- Entity type '{entityType}' is sealed. 'UseChangeTrackingProx
- The type '{dictionaryType}' used for shared entity type '{en
- The mapped indexer property on entity type '{entityType}' is
- Including navigation '{navigation}' is not supported as the
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/57bc65d515b6b013.
Report an issue: GitHub.