Skip to content

Commit

Permalink
Audit.NET.SqlServer: Avoid disposing the DbContext when an instance i…
Browse files Browse the repository at this point in the history
…s provided
  • Loading branch information
thepirat000 committed Dec 11, 2024
1 parent c24ee6c commit 6da5fac
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 37 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ All notable changes to Audit.NET and its extensions will be documented in this f

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [27.3.2] - 2024-12-11:
- Audit.NET.SqlServer: Avoid disposing the DbContext when an instance is provided.

## [27.3.1] - 2024-12-10:
- Audit.NET.SqlServer: Introducing configuration options in the SQL Server data provider to enable the use of a custom Entity Framework DbContext for storing and querying audit events.
- Audit.EntityFramework.Core: New DbContext Data Provider to store audit events using a custom Entity Framework DbContext. (#716)
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>27.3.1</Version>
<Version>27.3.2</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<LangVersion>latest</LangVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class DbContextDataProvider<TDbContext, TEntity> : AuditDataProvider
public Action<AuditEvent, TEntity> Mapper { get; set; }

/// <summary>
/// Whether to dispose the DbContextBuilder after each operation.
/// Whether to dispose the DbContext after each operation. Default is false.
/// </summary>
public bool DisposeDbContext { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public interface ISqlServerProviderConfigurator

/// <summary>
/// Specifies the DbContext instance to use as a function of the audit event. Alternative to ConnectionString.
/// When a DbContext instance is provided using this setting, the DbContext will not be disposed by the library.
/// </summary>
/// <param name="dbContext">The DbContext instance.</param>
ISqlServerProviderConfigurator DbContext(Func<AuditEvent, DbContext> dbContext);
Expand Down
144 changes: 109 additions & 35 deletions src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class SqlDataProvider : AuditDataProvider

/// <summary>
/// The Db Context instance to use. Alternative to ConnectionString and DbConnection.
/// When a DbContext instance is provided with this setting, the DbContext will not be disposed by the library.
/// </summary>
public Setting<DbContext> DbContext { get; set; }

Check warning on line 43 in src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs

View workflow job for this annotation

GitHub Actions / build_and_test

Type of 'SqlDataProvider.DbContext' is not CLS-compliant

Expand Down Expand Up @@ -104,47 +105,94 @@ public SqlDataProvider(Action<Configuration.ISqlServerProviderConfigurator> conf
public override object InsertEvent(AuditEvent auditEvent)
{
object[] parameters = GetParametersForInsert(auditEvent);
using var ctx = CreateContext(auditEvent);
var ctx = CreateContext(auditEvent, out var isLocal);
var cmdText = GetInsertCommandText(auditEvent);

try
{
#if NET7_0_OR_GREATER
var result = ctx.Database.SqlQueryRaw<string>(cmdText, parameters);
var id = result.ToList().FirstOrDefault();
var result = ctx.Database.SqlQueryRaw<string>(cmdText, parameters);
var id = result.ToList().FirstOrDefault();
#else
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, parameters);
var id = result.ToList().FirstOrDefault()?.Value;
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, parameters);
var id = result.ToList().FirstOrDefault()?.Value;
#endif
return id;
return id;
}
finally
{
if (isLocal && ctx != null)
{
ctx.Dispose();
}
}
}

public override async Task<object> InsertEventAsync(AuditEvent auditEvent, CancellationToken cancellationToken = default)
{
var parameters = GetParametersForInsert(auditEvent);
await using var ctx = CreateContext(auditEvent);
var ctx = CreateContext(auditEvent, out var isLocal);
var cmdText = GetInsertCommandText(auditEvent);

try
{
#if NET7_0_OR_GREATER
var result = ctx.Database.SqlQueryRaw<string>(cmdText, parameters);
var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault();
var result = ctx.Database.SqlQueryRaw<string>(cmdText, parameters);
var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault();
#else
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, parameters);
var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault()?.Value;
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, parameters);
var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault()?.Value;
#endif
return id;
return id;
}
finally
{
if (isLocal && ctx != null)
{
await ctx.DisposeAsync();
}
}


}

public override void ReplaceEvent(object eventId, AuditEvent auditEvent)
{
var parameters = GetParametersForReplace(eventId, auditEvent);
using var ctx = CreateContext(auditEvent);
var ctx = CreateContext(auditEvent, out var isLocal);
var cmdText = GetReplaceCommandText(auditEvent);
ctx.Database.ExecuteSqlRaw(cmdText, parameters);

try
{
ctx.Database.ExecuteSqlRaw(cmdText, parameters);
}
finally
{
if (isLocal && ctx != null)
{
ctx.Dispose();
}
}

}

public override async Task ReplaceEventAsync(object eventId, AuditEvent auditEvent, CancellationToken cancellationToken = default)
{
var parameters = GetParametersForReplace(eventId, auditEvent);
await using var ctx = CreateContext(auditEvent);
var ctx = CreateContext(auditEvent, out var isLocal);
var cmdText = GetReplaceCommandText(auditEvent);
await ctx.Database.ExecuteSqlRawAsync(cmdText, parameters, cancellationToken);

try
{
await ctx.Database.ExecuteSqlRawAsync(cmdText, parameters, cancellationToken);
}
finally
{
if (isLocal && ctx != null)
{
await ctx.DisposeAsync();
}
}
}

public override T GetEvent<T>(object eventId)
Expand All @@ -154,22 +202,33 @@ public override T GetEvent<T>(object eventId)
return null;
}

using var ctx = CreateContext(null);
var ctx = CreateContext(null, out var isLocal);
var cmdText = GetSelectCommandText(null);

try
{
#if NET7_0_OR_GREATER
var result = ctx.Database.SqlQueryRaw<string>(cmdText, new SqlParameter("@eventId", eventId));
var json = result.FirstOrDefault();
var result = ctx.Database.SqlQueryRaw<string>(cmdText, new SqlParameter("@eventId", eventId));
var json = result.FirstOrDefault();
#else
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId));
var json = result.FirstOrDefault()?.Value;
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId));
var json = result.FirstOrDefault()?.Value;
#endif

if (json != null)
if (json != null)
{
return AuditEvent.FromJson<T>(json);
}

return null;
}
finally
{
return AuditEvent.FromJson<T>(json);
if (isLocal && ctx != null)
{
ctx.Dispose();
}
}

return null;
}

public override async Task<T> GetEventAsync<T>(object eventId, CancellationToken cancellationToken = default)
Expand All @@ -179,23 +238,34 @@ public override async Task<T> GetEventAsync<T>(object eventId, CancellationToken
return null;
}

await using var ctx = CreateContext(null);
var ctx = CreateContext(null, out var isLocal);
var cmdText = GetSelectCommandText(null);

try
{
#if NET7_0_OR_GREATER
var result = ctx.Database.SqlQueryRaw<string>(cmdText, new SqlParameter("@eventId", eventId));
var json = await result.FirstOrDefaultAsync(cancellationToken);
var result = ctx.Database.SqlQueryRaw<string>(cmdText, new SqlParameter("@eventId", eventId));
var json = await result.FirstOrDefaultAsync(cancellationToken);
#else
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId));
var json = (await result.FirstOrDefaultAsync(cancellationToken))?.Value;
var result = ctx.Set<AuditEventValueModel>().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId));
var json = (await result.FirstOrDefaultAsync(cancellationToken))?.Value;
#endif


if (json != null)
if (json != null)
{
return AuditEvent.FromJson<T>(json);
}

return null;
}
finally
{
return AuditEvent.FromJson<T>(json);
if (isLocal && ctx != null)
{
await ctx.DisposeAsync();
}
}

return null;
}

protected internal string GetFullTableName(AuditEvent auditEvent)
Expand Down Expand Up @@ -349,16 +419,20 @@ protected string GetSelectCommandText(AuditEvent auditEvent)
return cmdText;
}

protected virtual DbContext CreateContext(AuditEvent auditEvent)
protected virtual DbContext CreateContext(AuditEvent auditEvent, out bool isLocal)

Check warning on line 422 in src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs

View workflow job for this annotation

GitHub Actions / build_and_test

Return type of 'SqlDataProvider.CreateContext(AuditEvent, out bool)' is not CLS-compliant
{
// Use the DbContext if provided
var dbContext = DbContext.GetValue(auditEvent);

if (dbContext != null)
{
isLocal = false;

return dbContext;
}

isLocal = true;

// Use the connection string or the db connection
var ctxOptions = DbContextOptions.GetValue(auditEvent);
var dbConnection = DbConnection.GetValue(auditEvent);
Expand Down

0 comments on commit 6da5fac

Please sign in to comment.