Skip to content

Add IgnoreEntity<T>(), and stop an excluded multi-column key from throwing - #1

Merged
sadeqabuhattem merged 1 commit into
mainfrom
ignore-entity
Oct 26, 2026
Merged

sadeqabuhattem merged 1 commit into
mainfrom
ignore-entity

Conversation

@sadeqabuhattem

Copy link
Copy Markdown
Member

Why

DotNetBoost.MultiLingual adds a Translation row to every catalogue save. Translation has a multi-column primary key (EntityName, EntityId, PropertyName, LanguageCode), and the EF Core interceptor throws on any multi-column key. As a result, .UseMultiLingual(sp).UseAuditing(sp) can't be used on one context, and dotnetboost-sample records every audit entry by hand as a workaround. This PR is the library half of the fix and is meant to ship as 1.0.0-preview.2.

Bug: excluding a multi-column-key entity still threw

The error message tells you to exclude such an entity with [AuditIgnore]. But when the interceptor skipped an excluded entity, it reported the skip with the entity's key. Reading a multi-column key throws, so [AuditIgnore] and RequireOptIn failed every save anyway.

  • A skipped entity is now reported with an empty key when its key can't be turned into an address. That covers a multi-column key and also a single key longer than the 64-character column, which threw the same way. The new helper is TrackedKeys.KeyForReport.
  • Empty, not the key parts joined into one string. The library never builds a combined key, consistent with EntityKey and both key accessors. Empty is also what a report already says for a creation skipped before its key existed.
  • A multi-column key on an entity that is not excluded is still refused. The message now also suggests .IgnoreEntity<T>().

Feature: IgnoreEntity<TEntity>() / IgnoreEntities(params Type[])

This works like a class-level [AuditIgnore], for a type the application can't annotate because it doesn't own it.

services.AddAuditing()
    .UseEntityFrameworkCore<AppDbContext>()
    .DeclareEntitiesFrom(typeof(Product).Assembly)
    .IgnoreEntity<Translation>()
    .Build();
  • It follows the attribute's rule: suppressed and reported as EntityIgnored, and it takes precedence over [Audited] and RequireOptIn. Subclasses are ignored too, which also covers EF proxies. A write addressed by name, audit.For("Translation", id), is suppressed as well, and a test checks it behaves the same as a declared [AuditIgnore] type.
  • The ignored types are stored in AuditingOptions and checked next to plan.Ignored. They are not stored in the static AuditPlan cache, because an ignore is configuration (like RequireOptIn), not a fact about the type.
  • Build() refuses a type that is both declared and ignored. Declaring covers DeclareEntity, DeclareEntities and assembly scans. It also refuses an ignored type whose stored name matches a declared entity's. Because of this, an ignored type can never appear in GET /api/audit/entities.
  • It's called IgnoreEntity and not Ignore because EF Core's modelBuilder.Ignore<T>() means "don't map this type".

The only new public API is the two builder methods; PublicApi.txt is updated.

Tests

The new tests were written first and failed before the fix. The [AuditIgnore] regression test failed with the exact exception from the bug report.

  • EF capture (SQLite):
    • A Translation-shaped entity with a 4-column key saves beside an audited Product, across both create and update. Product gets its entries and Translation is reported as EntityIgnored.
    • The [AuditIgnore] multi-column-key regression.
    • An ignored entity whose single key is too long for the column.
    • Under RequireOptIn, an ignored entity is reported as EntityIgnored, not as not opted in.
    • An ignored type that an assembly scan declares is refused at Build().
    • The existing composite-key refusal test now also checks that the message mentions IgnoreEntity<.
  • Registration:
    • Suppression whether the entity is addressed by instance or by name.
    • Same results as a declared [AuditIgnore] type.
    • Takes precedence over [Audited] and RequireOptIn.
    • Other entities are still recorded, and ignoring a type twice is harmless.
    • Declared-and-ignored and same-name conflicts are refused.
    • Argument checks on both overloads.

Locally, after rebasing onto the Aspire 13.6.4 commit: the Release build is clean, and unit (189), provider (238), API (59) and surface (7) tests pass on net8.0 and net10.0. The 202 integration tests passed locally before the rebase; this PR's CI runs them on amd64.

Docs

  • README: a new paragraph on excluding entities, with the MultiLingual Translation example.
  • CHANGELOG: a new 1.0.0-preview.2 section.
  • configuration-reference.md, limitations.md and scope.md are updated.

Not in this PR

When an entity's key is generated by the database, the interceptor writes the audit entry in a second save. That second save overwrites MultiLingual translations. The sample will avoid it by assigning keys in code (HiLo).

🤖 Generated with Claude Code

…owing

The interceptor reported a skipped entity with its key, and reading a
multi-column key throws, so a class-level [AuditIgnore] on such an entity -
the refusal's own advice - still failed every save. A skip is now reported
with an empty key when the key cannot be addressed: the library never
composes a multi-column key, and empty is what a report already says for an
entity skipped before it was addressed. A non-excluded multi-column key is
still refused, and the message now names IgnoreEntity<T>().

IgnoreEntity<T>() / IgnoreEntities(params Type[]) are a class-level
[AuditIgnore] for a type the application cannot annotate - the motivating
case being DotNetBoost.MultiLingual's Translation, which shares the context
and is keyed on four columns. Same rule as the attribute: EntityIgnored,
ahead of [Audited] and RequireOptIn, subclasses included, writes by name
included. Held in the options rather than the static AuditPlan cache.
Build() refuses a type both declared and ignored, and an ignored type that
shares a declared entity's stored name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sadeqabuhattem
sadeqabuhattem merged commit 6520fa2 into main Oct 26, 2026
5 checks passed
@sadeqabuhattem
sadeqabuhattem deleted the ignore-entity branch October 26, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant