Skip to content

Commit 01febfd

Browse files
committed
Polished before release
1 parent 2a73845 commit 01febfd

22 files changed

+399
-150
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Deploy NuGet Package
22

33
env:
4-
PROJECT_PATH: './src/PandaNuGet/PandaNuGet.csproj'
4+
PROJECT_PATH: './src/EFCore.Audit/EFCore.Audit.csproj'
55
OUTPUT_DIR: 'nupkgs'
66
NUGET_SOURCE: 'https://api.nuget.org/v3/index.json'
77
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}

EFCore.Audit.sln.DotSettings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=pandatech/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Readme.md

Lines changed: 202 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,211 @@
1-
# Pandatech.***
1+
# Pandatech.EFCore.Audit
22

3-
## Introduction
3+
`Pandatech.EFCore.Audit` is a powerful and configurable library designed to collect audit trail data from the EF Core
4+
`DbContext` change tracker. It is built with scalability and professional-grade extensibility in mind.
45

56
## Features
67

8+
- **Scalable & Configurable**: Tailor the behavior to meet your project's needs.
9+
- **Composite Key Handling**: Returns concatenated composite keys in a single property using `_` as the delimiter.
10+
- **Property Transformation**: Customize tracked properties (e.g., rename, transform, or ignore).
11+
12+
## Limitations
13+
14+
- Not atomic: Being event-based, there is a risk of losing audit data in edge cases.
15+
- Does not work with untracked operations like `AsNoTracking`, `ExecuteUpdate`, `ExecuteDelete`, etc.
16+
717
## Installation
818

9-
## Usage
19+
Install the NuGet package:
20+
21+
```bash
22+
dotnet add package Pandatech.EFCore.Audit
23+
```
24+
25+
## Integration
26+
27+
To integrate `Pandatech.EFCore.Audit` into your project,, follow these steps:
28+
29+
### 1. Configure DbContext
30+
31+
Set up your `DbContext` to include your entities:
32+
33+
```csharp
34+
public class PostgresContext(DbContextOptions options) : DbContext(options)
35+
{
36+
public DbSet<Blog> Blogs { get; set; }
37+
public DbSet<Post> Posts { get; set; }
38+
}
39+
```
40+
41+
### 2. Configure Entities
42+
43+
Entities can be set up for auditing using custom configurations. Below are examples:
44+
45+
#### Blog Entity
46+
47+
```csharp
48+
public class Blog
49+
{
50+
public int Id { get; set; }
51+
public required string Title { get; set; }
52+
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
53+
public BlogType BlogType { get; set; }
54+
public required byte[] EncryptedKey { get; set; }
55+
}
56+
57+
public class BlogAuditTrailConfiguration : AuditTrailConfigurator<Blog>
58+
{
59+
public BlogAuditTrailConfiguration()
60+
{
61+
SetReadPermission(Permission.UserPermission);
62+
WriteAuditTrailOnEvents(AuditActionType.Create, AuditActionType.Update, AuditActionType.Delete);
63+
RuleFor(s => s.EncryptedKey).Transform(Convert.ToBase64String);
64+
}
65+
}
66+
67+
public enum Permission
68+
{
69+
AdminPermission,
70+
UserPermission
71+
}
72+
```
73+
74+
#### Post Entity
75+
76+
```csharp
77+
public class Post
78+
{
79+
public int Id { get; set; }
80+
public int BlogId { get; set; }
81+
public required string Title { get; set; }
82+
public required string Content { get; set; }
83+
public Blog Blog { get; set; } = null!;
84+
}
85+
86+
public class PostAuditConfiguration : AuditTrailConfigurator<Post>
87+
{
88+
public PostAuditConfiguration()
89+
{
90+
SetServiceName("BlogService");
91+
RuleFor(s => s.Content).Ignore();
92+
RuleFor(s => s.Title).Rename("TotallyNewTitle");
93+
}
94+
}
95+
```
96+
97+
#### Configuration Details
98+
99+
- **SetServiceName:** Specifies a custom service name that will be returned during the audit trail event. This can be
100+
useful for identifying the origin of the change.
101+
- **SetReadPermission:** Assigns a predefined permission level included in the event, enabling better control over who
102+
can access the audit information as row-level security.
103+
- **WriteAuditTrailOnEvents:** Defines the specific events (`Create`, `Update`, `Delete`) on which an entity should be
104+
tracked. If this option is not configured, all events for the entity **will be tracked by default**.
105+
- **Exclusion of Configuration:** If an entity should not be audited, its configuration should be omitted entirely.
106+
Entities
107+
without configuration will not be tracked.
108+
- **Transform:** Allows you to apply a custom function to modify the value of a property before it is recorded in the
109+
audit trail. For example, this can be used to encrypt/decrypt or format data.
110+
- **Ignore:** Skips tracking of the specified property within the entity. Useful for sensitive or irrelevant data.
111+
- **Rename:** Changes the property name in the audit trail output. This is useful for aligning property names with
112+
business-specific terminology or conventions.
113+
114+
### 3. Register `DbContext` in `Program.cs`
115+
116+
Register your `DbContext` and add the audit trail interceptors:
117+
118+
```csharp
119+
public static WebApplicationBuilder AddPostgresContext<TContext>(this WebApplicationBuilder builder,
120+
string connectionString)
121+
where TContext : DbContext
122+
{
123+
builder.Services.AddDbContextPool<TContext>((sp, options) =>
124+
{
125+
options
126+
.UseNpgsql(connectionString)
127+
.AddAuditTrailInterceptors(sp);
128+
});
129+
130+
return builder;
131+
}
132+
```
133+
134+
### 4. Set Up the Audit Trail Subscriber
135+
136+
Configure how you want to handle audit trail events:
137+
138+
```csharp
139+
AuditTrailSubscriber.ConfigureAuditTrailHandler(auditTrailEventData =>
140+
{
141+
// Implement your custom logic here
142+
Console.WriteLine("Audit Trail Event Received:");
143+
Console.WriteLine(JsonSerializer.Serialize(auditTrailEventData));
144+
});
145+
```
146+
147+
### 5. Example `Program.cs`
148+
149+
Below is a simplified example of how your Program.cs might look:
150+
151+
```csharp
152+
var builder = WebApplication.CreateBuilder(args);
153+
154+
// Register audit trail configurations
155+
builder.Services.AddAuditTrail(typeof(Program).Assembly);
156+
157+
// Register DbContext with audit trail interceptors
158+
builder.AddPostgresContext<PostgresContext>(
159+
"Server=localhost;Port=5432;Database=audit_test;User Id=test;Password=test;Pooling=true;");
160+
161+
var app = builder.Build();
162+
163+
// Configure audit trail event handling
164+
AuditTrailSubscriber.ConfigureAuditTrailHandler(auditTrailEventData =>
165+
{
166+
// Implement your custom logic here
167+
Console.WriteLine("Audit Trail Event Received:");
168+
Console.WriteLine(JsonSerializer.Serialize(auditTrailEventData));
169+
});
170+
171+
app.Run();
172+
```
173+
174+
### 6. Audit Trail Event Data
175+
176+
The audit trail event data is represented by the following classes:
177+
178+
```csharp
179+
public record AuditTrailEventData(List<AuditTrailEventEntity> Entities);
180+
181+
public record AuditTrailEventEntity(
182+
string? ServiceName,
183+
AuditActionType ActionType,
184+
string EntityName,
185+
object? ReadPermission,
186+
string PrimaryKeyValue,
187+
Dictionary<string, object?> TrackedProperties);
188+
```
189+
190+
- AuditTrailEventData: Contains a list of `AuditTrailEventEntity` objects.
191+
- AuditTrailEventEntity: Represents an audited entity with its associated data.
192+
- ServiceName: The name of the service where the change originated. Configured manually using `SetServiceName`.
193+
- ActionType: The type of action performed (`Create`, `Update`, `Delete`).
194+
- EntityName: The name of the entity.
195+
- ReadPermission: The assigned permission level for accessing this audit trail. Configured manually using
196+
`SetReadPermission`.
197+
- PrimaryKeyValue: The primary key value(s) of the entity.
198+
- TrackedProperties: A dictionary containing the tracked properties and their values.
199+
200+
## Notes
201+
202+
- **Partial Property Tracking:** For `Update` actions, `TrackedProperties` only includes properties that have been
203+
modified.
204+
- **Event Handling:** The provided `Console.WriteLine` in the demo is a placeholder. You are responsible for
205+
implementing your own event handling logic.
206+
- **Database Compatibility:** Compatible with PostgreSQL and other relational databases supported by EF Core.
207+
- **Compatible with `.Net 9 +`
10208

11209
## License
12210

13-
Pandatech.*** is licensed under the MIT License.
211+
This project is licensed under the MIT License.

src/EFCore.Audit/Class1.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
public class Class1
44
{
5-
//todo refactor lib and test names using PascalCase and not adding Pandatech. prefix
6-
//Refactor NuGet package .csproj file
75
//Refactor CI/CD pipeline
86
//UPDATE README both in the project and outside of project. Outside is for github, inside is for nuget.org
97
//Make repository public in order to read nuget.org keys!

src/EFCore.Audit/Configurator/AuditTrailConfigurationLoader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace EFCore.Audit.Configurator;
44

5-
public static class AuditTrailConfigurationLoader
5+
internal static class AuditTrailConfigurationLoader
66
{
77
public static AuditTrailConfigurator LoadFromAssemblies(params Assembly[] assemblies)
88
{
@@ -17,7 +17,7 @@ public static AuditTrailConfigurator LoadFromAssemblies(params Assembly[] assemb
1717
BaseType.IsGenericType: true
1818
} &&
1919
t.BaseType.GetGenericTypeDefinition() ==
20-
typeof(AbstractAuditTrailConfigurator<>));
20+
typeof(AuditTrailConfigurator<>));
2121

2222
foreach (var type in types)
2323
{

src/EFCore.Audit/Configurator/AbstractAuditTrailConfigurator.cs renamed to src/EFCore.Audit/Configurator/AuditTrailConfigurator.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
using EFCore.Audit.Models;
1+
using System.Linq.Expressions;
2+
using EFCore.Audit.Models;
23

34
namespace EFCore.Audit.Configurator;
45

5-
using System.Linq.Expressions;
6-
76
public abstract class AbstractAuditTrailConfigurator
87
{
98
public abstract EntityAuditConfiguration Build();
109
}
1110

12-
public abstract class AbstractAuditTrailConfigurator<TEntity> : AbstractAuditTrailConfigurator where TEntity : class
11+
public abstract class AuditTrailConfigurator<TEntity> : AbstractAuditTrailConfigurator where TEntity : class
1312
{
1413
private readonly EntityAuditConfiguration _entityConfig = new();
1514

@@ -26,24 +25,23 @@ protected PropertyConfigurator<TEntity, TProperty> RuleFor<TProperty>(
2625
return new PropertyConfigurator<TEntity, TProperty>(_entityConfig.Properties[propertyName]);
2726
}
2827

29-
protected AbstractAuditTrailConfigurator<TEntity> SetReadPermission(object permission)
28+
protected AuditTrailConfigurator<TEntity> SetReadPermission(object permission)
3029
{
3130
_entityConfig.PermissionToRead = permission;
3231
return this;
3332
}
3433

35-
protected AbstractAuditTrailConfigurator<TEntity> SetServiceName(string serviceName)
34+
protected AuditTrailConfigurator<TEntity> SetServiceName(string serviceName)
3635
{
3736
_entityConfig.ServiceName = serviceName;
3837
return this;
3938
}
4039

41-
protected AbstractAuditTrailConfigurator<TEntity> WriteAuditTrailOnEvents(params AuditActionType[] auditActions)
40+
protected AuditTrailConfigurator<TEntity> WriteAuditTrailOnEvents(params AuditActionType[] auditActions)
4241
{
4342
_entityConfig.AuditActions = auditActions;
4443
return this;
4544
}
46-
4745
private static string GetPropertyName<TProperty>(Expression<Func<TEntity, TProperty>> propertyExpression)
4846
{
4947
if (propertyExpression.Body is MemberExpression memberExpression)
@@ -60,6 +58,12 @@ public PropertyConfigurator<TEntity, TProperty> Ignore()
6058
propertyConfig.Ignore = true;
6159
return this;
6260
}
61+
62+
public PropertyConfigurator<TEntity, TProperty> Rename(string newName)
63+
{
64+
propertyConfig.Name = newName;
65+
return this;
66+
}
6367

6468
public PropertyConfigurator<TEntity, TProperty> Transform<TOutput>(Func<TProperty, TOutput> transformFunc)
6569
{

src/EFCore.Audit/EFCore.Audit.csproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
<Authors>Pandatech</Authors>
1010
<Copyright>MIT</Copyright>
1111
<Version>1.0.0</Version>
12-
<PackageId>Pandatech.SET_YOUR_TITLE</PackageId>
13-
<Title>SET_YOUR_TITLE</Title>
14-
<PackageTags>Pandatech, library, SET_CUSTOM_TAGS</PackageTags>
15-
<Description>SET_YOUR_DESCRIPTION.</Description>
16-
<RepositoryUrl>SET_YOUR_GITHUB_REPO_URL</RepositoryUrl>
17-
<PackageReleaseNotes>InitialCommit</PackageReleaseNotes>
12+
<PackageId>Pandatech.EFCore.Audit</PackageId>
13+
<Title>Pandatech.EFCore.Audit</Title>
14+
<PackageTags>Pandatech;library;EFCore;Audit;DbContext;ChangeTracker;audit-trail</PackageTags>
15+
<Description>Pandatech.EFCore.Audit is a powerful and configurable library designed to collect audit trail data from the EF Core DbContext change tracker. It is built with scalability and professional-grade extensibility in mind.</Description>
16+
<RepositoryUrl>https://github.com/PandaTechAM/be-lib-efcore-audit</RepositoryUrl>
17+
<PackageReleaseNotes>Initial release of Pandatech.EFCore.Audit.</PackageReleaseNotes>
1818
</PropertyGroup>
1919

2020
<ItemGroup>
@@ -23,9 +23,9 @@
2323
</ItemGroup>
2424

2525
<ItemGroup>
26-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
27-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
28-
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
26+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0"/>
27+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0"/>
28+
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0"/>
2929
</ItemGroup>
3030

3131
</Project>

src/EFCore.Audit/Extensions/ChangeTrackerExtensions.cs renamed to src/EFCore.Audit/Extensions/AuditActionTypeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace EFCore.Audit.Extensions;
55

6-
internal static class ChangeTrackerExtensions
6+
internal static class AuditActionTypeExtensions
77
{
88
public static AuditActionType ToAuditActionType(this EntityState entityState)
99
{
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
using EFCore.Audit.Configurator;
2-
using EFCore.Audit.Interceptors;
1+
using EFCore.Audit.Interceptors;
32
using Microsoft.EntityFrameworkCore;
4-
using Microsoft.Extensions.DependencyInjection;
53

64
namespace EFCore.Audit.Extensions;
75

86
public static class DbContextOptionExtensions
97
{
10-
public static DbContextOptionsBuilder AddAuditTrailInterceptors(this DbContextOptionsBuilder optionsBuilder, IServiceProvider serviceProvider)
8+
public static DbContextOptionsBuilder AddAuditTrailInterceptors(this DbContextOptionsBuilder optionsBuilder, IServiceProvider sp)
119
{
12-
var auditTrailConfigurator = serviceProvider.GetRequiredService<AuditTrailConfigurator>();
13-
optionsBuilder.AddInterceptors(new SaveChangesAuditorInterceptor(auditTrailConfigurator));
14-
optionsBuilder.AddInterceptors(new TransactionAuditorInterceptor(auditTrailConfigurator));
10+
var httpContextAccessor = sp.GetHttpAccessor();
11+
optionsBuilder.AddInterceptors(new SaveChangesAuditorInterceptor(httpContextAccessor));
12+
optionsBuilder.AddInterceptors(new TransactionAuditorInterceptor(httpContextAccessor));
1513
return optionsBuilder;
1614
}
1715
}

0 commit comments

Comments
 (0)