public sealed class SoftDeleteInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
if (eventData.Context is null)
{
return base.SavingChangesAsync(
eventData, result, cancellationToken);
}
IEnumerable<EntityEntry<ISoftDeletable>> entries =
eventData
.Context
.ChangeTracker
.Entries<ISoftDeletable>()
.Where(e => e.State == EntityState.Deleted);
foreach (EntityEntry<ISoftDeletable> softDeletable in entries)
{
softDeletable.State = EntityState.Modified;
softDeletable.Entity.IsDeleted = true;
softDeletable.Entity.DeletedOnUtc = DateTime.UtcNow;
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
این رویکرد تضمین میکند که تمام عملیات حذف در سراسر برنامه از سیاست حذف نرم پیروی میکنند.
شما باید SoftDeleteInterceptor را با تزریق وابستگی (dependency injection) ثبت کرده و آن را با ApplicationDbContext پیکربندی کنید.
services.AddSingleton<SoftDeleteInterceptor>();
services.AddDbContext<ApplicationDbContext>(
(sp, options) => options
.UseSqlServer(connectionString)
.AddInterceptors(
sp.GetRequiredService<SoftDeleteInterceptor>()));
فیلتر کردن خودکار دادههای حذف نرم شده 🔍
برای اطمینان از اینکه رکوردهای حذف نرم شده به طور خودکار از کوئریها حذف میشوند، میتوانیم از فیلترهای کوئری سراسری (global query filters) در EF Core استفاده کنیم. ما میتوانیم فیلترهای کوئری را با استفاده از متد OnModelCreating روی موجودیتها اعمال کنیم تا رکوردهای علامتگذاری شده به عنوان حذف شده، به طور خودکار مستثنی شوند. این ویژگی نوشتن کوئریها را به شدت ساده میکند.
در اینجا نحوه پیکربندی فیلتر کوئری حذف نرم آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
}
}
یک محدودیت این است که شما نمیتوانید بیش از یک فیلتر کوئری برای هر موجودیت پیکربندی کنید.
با این حال، گاهی اوقات لازم است که رکوردهای حذف نرم شده را به صراحت در کوئریها لحاظ کنید. شما میتوانید این کار را با استفاده از متد IgnoreQueryFilters انجام دهید.
dbContex.Reviews
.IgnoreQueryFilters()
.Where(r => r.ApartmentId == apartmentId)
.ToList();
کوئریهای سریعتر با استفاده از ایندکس فیلتر شده (Filtered Index) 🚀
برای بهبود عملکرد کوئری، به ویژه در جداولی که تعداد زیادی رکورد حذف نرم شده دارند، میتوانید از ایندکسهای فیلتر شده (filtered indexes) استفاده کنید. یک ایندکس فیلتر شده فقط شامل رکوردهایی است که معیارهای مشخص شده را برآورده میکنند. این کار اندازه ایندکس را کاهش داده و زمان اجرای کوئری را برای عملیاتی که رکوردهای فیلتر شده را مستثنی میکنند، بهبود میبخشد. اکثر پایگاه دادههای محبوب از ایندکسهای فیلتر شده پشتیبانی میکنند.
در اینجا نحوه پیکربندی یک ایندکس فیلتر شده با EF Core آمده است:
public sealed class ApplicationDbContext(
DbContextOptions<UsersDbContext> options) : DbContext(options)
{
public DbSet<Review> Reviews { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Review>().HasQueryFilter(r => !r.IsDeleted);
modelBuilder.Entity<Review>()
.HasIndex(r => r.IsDeleted)
.HasFilter("IsDeleted = 0");
}
}