elsa-core/src/modules/Elsa.EntityFrameworkCore.Common/ElsaDbContextBase.cs
Sipke Schoorstra 237b22bf0f
Fix tenant ID handling and query filtering logic.
Replaced direct query filter setup with a dedicated method for cleaner and more readable tenant ID filtering logic. Removed unnecessary dependencies and improved code consistency by aligning naming conventions and simplifying expressions.
2025-05-16 21:57:49 +02:00

101 lines
3.6 KiB
C#

using Elsa.Common.Entities;
using Elsa.Common.Multitenancy;
using Elsa.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.EntityFrameworkCore;
/// <summary>
/// An optional base class to implement with some opinions on certain converters to install for certain DB providers.
/// </summary>
public abstract class ElsaDbContextBase : DbContext, IElsaDbContextSchema
{
private static readonly ISet<EntityState> ModifiedEntityStates = new HashSet<EntityState>
{
EntityState.Added,
EntityState.Modified,
};
protected IServiceProvider ServiceProvider { get; }
private readonly ElsaDbContextOptions? elsaDbContextOptions;
public string? TenantId { get; set; }
/// <summary>
/// The default schema used by Elsa.
/// </summary>
public static string ElsaSchema { get; set; } = "Elsa";
/// <inheritdoc/>
public string Schema { get; }
/// <summary>
/// The table used to store the migrations history.
/// </summary>
public static string MigrationsHistoryTable { get; set; } = "__EFMigrationsHistory";
/// <summary>
/// Initializes a new instance of the <see cref="ElsaDbContextBase"/> class.
/// </summary>
protected ElsaDbContextBase(DbContextOptions options, IServiceProvider serviceProvider) : base(options)
{
ServiceProvider = serviceProvider;
elsaDbContextOptions = options.FindExtension<ElsaDbContextOptionsExtension>()?.Options;
// ReSharper disable once VirtualMemberCallInConstructor
Schema = !string.IsNullOrWhiteSpace(elsaDbContextOptions?.SchemaName) ? elsaDbContextOptions.SchemaName : ElsaSchema;
var tenantAccessor = serviceProvider.GetService<ITenantAccessor>();
var tenantId = tenantAccessor?.Tenant?.Id;
if (!string.IsNullOrWhiteSpace(tenantId))
TenantId = tenantId.NullIfEmpty();
}
/// <inheritdoc/>
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
await OnBeforeSavingAsync(cancellationToken);
return await base.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (!string.IsNullOrWhiteSpace(Schema))
modelBuilder.HasDefaultSchema(Schema);
var additionalConfigurations = elsaDbContextOptions?.GetModelConfigurations(this);
additionalConfigurations?.Invoke(modelBuilder);
using var scope = ServiceProvider.CreateScope();
var entityTypeHandlers = scope.ServiceProvider.GetServices<IEntityModelCreatingHandler>().ToList();
foreach (var entityType in modelBuilder.Model.GetEntityTypes().ToList())
{
foreach (var handler in entityTypeHandlers)
handler.Handle(this, modelBuilder, entityType);
}
}
private async Task OnBeforeSavingAsync(CancellationToken cancellationToken)
{
using var scope = ServiceProvider.CreateScope();
var handlers = scope.ServiceProvider.GetServices<IEntitySavingHandler>().ToList();
foreach (var entry in ChangeTracker.Entries().Where(IsModifiedEntity))
{
foreach (var handler in handlers)
await handler.HandleAsync(this, entry, cancellationToken);
}
}
/// <summary>
/// Determine if an entity was modified.
/// </summary>
private bool IsModifiedEntity(EntityEntry entityEntry)
{
return ModifiedEntityStates.Contains(entityEntry.State) && entityEntry.Entity is Entity;
}
}