diff --git a/CHANGELOG.md b/CHANGELOG.md index cddde3c5..55c6effb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/Directory.Build.props b/Directory.Build.props index 5f31ce1f..1cb0f1bd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 27.3.1 + 27.3.2 false latest diff --git a/src/Audit.EntityFramework/Providers/DbContextDataProvider.cs b/src/Audit.EntityFramework/Providers/DbContextDataProvider.cs index c5d62feb..c702b1b5 100644 --- a/src/Audit.EntityFramework/Providers/DbContextDataProvider.cs +++ b/src/Audit.EntityFramework/Providers/DbContextDataProvider.cs @@ -34,7 +34,7 @@ public class DbContextDataProvider : AuditDataProvider public Action Mapper { get; set; } /// - /// Whether to dispose the DbContextBuilder after each operation. + /// Whether to dispose the DbContext after each operation. Default is false. /// public bool DisposeDbContext { get; set; } diff --git a/src/Audit.NET.SqlServer/ConfigurationApi/ISqlServerProviderConfigurator.cs b/src/Audit.NET.SqlServer/ConfigurationApi/ISqlServerProviderConfigurator.cs index 5b63ee05..abdf7357 100644 --- a/src/Audit.NET.SqlServer/ConfigurationApi/ISqlServerProviderConfigurator.cs +++ b/src/Audit.NET.SqlServer/ConfigurationApi/ISqlServerProviderConfigurator.cs @@ -32,6 +32,7 @@ public interface ISqlServerProviderConfigurator /// /// 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. /// /// The DbContext instance. ISqlServerProviderConfigurator DbContext(Func dbContext); diff --git a/src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs b/src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs index 77ed3084..d5068ed8 100644 --- a/src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs +++ b/src/Audit.NET.SqlServer/Providers/SqlDataProvider.cs @@ -38,6 +38,7 @@ public class SqlDataProvider : AuditDataProvider /// /// 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. /// public Setting DbContext { get; set; } @@ -104,47 +105,94 @@ public SqlDataProvider(Action 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(cmdText, parameters); - var id = result.ToList().FirstOrDefault(); + var result = ctx.Database.SqlQueryRaw(cmdText, parameters); + var id = result.ToList().FirstOrDefault(); #else - var result = ctx.Set().FromSqlRaw(cmdText, parameters); - var id = result.ToList().FirstOrDefault()?.Value; + var result = ctx.Set().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 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(cmdText, parameters); - var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault(); + var result = ctx.Database.SqlQueryRaw(cmdText, parameters); + var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault(); #else - var result = ctx.Set().FromSqlRaw(cmdText, parameters); - var id = (await result.ToListAsync(cancellationToken)).FirstOrDefault()?.Value; + var result = ctx.Set().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(object eventId) @@ -154,22 +202,33 @@ public override T GetEvent(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(cmdText, new SqlParameter("@eventId", eventId)); - var json = result.FirstOrDefault(); + var result = ctx.Database.SqlQueryRaw(cmdText, new SqlParameter("@eventId", eventId)); + var json = result.FirstOrDefault(); #else - var result = ctx.Set().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId)); - var json = result.FirstOrDefault()?.Value; + var result = ctx.Set().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId)); + var json = result.FirstOrDefault()?.Value; #endif - if (json != null) + if (json != null) + { + return AuditEvent.FromJson(json); + } + + return null; + } + finally { - return AuditEvent.FromJson(json); + if (isLocal && ctx != null) + { + ctx.Dispose(); + } } - - return null; } public override async Task GetEventAsync(object eventId, CancellationToken cancellationToken = default) @@ -179,23 +238,34 @@ public override async Task GetEventAsync(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(cmdText, new SqlParameter("@eventId", eventId)); - var json = await result.FirstOrDefaultAsync(cancellationToken); + var result = ctx.Database.SqlQueryRaw(cmdText, new SqlParameter("@eventId", eventId)); + var json = await result.FirstOrDefaultAsync(cancellationToken); #else - var result = ctx.Set().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId)); - var json = (await result.FirstOrDefaultAsync(cancellationToken))?.Value; + var result = ctx.Set().FromSqlRaw(cmdText, new SqlParameter("@eventId", eventId)); + var json = (await result.FirstOrDefaultAsync(cancellationToken))?.Value; #endif - if (json != null) + if (json != null) + { + return AuditEvent.FromJson(json); + } + + return null; + } + finally { - return AuditEvent.FromJson(json); + if (isLocal && ctx != null) + { + await ctx.DisposeAsync(); + } } - - return null; } protected internal string GetFullTableName(AuditEvent auditEvent) @@ -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) { // 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);