Entity History does not record "Delete" Owned Entity changes

Author: samuel-ko-progensoftCreated Jul 20, 2026Updated Aug 10, 2026

Environment

  • Abp.ZeroCore.EntityFrameworkCore 11.0.0
  • .NET 10 / EF Core 10
  • SQL Server

Summary

When we remove an [Owned] entity, it does not create entity history.

Steps to reproduce

csharp
[Audited]
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; };
    public Address? HomeAddress { get; set; }
}

[Owned]
public class Address
{
    public string Street { get; set; };
}

public class MyDbContext : AbpDbContext
{
    public DbSet<Person> People => Set<Person>();

    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
}
csharp
// 1. Create a Person with a HomeAddress.
var person = new Person { Name = "John", HomeAddress = new Address { Street = "Main St" } };
context.People.Add(person);
await context.SaveChangesAsync();

// 2. Remove the HomeAddress.
var loaded = await context.People.FirstAsync();
loaded.HomeAddress = null;
await context.SaveChangesAsync();

Actual behavior

For step 1, it produces a property change (as expected):

EntityTypeFullName EntityId PropertyName OriginalValue NewValue
Person.Address Id of the Person Street NULL "Main Street"

For step 2, it didn't produce any entity history.

Expected behavior

Step 2 should produce the following (as the opposite of "create" history):

EntityTypeFullName EntityId PropertyName OriginalValue NewValue
Person.Address Id of the Person Street "Main Street" NULL

Root cause

  1. AbpZeroCommonDbContext.SaveChangesAsync() calls EntityHistoryHelper.CreateEntityChangeSet(ChangeTracker.Entries().ToList()): https://github.com/aspnetboilerplate/aspnetboilerplate/blob/06fc849206855c9e2b8cffaa3dc9200edd9fffe2/src/Abp.ZeroCore.EntityFrameworkCore/Zero/EntityFrameworkCore/AbpZeroCommonDbContext.cs#L203-L215

    At this point, the owned entity's state is Deleted, so GetNewValue() correctly returns null: https://github.com/aspnetboilerplate/aspnetboilerplate/blob/06fc849206855c9e2b8cffaa3dc9200edd9fffe2/src/Abp.ZeroCore.EntityFrameworkCore/EntityHistory/Extensions/PropertyEntryExtensions.cs#L10-L15

  2. Then, base.SaveChanges() runs. The owned entity's state becomes Detached.

  3. Then, EntityHistoryHelper.SaveAsync(changeSet) calls the private UpdateChangeSet(changeSet) method, which re-reads the new value: https://github.com/aspnetboilerplate/aspnetboilerplate/blob/06fc849206855c9e2b8cffaa3dc9200edd9fffe2/src/Abp.ZeroCore.EntityFrameworkCore/EntityHistory/EntityHistoryHelper.cs#L331-L336

    At this point, the owned entity's state is Detached, so GetNewValue() returns propertyEntry.CurrentValue. This makes NewValue == OriginalValue, and the property change is pruned as "unchanged".

Source: aspnetboilerplate/aspnetboilerplate