🏷 فیلترهای کوئری نامدار در EF 10 (چندین فیلتر کوئری برای هر انتیتی)
فیلترهای کوئری سراسری (global query filters) در Entity Framework Core از دیرباز راهی مناسب برای اعمال شرایط مشترک به تمام کوئریهای یک انتیتی بودهاند. این فیلترها به ویژه در سناریوهایی مانند حذف منطقی (soft deletion) 🗑 و چند-مستأجری (multi-tenancy) 🏢 مفید هستند، جایی که شما میخواهید همان دستور WHERE به صورت خودکار به هر کوئری اضافه شود.
با این حال، نسخههای قبلی EF Core از یک محدودیت بزرگ 😩 رنج میبردند: هر نوع انتیتی فقط میتوانست یک فیلتر تعریف شده داشته باشد. اگر نیاز به ترکیب چندین شرط داشتید، یا باید عبارات && صریح مینوشتید یا فیلترها را به صورت دستی در کوئریهای خاص غیرفعال و دوباره اعمال میکردید.
با EF 10، این وضعیت تغییر میکند. ✨
قابلیت جدید فیلترهای کوئری نامدار (named query filters) به شما امکان میدهد چندین فیلتر را به یک انتیتی متصل کرده و با نام به آنها ارجاع دهید. سپس میتوانید فیلترهای فردی را در صورت نیاز غیرفعال کنید، به جای اینکه همه فیلترها را یکجا خاموش کنید.
بیایید این قابلیت جدید، چرایی اهمیت آن و چند روش عملی برای استفاده از آن را بررسی کنیم.
🔎فیلترهای کوئری (Query Filters) چه هستند؟
اگر مدتی است که از EF Core استفاده میکنید، ممکن است از قبل با فیلترهای کوئری سراسری آشنا باشید. یک فیلتر کوئری، شرطی است که EF به طور خودکار به تمام کوئریها برای یک نوع انتیتی خاص اعمال میکند. در پشت صحنه، EF هر زمان که آن انتیتی کوئری میشود، یک دستور WHERE اضافه میکند. کاربردهای معمول عبارتند از:
• حذف منطقی: فیلتر کردن ردیفهایی که IsDeleted در آنها true است.
• چند-مستأجری: فیلتر کردن بر اساس TenantId تا هر مستأجر فقط دادههای خود را ببیند.
برای مثال، یک فیلتر حذف منطقی ممکن است اینگونه پیکربندی شود:
modelBuilder.Entity<Order>()
.HasQueryFilter(order => !order.IsDeleted);
با وجود این فیلتر، هر کوئری روی Orders به طور خودکار رکوردهای حذف شده منطقی را حذف میکند. برای شامل کردن دادههای حذف شده، میتوانید IgnoreQueryFilters() را روی کوئری فراخوانی کنید. عیب این کار این است که تمام فیلترهای روی آن انتیتی غیرفعال میشوند.
استفاده از چندین فیلتر کوئری ✅
تاکنون، EF فقط یک فیلتر کوئری برای هر انتیتی مجاز میدانست. برای ترکیب فیلترها باید یک عبارت واحد با && مینوشتید:
modelBuilder.Entity<Order>()
.HasQueryFilter(order => !order.IsDeleted && order.TenantId == tenantId);
💡این کار میکند اما غیرفعال کردن انتخابی یک شرط را غیرممکن میسازد. EF 10 یک جایگزین بهتر معرفی میکند: فیلترهای کوئری نامدار.
برای متصل کردن چندین فیلتر به یک انتیتی، HasQueryFilter را با یک نام برای هر فیلتر فراخوانی کنید:
modelBuilder.Entity<Order>()
.HasQueryFilter("SoftDeletionFilter", order => !order.IsDeleted)
.HasQueryFilter("TenantFilter", order => order.TenantId == tenantId);
اکنون میتوانید فقط فیلتر حذف منطقی را خاموش کنید در حالی که فیلتر مستأجر را فعال نگه میدارید:
// تمام سفارشات (شامل حذف شدههای منطقی) برای مستأجر فعلی را برمیگرداند
var allOrders = await context.Orders
.IgnoreQueryFilters(["SoftDeletionFilter"])
.ToListAsync();
💡 نکته: استفاده از ثابتها برای نام فیلترها
فیلترهای نامدار از کلیدهای رشتهای استفاده
میکنند. هاردکد کردن این نامها میتواند باعث ایجاد "رشتههای جادویی" (magic strings) شکننده شود. برای جلوگیری از این مشکل، ثابتها را برای نام فیلترهای خود تعریف کنید.
public static class OrderFilters
{
public const string SoftDelete = nameof(SoftDelete);
public const string Tenant = nameof(Tenant);
}
modelBuilder.Entity<Order>()
.HasQueryFilter(OrderFilters.SoftDelete, order => !order.IsDeleted)
.HasQueryFilter(OrderFilters.Tenant, order => order.TenantId == tenantId);
یک رویه بهتر دیگر، پیچیدن فراخوانی ignore در یک متد توسعه (extension method) است:
public static IQueryable<Order> IncludeSoftDeleted(this IQueryable<Order> query)
=> query.IgnoreQueryFilters([OrderFilters.SoftDelete]);