Migrated ABP Framework- 8.2.3 t0 ABP Framework- 10.5.0 and .NET 8 to NET Framework to .NET 10.
After upgrading our project's .NET version and ABP packages (and separately migrating AutoMapper → Mapperly, which we've ruled out as the cause), we observed that entities which are not explicitly updated in the current unit of work are still getting their audit columns (LastModificationTime, LastModifierId) stamped and persisted on SaveChanges().
Repro scenario
- In a single unit of work, we call
_financialCounselingRepository.InsertAsync(financialCounseling, autoSave: true)— inserting a newFinancialCounseling(aFullAuditedEntity<long>). - Earlier in the same unit of work, we load an unrelated/loosely-related
Patiententity (alsoFullAuditedEntity<long>) via a repository read (tried both tracked andAsNoTracking()— issue persists once the entity re-enters the change tracker through navigation fixup). - We do not call
UpdateAsynconPatient, nor modify any of its business properties. - On
SaveChanges(), we instrumentedChangeTracker.Entries()right before EF Core's save call and observed:Patient→EntityState.Modified, with onlyLastModificationTimereported as anIsModifiedproperty.FinancialCounseling→EntityState.Modified, with onlyLastModificationTime,LastModifierIdreported asIsModified.- Other entities in the same save batch (e.g.
Encounter,TransactionLocking_TR) correctly show a full, expected set of business properties as modified — confirming those are legitimate changes.
- Result:
PatientandFinancialCounselingrows get aLastModificationTime/LastModifierIdupdate in the database even though no business column changed and no explicit update call was made against them.
What we expect
Entities that are EntityState.Modified purely because EF Core's DetectChanges()/navigation fixup flagged them (with zero non-audit, non-concurrency-stamp properties actually dirty) should not be audit-stamped by ABP's IAuditPropertySetter. Today, AuditPropertySetter (called from AbpDbContext's SaveChanges pipeline / HandlePropertiesBeforeSave) appears to stamp any entry with EntityState.Modified implementing IHasModificationTime, without checking whether any actual business property is dirty.
Questions for the ABP team
- Is this expected/by-design behavior — i.e., does
AuditPropertySetter.SetModificationAuditPropertiesintentionally stamp anyModifiedentry regardless of which properties changed? - Is there a supported/documented way to make ABP skip audit stamping when an entity's only dirty properties are audit/concurrency-stamp columns (
LastModificationTime,LastModifierId,ConcurrencyStamp)? - Could a recent EF Core or ABP version change have made
DetectChanges()/relationship-fixup more aggressive about marking loaded-but-unmodified entities asModified(e.g. due to shadow FK re-evaluation, concurrency token comparison, or navigation collection fixup)? We did not see this behavior prior to the .NET/ABP upgrade with the same code paths. - Is
IAuditPropertySetterthe correct/supported extension point to override this (e.g., replacing the DI registration with a custom implementation that checks for non-audit dirty properties before stamping), or is there a more idiomatic ABP configuration option (e.g. underAbpAuditingOptions) we're missing?
What we've tried
- Verified our Mapperly mapping profiles already
Ignore()all audit-related members (Id,CreationTime,CreatorId,LastModificationTime,LastModifierId) on create/update DTO→entity mappings — ruled out as cause. - Applied
AsNoTracking()to the relevant repository read methods — did not resolve the issue (entity still ends upModifiedin the tracker by the timeSaveChangesruns, likely via navigation fixup from a related tracked aggregate). - As a workaround, we added a guard in our custom
AppDbContext.HandlePropertiesBeforeSave()override that detects entries where allIsModifiedproperties are in{"LastModificationTime", "LastModifierId", "ConcurrencyStamp"}and resetsentry.State = EntityState.Unchangedbefore callingUpdateConcurrencyStamp(entry). This resolves the symptom but feels like it's masking a framework-level behavior we don't fully understand.
Additional context
- Entities involved:
Patient : FullAuditedEntity<long>,FinancialCounseling : FullAuditedEntity<long>(hasICollection<FCEncounterRelation> FCEncountersnavigation). - Child entity
FCEncounterRelation(alsoFullAuditedEntity<long>) does not exhibit this false-positive — its modification-time column stays untouched in the same save batch, which is somewhat inconsistent with the parent aggregates being falsely flagged. - We'd appreciate confirmation on whether ABP intentionally propagates audit stamping to any
Modified-state entity in the sameSaveChangesbatch regardless of relation to the actually-changed root entity, and whether there's a supported switch to opt out per-property rather than per-entity.
2 Answer(s)
-
0
(1) Is this expected/by-design?
In ABP’s EF Core save pipeline, modification audit properties are applied based onEntityEntry.State == Modified, not based on “did any non-audit business property change”. So if EF Core marks an entity asModified(even due to fixup/DetectChanges side effects), ABP will setLastModificationTime/LastModifierIdfor that entry. There is no documented built-in rule that says “skip stamping if only audit/concurrency properties are dirty”.(2) Supported/documented way to skip audit stamping when only audit/concurrency props are dirty?
ABP provides a property-level mechanism, but it is for a different scenario: you can mark a specific property with[DisableAuditing(UpdateModificationProps = false)]so changing that property won’t update modification audit props. This does not directly solve your case (where the entity is alreadyModifiedfor reasons you don’t want).
Example from docs:
public class MyUser : Entity<Guid> { public string Name { get; set; } [DisableAuditing(UpdateModificationProps = false, PublishEntityEvent = false)] public string ReadCount { get; set; } }In your scenario (only audit props appear modified), there is no documented ABP option that automatically ignores/rolls back such “audit-only modifications” at the entity level.
(3) Could EF Core / ABP changes be making this more visible?
Yes, this symptom is consistent with EF Core deciding an entity isModifieddue to relationship fixup / FK synchronization / DetectChanges behavior changes across versions. Once EF Core marksPatientasModified, ABP will treat it as a real update and stamp audit props. ABP itself also has options around “navigation changes” for entity change events, which indicates the framework has had to account for EF Core navigation/FK change detection differences:PublishEntityUpdatedEventWhenNavigationChanges(defaulttrue) and related behavior are documented under entity change events.
(4) Correct extension point?
Given there’s no documented switch for “audit-only modified entries”, the most deterministic approach is exactly what you already implemented: intercept in yourDbContextsave pipeline (e.g.,HandlePropertiesBeforeSave) and reset the entry toUnchangedwhen the only modified properties are audit/concurrency-stamp ones.
Overriding/replacingIAuditPropertySettercould work, but ABP doesn’t document it as a supported “filter-out audit-only modifications” feature, and without ABP source verification available here, I can’t provide a safe/accurate replacement registration snippet.
Validated references (closest official knobs relevant to this behavior):
- ABP Audit Logging docs (property-level
DisableAuditing(UpdateModificationProps = false)): https://abp.io/docs/10.5/framework/infrastructure/audit-logging - ABP entity base classes/audit properties overview: https://abp.io/docs/10.5/framework/architecture/domain-driven-design/entities
- ABP entity change event option re navigation changes (
PublishEntityUpdatedEventWhenNavigationChanges): https://abp.io/docs/10.5/framework/infrastructure/event-bus/local/ - Related support thread showing ABP/EFC navigation-change handling touchpoints: https://abp.io/support/questions/10057
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post) -
0
Hi,
ABP updates the principal entity when one of its related entities is added, updated or deleted: it stamps the modification audit properties on it even when none of its own properties changed. That's why the entry ends up
Modifiedwith only the audit columns dirty. It started in 8.3.0, so you didn't hit it on 8.2.3. It's controlled by theUpdateAggregateRootWhenNavigationChangesoption, which istrueby default.Keep the option on and list the entities you want to skip:
Configure<AbpEntityChangeOptions>(options => { options.IgnoredUpdateAggregateRootSelectors.Add("Patient", type => type == typeof(Patient)); options.IgnoredUpdateAggregateRootSelectors.Add("FinancialCounseling", type => type == typeof(FinancialCounseling)); });AbpEntityChangeOptionsis inVolo.Abp.Domain.Entities.Events. The entity updated event is still published for these entities, and the extraConcurrencyStampupdate is skipped as well, so you can drop the guard from yourHandlePropertiesBeforeSaveoverride.There is also a global
UpdateAggregateRootWhenNavigationChanges = false, but I wouldn't turn that one off. It applies to every aggregate root in the application, including the ones coming from the ABP modules, so none of them gets its audit properties and concurrency stamp updated anymore when only its child collections change.There is no reason to replace
IAuditPropertySetterhere, it doesn't get the EF entry so it can't tell which properties are dirty.Two related changes in 10.7:
unitOfWork.DisableUpdateAggregateRootWhenNavigationChanges()disables it for a single unit of work, and the principal is now updated only when it really has a navigation property for the changed relation. So if nothing but the foreign key tiesFinancialCounselingtoPatient, 10.7 stops stampingPatientwithout any configuration.Thanks
Markdown supported.Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)