dotnet/efcore · error · InvalidOperationException

Entity type ' ' is sealed. 'UseChangeTrackingProxies'…

Error message

Entity type '{entityType}' is sealed. '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

ProxyBindingRewriter iterates every concrete (non-abstract) entity type in the model. If an entity's CLR type is sealed (clrType.IsSealed), it throws ItsASeal because Castle DynamicProxy cannot create a proxy subclass of a sealed class. Change-tracking proxies need to override all properties; lazy-loading proxies need to override navigation properties — both impossible when the class cannot be inherited.

Solutions

  1. Remove the sealed modifier from every entity class that participates in the DbContext model.
  2. If the entity must stay sealed, exclude it from proxy generation or do not enable proxies.
  3. Ensure all entity types are public, unsealed, and have virtual properties plus a public/protected constructor.

Example fix

// before
public sealed class Blog
{
    public int Id { get; set; }
    public virtual List<Post> Posts { get; set; }
}
// UseChangeTrackingProxies() → throws ItsASeal

// after
public class Blog
{
    public int Id { get; set; }
    public virtual List<Post> Posts { get; set; }
}
Defensive patterns

Strategy: validation

Validate before calling

// At startup, scan all entity CLR types in the model for sealed classes.
static void ValidateNoSealedEntities(ModelBuilder modelBuilder)
{
    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
        if (entityType.ClrType.IsSealed)
        {
            throw new InvalidOperationException(
                $"{entityType.ClrType.Name} is sealed. Remove 'sealed' to use proxies.");
        }
    }
}

Type guard

// Type guard: check if a CLR type is suitable for proxy generation
static bool IsProxyCompatible(Type clrType)
    => !clrType.IsSealed && clrType.IsPublic && !clrType.IsAbstract;

Try / catch

try
{
    using var context = new MyContext(options);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("sealed"))
{
    // Remove sealed from the offending entity class, then retry
    logger.LogError(ex, "An entity type is sealed; proxies require unsealed types.");
    throw;
}

Prevention

When it happens

Trigger: Enabling UseChangeTrackingProxies() or UseLazyLoadingProxies() when at least one entity type in the model is declared as a sealed class. The throw occurs during model finalization when ProxyBindingRewiter processes the entity types.

Common situations: Using DTOs or domain entities declared as sealed (common in some C# coding conventions that seal by default). Adding proxies to an existing model that contains sealed entities. Code generation tools that emit sealed entity classes.

Related errors


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

Appendix: source

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

        IConventionContext<IConventionModelBuilder> context)
    {
        if (_options?.UseProxies == true)
        {
            modelBuilder.HasAnnotation(ProxyAnnotationNames.LazyLoading, _options.UseLazyLoadingProxies);
            modelBuilder.HasAnnotation(ProxyAnnotationNames.ChangeTracking, _options.UseChangeTrackingProxies);
            modelBuilder.HasAnnotation(ProxyAnnotationNames.CheckEquality, _options.CheckEquality);

            foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
            {
                var clrType = entityType.ClrType;
                if (clrType.IsAbstract)
                {
                    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(

View on GitHub (pinned to 3a2006ef56)