Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions AuditSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuditSharp.EntityFrameworkC
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuditSharp.PostgreSql", "src\AuditSharp.PostgreSql\AuditSharp.PostgreSql.csproj", "{333108FB-B1BF-4C42-A8BA-87254AC37AE9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{DCDBFE60-44D2-4C58-B1DA-FD5A31602143}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuditSharp.Sample.PostgreSql", "..\sample\AuditSharp.Sample.PostgreSql\AuditSharp.Sample.PostgreSql.csproj", "{96CFEAAA-336D-4021-86BE-901EE1F97DF5}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuditSharp.MySql", "src\AuditSharp.MySql\AuditSharp.MySql.csproj", "{7AF90E7A-3DB1-4874-8ABA-CF1A5DC807AC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -21,7 +19,7 @@ Global
{8776F127-E240-48DA-ABF8-3163D64E100D} = {41BC54EC-CB94-4945-9A91-DA74698A0F1F}
{1A22120C-9D62-4A6F-9782-C45876444CC0} = {41BC54EC-CB94-4945-9A91-DA74698A0F1F}
{333108FB-B1BF-4C42-A8BA-87254AC37AE9} = {41BC54EC-CB94-4945-9A91-DA74698A0F1F}
{96CFEAAA-336D-4021-86BE-901EE1F97DF5} = {DCDBFE60-44D2-4C58-B1DA-FD5A31602143}
{7AF90E7A-3DB1-4874-8ABA-CF1A5DC807AC} = {41BC54EC-CB94-4945-9A91-DA74698A0F1F}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8776F127-E240-48DA-ABF8-3163D64E100D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand All @@ -36,9 +34,9 @@ Global
{333108FB-B1BF-4C42-A8BA-87254AC37AE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{333108FB-B1BF-4C42-A8BA-87254AC37AE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{333108FB-B1BF-4C42-A8BA-87254AC37AE9}.Release|Any CPU.Build.0 = Release|Any CPU
{96CFEAAA-336D-4021-86BE-901EE1F97DF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96CFEAAA-336D-4021-86BE-901EE1F97DF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96CFEAAA-336D-4021-86BE-901EE1F97DF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96CFEAAA-336D-4021-86BE-901EE1F97DF5}.Release|Any CPU.Build.0 = Release|Any CPU
{7AF90E7A-3DB1-4874-8ABA-CF1A5DC807AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AF90E7A-3DB1-4874-8ABA-CF1A5DC807AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AF90E7A-3DB1-4874-8ABA-CF1A5DC807AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AF90E7A-3DB1-4874-8ABA-CF1A5DC807AC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
23 changes: 23 additions & 0 deletions src/AuditSharp.MySql/AuditSharp.MySql.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AuditSharp.EntityFrameworkCore\AuditSharp.EntityFrameworkCore.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>

</Project>
13 changes: 13 additions & 0 deletions src/AuditSharp.MySql/Context/AuditSharpMySqlDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using AuditSharp.EntityFrameworkCore.Context;
using Microsoft.EntityFrameworkCore;

namespace AuditSharp.MySql.Context;

public class AuditSharpMySqlDbContext:AuditSharpCoreDbContext
{

public AuditSharpMySqlDbContext(DbContextOptions<AuditSharpMySqlDbContext> options):base(options)
{

}
}
14 changes: 14 additions & 0 deletions src/AuditSharp.MySql/Context/DesignTimeDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;

namespace AuditSharp.MySql.Context;

public class DesignTimeDbContext:IDesignTimeDbContextFactory<AuditSharpMySqlDbContext>
{
public AuditSharpMySqlDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<AuditSharpMySqlDbContext>();
optionsBuilder.UseMySql("{Your Connection String}", ServerVersion.AutoDetect("{Your Connection String}"));
return new AuditSharpMySqlDbContext(optionsBuilder.Options);
}
}
123 changes: 123 additions & 0 deletions src/AuditSharp.MySql/Extensions/Interceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System.Text.Json;
using AuditSharp.Core.Entities;
using AuditSharp.EntityFrameworkCore.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.DependencyInjection;

namespace AuditSharp.MySql.Extensions;

public class Interceptor:SaveChangesInterceptor
{
private readonly List<TrackedChange> _trackedChanges = new();

public override ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result,
CancellationToken cancellationToken = new CancellationToken())
{
TrackChanges(eventData, result);
return base.SavedChangesAsync(eventData, result, cancellationToken);
}

public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result,
CancellationToken cancellationToken = new CancellationToken())
{
ProcessAsync(eventData);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}

public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
ProcessAsync(eventData);
return base.SavingChanges(eventData, result);
}

private void ProcessAsync(DbContextEventData eventData)
{
var context = eventData.Context;

_trackedChanges.Clear();

foreach (var entry in context!.ChangeTracker.Entries().Where(e =>
e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted))
{
var oldValues = entry.State == EntityState.Added
? string.Empty
: JsonSerializer.Serialize(
entry.OriginalValues.Properties.ToDictionary(p => p.Name, p => entry.OriginalValues[p]));

var newValues = entry.State == EntityState.Deleted
? string.Empty
: JsonSerializer.Serialize(
entry.CurrentValues.Properties.ToDictionary(p => p.Name, p => entry.CurrentValues[p]));

_trackedChanges.Add(new TrackedChange(
entry.Entity.GetType().Name,
entry.State, oldValues,
newValues,
entry.Metadata.FindPrimaryKey()?.Properties,
entry
));
}
}

public override int SavedChanges(SaveChangesCompletedEventData eventData, int result)
{
TrackChanges(eventData, result);
return base.SavedChanges(eventData, result);
}
private int TrackChanges(SaveChangesCompletedEventData eventData, int result)
{
if (_trackedChanges.Count == 0) return result;

var context = eventData.Context;
if (context == null) return result;

var auditLogs = new List<AuditLog>();

foreach (var change in _trackedChanges)
{
var primaryKeyValue = change.PrimaryKeyProperties != null
? string.Join(",", change.PrimaryKeyProperties.Select(p =>
change.Entry.Property(p.Name).CurrentValue?.ToString() ?? "undefined"))
: "undefined";
var newValues = JsonSerializer.Serialize(
change.Entry.CurrentValues.Properties.ToDictionary(p => p.Name, p => change.Entry.OriginalValues[p]));
auditLogs.Add(new AuditLog(
change.EntityName,
change.State.ToString(),
change.OldValues,
newValues,
primaryKeyValue
));
}

var auditSharpContext = IoCManager.Instance.GetRequiredService<IAuditSharpContext>();
foreach (var log in auditLogs)
{
auditSharpContext.InsertAsync(log);
}

_trackedChanges.Clear();
return result;
}


private class TrackedChange(
string entityName,
EntityState state,
string oldValues,
string newValues,
IReadOnlyList<IProperty>? primaryKeyProperties,
EntityEntry entry)
{
public string EntityName { get; set; } = entityName;
public EntityState State { get; set; } = state;
public string OldValues { get; set; } = oldValues;
public string NewValues { get; set; } = newValues;
public IReadOnlyList<IProperty>? PrimaryKeyProperties { get; init; } = primaryKeyProperties;

public EntityEntry Entry { get; set; } = entry;
}
}
12 changes: 12 additions & 0 deletions src/AuditSharp.MySql/Extensions/IoCManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Extensions.DependencyInjection;

namespace AuditSharp.MySql.Extensions;

public static class IoCManager
{
public static IServiceProvider Instance;
public static void Register(this IServiceCollection services)
{
Instance = services.BuildServiceProvider();
}
}
40 changes: 40 additions & 0 deletions src/AuditSharp.MySql/Extensions/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using AuditSharp.EntityFrameworkCore.Context;
using AuditSharp.MySql.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AuditSharp.MySql.Extensions;

public static class Program
{
public static IServiceCollection AddAuditSharp(this IServiceCollection services, Action<DbContextOptionsBuilder> optionsBuilder)
{
services.AddDbContext<AuditSharpMySqlDbContext>(optionsBuilder, ServiceLifetime.Transient, ServiceLifetime.Transient);
services.AddTransient<IAuditSharpContext>(sp=> sp.GetService<AuditSharpMySqlDbContext>()!);
services.Register();
return services;
}

public static void RegisterAuditSharp(this DbContextOptionsBuilder options)
{
options.AddInterceptors(new Interceptor());
}

public static IHost UseAuditSharp(this IHost host)
{
using var serviceScope = host.Services.CreateScope();
var context = serviceScope.ServiceProvider.GetService<AuditSharpMySqlDbContext>();
var pendingMigrations = context!.Database.GetPendingMigrations();
if (pendingMigrations.Any())
{
context.Database.Migrate();
}
else
{
context.Database.EnsureCreated();
}

return host;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading