---
name: ServiceSearchPatterns
description: Quy tắc chuẩn cho Search/Filter/Sort/Pagination trong Service layer (SmartTable, AutoMapper).
---

# Service Search Patterns

Tài liệu này mô tả quy tắc bắt buộc khi viết method Search trong Service.

## 1. Search Method Pattern (CHUẨN)

```csharp
public async Task<SmartTableResult<ExampleItem>> Search(SmartTableParam param)
{
    IQueryable<Example> query = _repository.Query()
        .Include(x => x.RelatedEntity1)
        .Include(x => x.RelatedEntity2)
        .Where(x => !x.IsDeleted); // Nếu có soft delete

    // 1. Lọc theo từ khóa tìm kiếm (PredicateObject pattern)
    if (param.Search.PredicateObject != null)
    {
        dynamic search = param.Search.PredicateObject;

        if (search.Keyword != null)
        {
            string keyword = search.Keyword;
            keyword = keyword.Trim().ToLower();
            query = query.Where(x => 
                x.Property1.Contains(keyword) ||
                x.Property2.ToLower().Contains(keyword) ||
                (x.RelatedEntity != null && x.RelatedEntity.Property.ToLower().Contains(keyword)));
        }

        // Các filter khác (enum, date range, FK filter...)
        if (search.Status != null)
        {
            MyStatus status = search.Status;
            query = query.Where(x => x.Status == status);
        }

        if (search.CreateDateStart != null)
        {
            DateTime createStart = search.CreateDateStart;
            var startOfDay = createStart.StartOfDay();
            query = query.Where(x => x.CreatedTime >= startOfDay);
        }

        if (search.CreateDateEnd != null)
        {
            DateTime createEnd = search.CreateDateEnd;
            var endOfDay = createEnd.EndOfDay();
            query = query.Where(x => x.CreatedTime <= endOfDay);
        }
    }

    // 2. Sắp xếp mặc định (BẮT BUỘC)
    param.Sort = new Sort() { Predicate = "CreatedTime", Reverse = true };

    // 3. Sử dụng SmartTableExtension (với AutoMapper .ToItem())
    return await query.ToSmartTableResultAsync(param, entity => entity.ToItem());
}
```

## 2. Các quy tắc BẮT BUỘC

### ✅ PHẢI làm:
1. **Luôn set default Sort** trước khi gọi `ToSmartTableResultAsync`
2. **Luôn sử dụng AutoMapper** để convert entity → DTO (dùng `.ToItem()`)
3. **Include các related entities** cần thiết cho mapping
4. **Xử lý null reference** khi access related properties
5. **Dùng `dynamic search`** với `PredicateObject` cho search filters

### ❌ KHÔNG được làm (Anti-Patterns):

```csharp
// ❌ SAI: Tạo DTO thủ công bằng Select new
return await query.ToSmartTableResultAsync(param, x => new ExampleItem
{
    Id = x.Id,
    Name = x.Name,
    // ... nhiều properties thủ công
});

// ✅ ĐÚNG: Sử dụng AutoMapper extension
return await query.ToSmartTableResultAsync(param, x => x.ToItem());
```

```csharp
// ❌ SAI: Không set default Sort
return await query.ToSmartTableResultAsync(param, x => x.ToItem());

// ✅ ĐÚNG: Set Sort trước
param.Sort = new Sort() { Predicate = "CreatedTime", Reverse = true };
return await query.ToSmartTableResultAsync(param, x => x.ToItem());
```

```csharp
// ❌ SAI: Dùng param.Search.Value (không tồn tại)
if (!string.IsNullOrEmpty(param.Search?.Value))

// ✅ ĐÚNG: Dùng PredicateObject + dynamic
if (param.Search.PredicateObject != null)
{
    dynamic search = param.Search.PredicateObject;
    if (search.Keyword != null) { ... }
}
```

## 3. AutoMapper Extension Methods

### Cấu hình Profile
```csharp
// Trong MappingProfile
CreateMap<Entity, Item>()
    .ForMember(dest => dest.RelatedName, 
        opt => opt.MapFrom(src => src.Related != null ? src.Related.Name : ""))
    .ForMember(dest => dest.ChildCount, 
        opt => opt.MapFrom(src => src.Children.Count(x => !x.IsDeleted)));
```

### Extension Methods
```csharp
// Trong MappingExtensions file
public static ItemType ToItem(this Entity entity)
{
    return entity.MapTo<Entity, ItemType>();
}

public static Entity ToEntity(this ModelType model)
{
    return model.MapTo<ModelType, Entity>();
}
```

### Sử dụng trong Service
```csharp
var items = entities.Select(x => x.ToItem()).ToList();
var model = entity.ToModel();
var entity = model.ToEntity();
```

## 4. SmartTable Components

| Class | Mô tả |
|-------|--------|
| `SmartTableParam` | Input: chứa Pagination, Search, Sort, IsExport |
| `SmartTableResult<T>` | Output: chứa data items + total count |
| `Search.PredicateObject` | JObject chứa filters từ frontend |
| `Sort.Predicate` | Tên property để sort |
| `Sort.Reverse` | true = DESC, false = ASC |
| `Pagination` | Page number + page size |
