dotnet/efcore · error · InvalidOperationException

Property ' ' on entity type ' ' is mapped without a CLR…

Error message

Property '{property}' on entity type '{entityType}' is mapped without a CLR property. '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

When UseChangeTrackingProxies is enabled, ProxyBindingRewriter examines each declared navigation and skip-navigation. For change-tracking proxies, if a navigation has no CLR PropertyInfo (navigationBase.PropertyInfo == null — meaning it is a field-only or shadow navigation), it throws FieldProperty. Change-tracking proxies override property getters/setters to intercept changes, which is impossible without a CLR property to override.

Solutions

  1. Add a public or protected virtual CLR property for every navigation that currently has only a field.
  2. Switch to UseLazyLoadingProxies (which can tolerate field access via SetPropertyAccessMode) if full change-tracking proxies are not needed.
  3. Do not enable UseChangeTrackingProxies if your model intentionally uses field-only navigations.

Example fix

// before — field-only navigation, no CLR property
public class Blog
{
    [BackingField("_posts")]
    public ICollection<Post> Posts => _posts;
    private readonly List<Post> _posts = new();
}
// UseChangeTrackingProxies() → throws FieldProperty

// after — add a settable virtual CLR property
public class Blog
{
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}
Defensive patterns

Strategy: validation

Validate before calling

// At startup, verify all navigations have CLR properties when change-tracking proxies are enabled.
static void ValidateNavigationProperties(ModelBuilder modelBuilder)
{
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        foreach (var nav in entityType.GetDeclaredNavigations()
                     .Concat<INavigationBase>(entityType.GetDeclaredSkipNavigations()))
        {
            if (!nav.IsShadowProperty() && nav.PropertyInfo == null)
            {
                throw new InvalidOperationException(
                    $"{entityType.DisplayName()}.{nav.Name} has no CLR property; " +
                    "change-tracking proxies require one.");
            }
        }
    }
}

Try / catch

try
{
    using var context = new MyContext(options);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("mapped without a CLR property"))
{
    // Add a CLR property for the navigation, or switch to lazy-loading proxies
    logger.LogError(ex, "Navigation lacks CLR property; change-tracking proxies need one.");
    throw;
}

Prevention

When it happens

Trigger: Enabling UseChangeTrackingProxies() when at least one navigation property is mapped without a CLR property — e.g. using [BackingField] with only a field and no property, or configuring a shadow navigation in OnModelCreating. The throw occurs during model finalization.

Common situations: Domain models that use field-only collections (e.g. private readonly List<Post> _posts with [BackingField]) for encapsulation. Configuring navigations entirely in fluent API without corresponding CLR properties. Combining DDD-style entity patterns with change-tracking proxies.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/fa2d6159910ad45f. Report an issue: GitHub.

Appendix: source

Thrown at src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs:86

                {
                    continue;
                }

                if (clrType.IsSealed)
                {
                    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 })

View on GitHub (pinned to 3a2006ef56)