aspnetboilerplate/aspnetboilerplate · error · AbpException

Unknown EntityChangeType:

Error message

Unknown EntityChangeType: 

What it means

EntityChangeEventHelper.TriggerEntityChangeEvents iterates EntityChangeType values (Created/Updated/Deleted) collected by the unit of work and throws AbpException when the ChangeType is none of these. Since EntityChangeType normally only holds those three values, hitting the default branch signals an internal invariant violation — usually a custom or extended EntityChangeType enum value that the event helper does not handle, or corrupted change-tracking state.

Solutions

  1. Find the code assigning the unexpected EntityChangeType value and restrict it to Created/Updated/Deleted.
  2. If you extended EntityChangeType, override EntityChangeEventHelper and handle the new value in your own helper.
  3. Cast validation: never (EntityChangeType)rawInt without checking Enum.IsDefined.
  4. Log changedEntity.ChangeType at the throw point to identify which value leaked in.

Example fix

// before
var changeType = (EntityChangeType)rawValue; // may be undefined
// after
if (!Enum.IsDefined(typeof(EntityChangeType), rawValue))
    throw new ArgumentException($"Invalid EntityChangeType value: {rawValue}");
var changeType = (EntityChangeType)rawValue;
Defensive patterns

Strategy: type-guard

Validate before calling

if (!Enum.IsDefined(typeof(EntityChangeType), changeEntity.ChangeType))
    throw new InvalidOperationException($"ChangeType {changeEntity.ChangeType} is not a known EntityChangeType");

Type guard

bool IsKnownEntityChangeType(EntityChangeType t) =>
    t == EntityChangeType.Created || t == EntityChangeType.Updated || t == EntityChangeType.Deleted;

Try / catch

try
{
    await _unitOfWork.CompleteAsync(); // triggers entity change events
}
catch (AbpException ex) when (ex.Message.StartsWith("Unknown EntityChangeType"))
{
    Logger.Error(ex.Message);
    throw; // invariant violation — should not be swallowed
}

Prevention

When it happens

Trigger: A unit-of-work entity change snapshot carries an EntityChangeType value outside Created/Updated/Deleted (custom enum extension, memory corruption, or a custom IEntityChangeSet source), then TriggerEntityChangeEvents runs at UoW completion via TriggerEventsInternal.

Common situations: Upgrading ABP while a custom enum value was added; third-party code casting an int into EntityChangeType; bespoke change-tracking integrations; binary serialization of old enum values.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of aspnetboilerplate/aspnetboilerplate@2323c13a15 (2026-09-08). Data as JSON: /api/errors/0b94646f84753c05. Report an issue: GitHub.

Appendix: source

Thrown at src/Abp/Events/Bus/Entities/EntityChangeEventHelper.cs:104

        {
            foreach (var changedEntity in changedEntities)
            {
                switch (changedEntity.ChangeType)
                {
                    case EntityChangeType.Created:
                        TriggerEntityCreatingEvent(changedEntity.Entity);
                        TriggerEntityCreatedEventOnUowCompleted(changedEntity.Entity);
                        break;
                    case EntityChangeType.Updated:
                        TriggerEntityUpdatingEvent(changedEntity.Entity);
                        TriggerEntityUpdatedEventOnUowCompleted(changedEntity.Entity);
                        break;
                    case EntityChangeType.Deleted:
                        TriggerEntityDeletingEvent(changedEntity.Entity);
                        TriggerEntityDeletedEventOnUowCompleted(changedEntity.Entity);
                        break;
                    default:
                        throw new AbpException("Unknown EntityChangeType: " + changedEntity.ChangeType);
                }
            }
        }

        protected virtual void TriggerDomainEvents(List<DomainEventEntry> domainEvents)
        {
            foreach (var domainEvent in domainEvents)
            {
                EventBus.Trigger(domainEvent.EventData.GetType(), domainEvent.SourceEntity, domainEvent.EventData);
            }
        }

        protected virtual void TriggerEventWithEntity(Type genericEventType, object entity, bool triggerInCurrentUnitOfWork)
        {
            var entityType = entity.GetType();
            var eventType = genericEventType.MakeGenericType(entityType);

            if (triggerInCurrentUnitOfWork ||

View on GitHub (pinned to 2323c13a15)