Entity Mapping is a configuration system within the EntityFramework Data Provider that defines how source entities are transformed into audit entities. This system enables storing audit logs as entities within the same Entity Framework model as the audited entities, providing type-safe audit trails with automatic property copying and custom transformation logic.
For general information about the EntityFramework Data Provider, see EntityFramework Data Provider. For broader Entity Framework integration patterns, see Integration Approaches.
Entity Mapping addresses three primary concerns:
The mapping system is implemented through the IAuditEntityMapping interface and AuditEntityMapping class, configured via the EntityFrameworkProviderConfigurator when setting up the EntityFrameworkDataProvider.
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs1-223 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs1-364
Sources: src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs8-363 src/Audit.EntityFramework/ConfigurationApi/MappingInfo.cs1-31 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs28-395
The AuditEntityMapping class supports three distinct mapping strategies, processed in order of precedence:
Direct mapping from source entity type to audit entity type, stored in the _mapping dictionary. This is the most common pattern for entities with corresponding audit types.
| Method Signature | Purpose |
|---|---|
Map<TSourceEntity, TAuditEntity>() | Simple type-to-type mapping with automatic property copying |
Map<TSourceEntity, TAuditEntity>(Action<AuditEvent, EventEntry, TAuditEntity>) | Mapping with synchronous action to populate audit-specific fields |
Map<TSourceEntity, TAuditEntity>(Func<AuditEvent, EventEntry, TAuditEntity, Task>) | Mapping with asynchronous action |
Map<TSourceEntity, TAuditEntity>(Func<AuditEvent, EventEntry, TAuditEntity, bool>) | Mapping with conditional inclusion (return false to exclude) |
Map<TSourceEntity>(Func<EventEntry, Type> mapper) | Dynamic type mapping based on event entry properties |
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs12-124 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs15-174
Predicate-based mapping for entities without corresponding CLR types, such as implicit join tables created by EF. These mappings are stored in the _explicitMapping list and evaluated sequentially.
| Method Signature | Purpose |
|---|---|
MapExplicit<TAuditEntity>(Func<EventEntry, bool>, Action<EventEntry, TAuditEntity>) | Maps entries matching predicate to audit type |
MapTable<TAuditEntity>(string tableName, Action<EventEntry, TAuditEntity>) | Maps specific table name to audit type |
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs126-164 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs176-236
A global action executed for all audit entities after specific mapping actions. This provides a centralized location for common audit fields like timestamps or user information.
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs166-221 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs238-292
Sources: src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs113-186 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs188-265 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs322-361
Map Order entities to OrderAudit entities with automatic property copying:
The EntityFrameworkDataProvider will automatically copy properties with matching names from Order to OrderAudit.
Sources: test/Audit.EntityFramework.Core.UnitTest/EfCoreInMemoryTests.cs505-507
Populate audit-specific fields using an action:
The action receives:
AuditEvent ev: The full audit eventEventEntry entry: The entry with entity changesTAuditEntity orderAudit: The created audit entity instanceSources: test/Audit.EntityFramework.Core.UnitTest/EfCoreInMemoryTests.cs516-523
Use boolean-returning actions to conditionally exclude audit entities:
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs31-35 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs49-57
Map entities without CLR types using predicates:
Sources: test/Audit.EntityFramework.Core.UnitTest/EfCoreInMemoryTests.cs508-515
Simplified explicit mapping by table name:
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs146-154 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs207-219
Execute common logic across all audit entities:
The common action executes after specific mapping actions, allowing centralized logic for shared audit properties.
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs166-178 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs238-244
The EntityFrameworkDataProvider automatically copies properties from source entities to audit entities when property names match, unless explicitly disabled.
The SetAuditEntityMatchedProperties method performs property copying:
entry.ColumnValues (database values) or entity properties (current values)Sources: src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs308-346 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs348-358
Use IgnoreMatchedProperties to disable automatic copying globally or per-type:
Or selectively by type:
This is useful when audit entities have different structures or when custom logic fully populates the audit entity.
Sources: src/Audit.EntityFramework/ConfigurationApi/EntityFrameworkProviderConfigurator.cs174-182 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs89-93
Map source types to different audit types based on runtime conditions:
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs119-124 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs167-174
Use AuditEntityCreator to control entity instantiation, useful for EF Core proxies or dependency injection:
The creator function receives the audit DbContext and EventEntry, allowing context-aware instantiation.
Sources: src/Audit.EntityFramework/ConfigurationApi/EntityFrameworkProviderConfigurator.cs78-82 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs61-65 test/Audit.EntityFramework.Core.UnitTest/EfCoreInMemoryTests.cs638-645
All mapping actions support async variants for I/O operations:
Sources: src/Audit.EntityFramework/ConfigurationApi/IAuditEntityMapping.cs20-26 src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs35-47
Explicit mappings are evaluated in order, with the first matching predicate winning:
Sources: src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs304-320
The EntityFrameworkDataProvider integrates mappings through three configuration methods:
| Method | Type | Purpose |
|---|---|---|
AuditTypeMapper | Func<Type, EventEntry, Type> | Retrieved from GetMapper() - handles type-based mappings |
ExplicitMapper | Func<EventEntry, Type> | Retrieved from GetExplicitMapper() - handles predicate-based mappings |
AuditEntityAction | Func<AuditEvent, EventEntry, object, Task<bool>> | Retrieved from GetAction() - combines specific and common actions |
These are set during provider configuration:
Sources: src/Audit.EntityFramework/ConfigurationApi/EntityFrameworkProviderConfigurator.cs68-76 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs67-87
The MappingInfo class stores mapping configuration for each source-to-audit relationship:
Key Properties:
EventEntry. Setting TargetType creates a mapper that returns a fixed type.bool indicating whether to include the entity in the audit.Sources: src/Audit.EntityFramework/ConfigurationApi/MappingInfo.cs7-30
When an audit entity is created, actions execute in this sequence:
MappingInfo.Action)AuditEntityAction() (from _commonAction)Each action can return false to exclude the entity from the audit. If any action returns false, subsequent actions are not executed and the entity is excluded.
Sources: src/Audit.EntityFramework/ConfigurationApi/AuditEntityMapping.cs322-361 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs152-163
For straightforward auditing where audit entities mirror source entities:
Sources: test/Audit.EntityFramework.Core.UnitTest/EfCoreInMemoryTests.cs435-461
For applications with different audit strategies per entity type:
This pattern combines type-based mapping for structured entities with explicit mapping for dynamic entities, applying common logic only where appropriate.
Sources: test/Audit.EntityFramework.Core.UnitTest/EfCoreInMemoryTests.cs500-563
When audit entities reside in a different database context:
The UseDbContext method specifies the audit context, and DisposeDbContext(true) ensures proper cleanup.
Sources: src/Audit.EntityFramework/ConfigurationApi/EntityFrameworkProviderConfigurator.cs25-29 src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs98-111
The EntityFrameworkDataProvider handles proxy types generated by EF (Castle.Proxies) to ensure correct type matching:
This ensures that both lazy-loading proxies and change-tracking proxies are correctly mapped to their base entity types.
Sources: src/Audit.EntityFramework/Providers/EntityFrameworkDataProvider.cs373-384
The AuditEntityMappingTests test class demonstrates comprehensive testing patterns for entity mappings:
| Test Category | Purpose |
|---|---|
| Generic Mapping Tests | Verify type-to-type mappings are registered correctly |
| Action Tests | Ensure synchronous and asynchronous actions execute |
| Boolean Return Tests | Validate conditional inclusion based on action results |
| Explicit Mapping Tests | Test predicate-based and table name mappings |
| Common Action Tests | Verify global action execution across all entities |
| Chaining Tests | Ensure specific and common actions execute in order |
Sources: test/Audit.EntityFramework.Core.UnitTest/AuditEntityMappingTests.cs1-560
Refresh this wiki