إزاي تعمل Sorting في الـ API بتاعتك بشكل سهل ومرن؟
لما تيجي تتعامل مع APIs بيبقا في الغالب بيتم إرجاع البيانات من الـ Endpoint بنفس الترتيب اللي موجود في قاعدة البيانات، وده بيبقا غالبًا مبني على الـ Primary Key. لكن لو انت محتاج تدي المستخدم (زي Front-end أو Mobile Developer) إمكانية يعمل Sort على البيانات اللي بيرجعها الـ API، محتاج تضيف خاصية للـ Sorting بناءً على عمود معين زي Name
أو Price
.
الـ Scenario اللي عندنا
احنا عايزين نعدل الـ Endpoint بتاعنا بحيث يقدر ياخد Query Parameter من الـ URL بالشكل ده:
{{url}}/api/products?sort=name
الخطوات لتطبيق الـ Sorting:
- تعديل الـ Endpoint نفسه: نعدل الـ
GetProducts
method عشان تاخدsort
كـ Parameter:
[HttpGet] // GET: /api/Products
public async Task<ActionResult<IReadOnlyList<ProductToReturnDto>>> GetProducts(string sort)
{
var spec = new ProductWithBrandAndCategorySpecifications(sort);
}
```
هنا هنبعت قيمة الـ `sort` للـ Specification الخاصة بينا اللي هتتعامل مع الـ Sorting.
هنحتاج نغير الـ Specification بتاعنا انه ياخد Parameter نوعه String
في الـ [[Specification Design Pattern]] اللي علمناها في [[Refactor GetProducts and GetProduct using Specification DP]]
---
2. **تعديل الـ Specification Design Pattern**: بما إننا بنستخدم الـ `Specification Pattern` لتنظيم الـ Queries، هنعدل الـ Specification الحالية عشان تدعم الـ Sorting.
- عندنا حاليًا اتنين Specifications:
- الـ`Criteria`: لتحديد الشرط.
- الـ`Includes`: لإضافة الـ Relationships اللي محتاجينها (زي الـ `Brand` أو `Category`).
- في الـ LINQ عندنا 4 طرق للترتيب:
- `OrderBy`
- `OrderByDescending`
- `ThenBy`
- `ThenByDescending`
لكننا هنركز بس على أول اتنين، لأننا بنرتب بناءً على عمود واحد (زي `Name` أو `Price`).
لو مش فاكر تستخدم إيه، ارجع لـ Static Query زي دي:
```csharp
return (IEnumerable<T>) await _dbContext.Set<Product>().OrderBy(p => p.Name).Include(p => p.Brand);
- إضافة خصائص جديدة للـ Base Specification: هنحتاج نضيف خصائص جديدة للـ
BaseSpecifications
عشان تدعم الـOrderBy
وOrderByDescending
.
- هنعدل الـ Interface:
public interface ISpecifications<T> where T : BaseEntity
{
Expression<Func<T, bool>> Criteria { get; set; }
List<Expression<Func<T, object>>> Includes { get; set; }
// New properties signutures
Expression<Func<T, object>> OrderBy { get; set; }
Expression<Func<T, object>> OrderByDesc { get; set; }
}
- ثم نعدل الـ Class:
public class BaseSpecifications<T> : ISpecification<T> where T : BaseEntity
{
public Expression<Func<T, bool>> Criteria { get; private set; }
public List<Expression<Func<T, object>>> Includes { get; } = new List<Expression<Func<T, object>>>();
public Expression<Func<T, object>> OrderBy { get; private set; }
public Expression<Func<T, object>> OrderByDesc { get; private set; }
public BaseSpecification()
{
}
public BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
// Two methods: To just SET
public void AddOrderBy(Expression<Func<T, object>> orderByExpression)
{
OrderBy = orderByExpression;
}
public void AddOrderByDesc(Expression<Func<T, object>> orderByDescExpression)
{
OrderByDesc = orderByDescExpression;
}
}
- تعديل الـ Specification اللي بنستخدمها: هنعدل الـ
ProductWithBrandAndCategorySpecifications
عشان تدعم الـ Sorting بناءً على قيمة الـsort
.
public class ProductWithBrandAndCategorySpecifications : BaseSpecifications<Product>
{
public ProductWithBrandAndCategorySpecifications(string sort)
{
Includes.Add(p => p.Brand);
Includes.Add(p => p.Category);
if (!string.IsNullOrEmpty(sort))
{
switch (sort)
{
case "priceAsc":
AddOrderBy(p => p.Price);
break;
case "priceDesc":
AddOrderByDesc(p => p.Price);
break;
default:
AddOrderBy(p => p.Name);
break;
}
}
else
{
AddOrderBy(p => p.Name);
}
}
}
- تعديل الـ Specifications Evaluator: هنعدل الطريقة اللي الـ
Specification
بتتطبق بيها عشان نضيف خطوة الترتيب (Sorting). وهنا لازم نطبق الترتيب بعد الـWhere
:
internal static class SpecificationsEvaluator<T> where T : BaseEntity
{
public static IQueryable<T> GetQuery(IQueryable<T> inputQuery, ISpecification<T> spec)
{
var query = inputQuery;
// Apply Criteria
if (spec.Criteria != null)
{
query = query.Where(spec.Criteria);
}
// Apply Order By
if (spec.OrderBy != null)
{
query = query.OrderBy(spec.OrderBy);
}
else if (spec.OrderByDesc != null)
{
query = query.OrderByDescending(spec.OrderByDesc);
}
// Apply Includes
query = spec.Includes.Aggregate(query, (current, include) => current.Include(include));
return query;
}
}
- نتيجة الـ Sorting:
- لو تم الترتيب بناءً على
Name
، هترجع البيانات مرتبة أبجديًا. - لو اخترت الترتيب بـ
Price
(تصاعديًا أو تنازليًا)، الترتيب هيبقا بناءً على السعر فقط.
- لو تم الترتيب بناءً على
خلاصة:
- أضفنا خاصية مرنة للـ Sorting باستخدام الـ
Specification Pattern
. - دعمنا
OrderBy
وOrderByDescending
. - قمنا بتعديل الـ Specifications Evaluator لتطبيق الترتيب بعد الـ
Where
.
النتيجة النهائية هي API بتسمح للـ Front-end أو Mobile Developer بترتيب البيانات بسهولة عن طريق Query Parameter زي ?sort=name
أو ?sort=priceDesc
.