Skip to content

Commit 8ac01ef

Browse files
authored
Merge pull request #2 from Clifftech123/develop
Add InvalidFilterException, enhance DbSet and Queryable extensions wi…
2 parents 3b0f72b + 0cce302 commit 8ac01ef

16 files changed

Lines changed: 1354 additions & 169 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace EfCoreKit.Abstractions.Exceptions;
2+
3+
/// <summary>
4+
/// Thrown when a <see cref="EfCoreKit.Abstractions.Models.FilterDescriptor"/> is invalid —
5+
/// e.g. a missing field name or an unsupported operator.
6+
/// </summary>
7+
public sealed class InvalidFilterException : EfCoreKitException
8+
{
9+
/// <summary>
10+
/// Initializes a new instance of the <see cref="InvalidFilterException"/> class.
11+
/// </summary>
12+
/// <param name="message">The message that describes the error.</param>
13+
public InvalidFilterException(string message) : base(message) { }
14+
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="InvalidFilterException"/> class
17+
/// with an inner exception.
18+
/// </summary>
19+
/// <param name="message">The message that describes the error.</param>
20+
/// <param name="inner">The exception that caused this exception.</param>
21+
public InvalidFilterException(string message, Exception inner) : base(message, inner) { }
22+
}

src/EfCoreKit.Core/Context/EfCoreKitDbContext.cs

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
11
using EfCoreKit.Abstractions.Interfaces;
2+
using EfCoreKit.Core.Filters;
3+
using EfCoreKit.Core.Interceptors;
24
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.Extensions.Logging;
36

47
namespace EfCoreKit.Core.Context;
58

69
/// <summary>
710
/// Base <see cref="DbContext"/> that provides automatic soft delete, audit trail,
8-
/// multi-tenancy, and bulk operation support.
11+
/// multi-tenancy, and bulk operation support via EF Core interceptors.
912
/// </summary>
13+
/// <remarks>
14+
/// This class inherits from <see cref="DbContext"/> — all standard EF Core features remain
15+
/// fully available. You can use LINQ queries, raw SQL (<c>Database.ExecuteSqlAsync</c>),
16+
/// change tracking, migrations, <c>DbSet&lt;T&gt;</c>, and any EF Core provider feature
17+
/// exactly as you would with a plain <see cref="DbContext"/>.
18+
///
19+
/// Save-pipeline behaviour (audit, soft delete, tenant enforcement) is handled by
20+
/// dedicated <see cref="Microsoft.EntityFrameworkCore.Diagnostics.SaveChangesInterceptor"/>
21+
/// instances registered in <see cref="OnConfiguring"/>. This means the same interceptors
22+
/// can be used with any <see cref="DbContext"/> — inheriting from this class is optional.
23+
/// </remarks>
1024
/// <typeparam name="TContext">The derived context type.</typeparam>
1125
public abstract class EfCoreKitDbContext<TContext> : DbContext
1226
where TContext : DbContext
1327
{
1428
private readonly EfCoreKitOptions _options;
1529
private readonly IUserProvider? _userProvider;
1630
private readonly ITenantProvider? _tenantProvider;
31+
private readonly ILoggerFactory? _loggerFactory;
1732

1833
/// <summary>
1934
/// Initializes a new instance of the <see cref="EfCoreKitDbContext{TContext}"/> class.
@@ -36,16 +51,19 @@ protected EfCoreKitDbContext(
3651
/// <param name="kitOptions">The EfCoreKit configuration options.</param>
3752
/// <param name="userProvider">The user provider for audit trail.</param>
3853
/// <param name="tenantProvider">The tenant provider for multi-tenancy.</param>
54+
/// <param name="loggerFactory">Optional logger factory for slow query logging.</param>
3955
protected EfCoreKitDbContext(
4056
DbContextOptions<TContext> options,
4157
EfCoreKitOptions kitOptions,
4258
IUserProvider? userProvider,
43-
ITenantProvider? tenantProvider)
59+
ITenantProvider? tenantProvider,
60+
ILoggerFactory? loggerFactory = null)
4461
: base(options)
4562
{
4663
_options = kitOptions ?? throw new ArgumentNullException(nameof(kitOptions));
4764
_userProvider = userProvider;
4865
_tenantProvider = tenantProvider;
66+
_loggerFactory = loggerFactory;
4967
}
5068

5169
/// <summary>
@@ -54,10 +72,24 @@ protected EfCoreKitDbContext(
5472
protected EfCoreKitOptions Options => _options;
5573

5674
/// <inheritdoc />
57-
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
75+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
5876
{
59-
OnBeforeSaveChanges();
60-
return await base.SaveChangesAsync(cancellationToken);
77+
base.OnConfiguring(optionsBuilder);
78+
79+
if (_options.AuditTrailEnabled)
80+
optionsBuilder.AddInterceptors(new AuditInterceptor(_options, _userProvider));
81+
82+
if (_options.SoftDeleteEnabled)
83+
optionsBuilder.AddInterceptors(new SoftDeleteInterceptor(_options, _userProvider));
84+
85+
if (_options.MultiTenancyEnabled)
86+
optionsBuilder.AddInterceptors(new TenantInterceptor(_options, _tenantProvider));
87+
88+
if (_options.SlowQueryThreshold is not null)
89+
{
90+
var logger = _loggerFactory?.CreateLogger<SlowQueryInterceptor>();
91+
optionsBuilder.AddInterceptors(new SlowQueryInterceptor(_options, logger));
92+
}
6193
}
6294

6395
/// <inheritdoc />
@@ -67,23 +99,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
6799
ConfigureGlobalFilters(modelBuilder);
68100
}
69101

70-
/// <summary>
71-
/// Hook called before changes are saved. Handles audit trail, soft delete, and tenant assignment.
72-
/// </summary>
73-
private void OnBeforeSaveChanges()
74-
{
75-
// TODO: Implement audit trail (HandleAuditableEntities)
76-
// TODO: Implement soft delete interception (HandleSoftDeletableEntities)
77-
// TODO: Implement tenant assignment (HandleTenantEntities)
78-
}
79-
80102
/// <summary>
81103
/// Applies global query filters for soft delete and multi-tenancy.
82104
/// </summary>
83105
/// <param name="modelBuilder">The model builder.</param>
84106
private void ConfigureGlobalFilters(ModelBuilder modelBuilder)
85107
{
86-
// TODO: Apply ISoftDeletable global filter
87-
// TODO: Apply ITenantEntity global filter
108+
if (_options.SoftDeleteEnabled)
109+
SoftDeleteQueryFilter.Apply(modelBuilder);
110+
111+
if (_options.MultiTenancyEnabled)
112+
TenantQueryFilter.Apply(modelBuilder, _tenantProvider);
88113
}
89114
}

0 commit comments

Comments
 (0)