طراحی Query Pipeline حرفه ای در ASP.NET Core برای جستجو و مرتب سازی داینامیک

طراحی Query Pipeline حرفه ای در ASP.NET Core برای جستجو و مرتب سازی داینامیک

نویسنده: مرضیه تقدسی | تاریخ انتشار: 19 خرداد 1405 | تعداد بازدید: 615

در بیشتر پروژه‌های ASP.NET Core، جستجو و مرتب‌سازی معمولاً داخل کنترلر نوشته می‌شود؛ همراه با if‌های تکراری، switch-case‌های طولانی و کوئری‌های تکراری. این کار باعث پیچیدگی کد و نقض اصول Clean Architecture می‌شود.

در این مقاله، فقط روی دو قابلیت بسیار مهم تمرکز می‌کنیم:

  • ApplySearch: جستجوی داینامیک با Expression Tree
  • OrderByField: مرتب‌سازی داینامیک بر اساس نام فیلد

این دو قابلیت، پایهٔ طراحی یک Query Pipeline حرفه‌ای در محیط‌های Enterprise هستند.

1. مشکل رایج در پروژه‌ها

معمولاً جستجو و مرتب‌سازی به شکل زیر نوشته می‌شود:

if (!string.IsNullOrWhiteSpace(search))
{
    query = query.Where(x => x.Name.Contains(search));
}

if (sort == "Name")
    query = query.OrderBy(x => x.Name);
else
    query = query.OrderByDescending(x => x.Id);

مشکلات این روش:

  • تکرار کد در تمام کنترلرها
  • وابستگی شدید به موجودیت‌ها
  • غیرقابل توسعه برای فیلدهای بیشتر
  • ناخوانا شدن کنترلر
  • عدم رعایت اصول SOLID

2. طراحی جستجوی داینامیک (ApplySearch)

هدف این است که بتوانیم روی هر تعداد فیلد دلخواه، بدون تکرار کد، جستجو انجام دهیم.

public static class SearchExtensions
{
    public static IQueryable<T> ApplySearch<T>(
        this IQueryable<T> query,
        string searchTerm,
        params string[] propertyNames)
    {
        if (string.IsNullOrWhiteSpace(searchTerm) || propertyNames == null || propertyNames.Length == 0)
            return query;

        searchTerm = searchTerm.Trim();
        var parameter = Expression.Parameter(typeof(T), "x");
        Expression finalExp = null;
        var constant = Expression.Constant(searchTerm);

        foreach (var propertyName in propertyNames)
        {
            var property = Expression.Property(parameter, propertyName);
            var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            var contains = Expression.Call(property, containsMethod!, constant);
            finalExp = finalExp == null ? contains : Expression.OrElse(finalExp, contains);
        }

        var lambda = Expression.Lambda<Func<T, bool>>(finalExp, parameter);
        return query.Where(lambda);
    }
}

ویژگی‌های این نسخه:

  • بدون وابستگی به Entity خاص
  • پشتیبانی از تعداد نامحدود فیلد جستجو
  • قابل ترجمه کامل به SQL توسط EF Core
  • Zero-Duplication
  • افزایش خوانایی و تمیزی کد کنترلر

 

نمونه استفاده:


query = query.ApplySearch(searchString, "Name");
    

یا چند فیلد:


query = query.ApplySearch(searchString, "Name", "MetaTitle", "MetaDescription");
    

3. طراحی مرتب‌سازی داینامیک (OrderByField)

برای حذف switch-case های طولانی مرتب‌سازی، از Expression Tree استفاده می‌کنیم:

public static class OrderByExtensions
{
    public static IQueryable<T> OrderByField<T>(
        this IQueryable<T> q,
        string sortField,
        bool ascending)
    {
        if (string.IsNullOrWhiteSpace(sortField)) return q;

        var param = Expression.Parameter(typeof(T), "x");
        var body = Expression.PropertyOrField(param, sortField);
        var keySelector = Expression.Lambda(body, param);
        var methodName = ascending ? "OrderBy" : "OrderByDescending";
        var method = typeof(Queryable).GetMethods().First(m => m.Name == methodName && m.GetParameters().Length == 2);
        var generic = method.MakeGenericMethod(typeof(T), body.Type);

        return (IQueryable<T>)generic.Invoke(null, new object[] { q, keySelector })!;
    }
}

 

مزایای این روش:

  • مرتب‌سازی داینامیک فقط با نام فیلد
  • بدون کد اضافه در کنترلر
  • کاملاً قابل استفاده مجدد در همه موجودیت‌ها
  • ترجمه عالی در SQL

نمونه استفاده:


query = query.OrderByField(sort, true);    // صعودی
query = query.OrderByField(sort, false);   // نزولی
    

4. اجرای Pipeline نهایی

کنترلر از حالت شلوغ به 3 خط تمیز تبدیل می‌شود:

query = query.ApplySearch(searchString, "Name");
query = query.OrderByField(sort, true);
var items = await query.ToListAsync();

این ساختار موجب خوانایی بیشتر، قابلیت تست بهتر، توسعه‌پذیری بالا و یکپارچگی کامل معماری می‌شود.