1
- using Microsoft . EntityFrameworkCore ;
2
- using Microsoft . EntityFrameworkCore . ChangeTracking ;
3
1
using Microsoft . EntityFrameworkCore . Diagnostics ;
4
- using System . Linq ;
5
- using System . Text . Json ;
6
2
7
3
namespace Shiny . Auditing ;
8
4
@@ -11,114 +7,32 @@ namespace Shiny.Auditing;
11
7
// TODO: catch ExecuteUpdate & ExecuteDelete - how? ExecuteDelete isn't something I believe in with audited tables anyhow - So only ExecuteUpdate
12
8
public class AuditSaveChangesInterceptor ( IAuditInfoProvider provider ) : SaveChangesInterceptor
13
9
{
10
+ AuditScope ? auditScope ;
11
+
14
12
public override ValueTask < InterceptionResult < int > > SavingChangesAsync ( DbContextEventData eventData , InterceptionResult < int > result , CancellationToken cancellationToken = default )
15
13
{
16
- var entries = this . GetAuditEntries ( eventData ) ;
17
- eventData . Context ! . AddRange ( entries ) ;
14
+ this . auditScope = AuditScope . Create ( provider , eventData ) ;
18
15
return base . SavingChangesAsync ( eventData , result , cancellationToken ) ;
19
16
}
20
17
21
18
public override InterceptionResult < int > SavingChanges ( DbContextEventData eventData , InterceptionResult < int > result )
22
19
{
23
- var entries = this . GetAuditEntries ( eventData ) ;
24
- eventData . Context ! . AddRange ( entries ) ;
20
+ this . auditScope = AuditScope . Create ( provider , eventData ) ;
25
21
return base . SavingChanges ( eventData , result ) ;
26
22
}
27
-
28
-
29
- static DbOperation ToOperation ( EntityState state )
30
- {
31
- if ( state == EntityState . Added )
32
- return DbOperation . Insert ;
33
-
34
- if ( state == EntityState . Deleted )
35
- return DbOperation . Delete ;
36
-
37
- return DbOperation . Update ;
38
- }
39
-
40
-
41
- protected virtual List < AuditEntry > GetAuditEntries ( DbContextEventData eventData )
42
- {
43
- var entries = new List < AuditEntry > ( ) ;
44
- var changeTracker = eventData . Context ! . ChangeTracker ;
45
- changeTracker . DetectChanges ( ) ;
46
-
47
- foreach ( var entry in changeTracker . Entries ( ) )
48
- {
49
- // Dot not audit entities that are not tracked, not changed, or not of type IAuditable
50
- if ( entry . State != EntityState . Detached &&
51
- entry . State != EntityState . Unchanged &&
52
- entry . Entity is IAuditable auditable )
53
- {
54
- if ( entry . State == EntityState . Modified )
55
- {
56
- entry . CurrentValues [ nameof ( IAuditable . DateUpdated ) ] = DateTimeOffset . UtcNow ;
57
- }
58
- else if ( entry . State == EntityState . Added )
59
- {
60
- entry . CurrentValues [ nameof ( IAuditable . DateUpdated ) ] = DateTimeOffset . UtcNow ;
61
- entry . CurrentValues [ nameof ( IAuditable . DateCreated ) ] = DateTimeOffset . UtcNow ;
62
- }
63
-
64
- entry . CurrentValues [ nameof ( IAuditable . LastEditUserIdentifier ) ] = provider . UserIdentifier ;
65
- var auditEntry = new AuditEntry
66
- {
67
- Operation = ToOperation ( entry . State ) ,
68
- EntityId = entry . Properties . Single ( p => p . Metadata . IsPrimaryKey ( ) ) . CurrentValue ! . ToString ( ) ! ,
69
- EntityType = entry . Metadata . ClrType . Name ,
70
- Timestamp = DateTime . UtcNow ,
71
- ChangeSet = this . CalculateChangeSet ( entry ) , // TODO: NULL on add
72
-
73
- UserIdentifier = provider . UserIdentifier ,
74
- UserIpAddress = provider . UserIpAddress ,
75
- Tenant = provider . Tenant ,
76
- AppLocation = provider . AppLocation
77
- } ;
78
- entries . Add ( auditEntry ) ;
79
- }
80
- }
81
- return entries ;
82
- }
83
-
84
23
85
- protected virtual JsonDocument CalculateChangeSet ( EntityEntry entry )
86
- {
87
- // TODO: if I'm deleting, I want all the original values (even ignored?)
88
- var dict = new Dictionary < string , object > ( ) ;
89
- foreach ( var property in entry . Properties )
90
- {
91
- if ( this . IsAuditedProperty ( property ) )
92
- {
93
- dict . Add ( property . Metadata . Name , property . OriginalValue ?? "NULL" ) ;
94
- }
95
- }
96
-
97
- var json = JsonSerializer . SerializeToDocument ( dict ) ;
98
- return json ;
99
- }
100
24
101
-
102
- protected virtual bool IsAuditedProperty ( PropertyEntry entry )
25
+ public override async ValueTask < int > SavedChangesAsync ( SaveChangesCompletedEventData eventData , int result , CancellationToken cancellationToken = default )
103
26
{
104
- if ( ! entry . IsModified )
105
- return false ;
106
-
107
- if ( entry . OriginalValue is byte [ ] )
108
- return false ;
109
-
110
- if ( this . IsPropertyIgnored ( entry . Metadata . Name ) )
111
- return false ;
27
+ if ( this . auditScope != null )
28
+ await this . auditScope . Commit ( cancellationToken ) ;
112
29
113
- return true ;
30
+ return await base . SavedChangesAsync ( eventData , result , cancellationToken ) ;
114
31
}
115
32
116
-
117
- protected virtual bool IsPropertyIgnored ( string propertyName ) => propertyName switch
33
+ public override int SavedChanges ( SaveChangesCompletedEventData eventData , int result )
118
34
{
119
- nameof ( IAuditable . LastEditUserIdentifier ) => true ,
120
- nameof ( IAuditable . DateCreated ) => true ,
121
- nameof ( IAuditable . DateUpdated ) => true ,
122
- _ => false
123
- } ;
35
+ this . auditScope ? . Commit ( ) ;
36
+ return base . SavedChanges ( eventData , result ) ;
37
+ }
124
38
}
0 commit comments