# Queries Overview Queries represent read operations that retrieve data without modifying state. ## What are Queries? Queries are **interrogative requests** to fetch data from your system. They never change state and always return results. **Characteristics:** - ✅ **Question-based names** - GetUser, SearchProducts, ListOrders - ✅ **Read-only** - Never modify state - ✅ **Always return data** - Must return a result - ✅ **Idempotent** - Can call multiple times safely - ✅ **Cacheable** - Results can be cached - ✅ **Fast** - Should be optimized for performance ## Basic Query Example ```csharp // Query public record GetUserQuery { public int UserId { get; init; } } // DTO (result) public record UserDto { public int Id { get; init; } public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; } // Handler public class GetUserQueryHandler : IQueryHandler { private readonly IUserRepository _userRepository; public async Task HandleAsync(GetUserQuery query, CancellationToken cancellationToken) { var user = await _userRepository.GetByIdAsync(query.UserId, cancellationToken); if (user == null) throw new KeyNotFoundException($"User {query.UserId} not found"); return new UserDto { Id = user.Id, Name = user.Name, Email = user.Email }; } } // Registration builder.Services.AddQuery(); // Endpoints: // GET /api/query/getUser?userId=1 // POST /api/query/getUser (with JSON body) ``` ## HTTP Support Queries automatically get **both GET and POST** endpoints: ### GET with Query String ```bash curl "http://localhost:5000/api/query/getUser?userId=123" ``` ### POST with JSON Body ```bash curl -X POST http://localhost:5000/api/query/getUser \ -H "Content-Type: application/json" \ -d '{"userId": 123}' ``` ## Query Documentation ### [Basic Queries](basic-queries.md) Simple query patterns: - Single entity queries - List queries - Search queries - Projection queries ### [Query Registration](query-registration.md) How to register queries: - Basic registration - Registration with validators - Bulk registration - Organizing registrations ### [Query Authorization](query-authorization.md) Securing queries: - IQueryAuthorizationService - Row-level security - Tenant isolation - Resource ownership ### [Query Attributes](query-attributes.md) Controlling query behavior: - [QueryName] - Custom endpoint names - [IgnoreQuery] - Internal queries - [GrpcIgnore] - HTTP-only queries ## Common Query Patterns ### Pattern 1: Get by ID ```csharp public record GetOrderQuery { public int OrderId { get; init; } } public class GetOrderQueryHandler : IQueryHandler { public async Task HandleAsync(GetOrderQuery query, CancellationToken cancellationToken) { var order = await _orders.GetByIdAsync(query.OrderId, cancellationToken); if (order == null) throw new KeyNotFoundException($"Order {query.OrderId} not found"); return MapToDto(order); } } ``` ### Pattern 2: List with Pagination ```csharp public record ListUsersQuery { public int Page { get; init; } = 1; public int PageSize { get; init; } = 10; } public class ListUsersQueryHandler : IQueryHandler> { public async Task> HandleAsync(ListUsersQuery query, CancellationToken cancellationToken) { var totalCount = await _users.CountAsync(cancellationToken); var users = await _users.GetAllAsync() .Skip((query.Page - 1) * query.PageSize) .Take(query.PageSize) .ToListAsync(cancellationToken); return new PagedResult { Items = users.Select(MapToDto).ToList(), TotalCount = totalCount, Page = query.Page, PageSize = query.PageSize }; } } ``` ### Pattern 3: Search ```csharp public record SearchProductsQuery { public string Keyword { get; init; } = string.Empty; public decimal? MinPrice { get; init; } public decimal? MaxPrice { get; init; } } public class SearchProductsQueryHandler : IQueryHandler> { public async Task> HandleAsync(SearchProductsQuery query, CancellationToken cancellationToken) { var products = _products.GetAllAsync(); if (!string.IsNullOrWhiteSpace(query.Keyword)) { products = products.Where(p => p.Name.Contains(query.Keyword) || p.Description.Contains(query.Keyword)); } if (query.MinPrice.HasValue) products = products.Where(p => p.Price >= query.MinPrice.Value); if (query.MaxPrice.HasValue) products = products.Where(p => p.Price <= query.MaxPrice.Value); var result = await products.ToListAsync(cancellationToken); return result.Select(MapToDto).ToList(); } } ``` ### Pattern 4: Aggregation ```csharp public record GetOrderStatisticsQuery { public int CustomerId { get; init; } } public record OrderStatistics { public int TotalOrders { get; init; } public decimal TotalSpent { get; init; } public decimal AverageOrderValue { get; init; } public DateTime? LastOrderDate { get; init; } } public class GetOrderStatisticsQueryHandler : IQueryHandler { public async Task HandleAsync(GetOrderStatisticsQuery query, CancellationToken cancellationToken) { var orders = await _orders .Where(o => o.CustomerId == query.CustomerId) .ToListAsync(cancellationToken); return new OrderStatistics { TotalOrders = orders.Count, TotalSpent = orders.Sum(o => o.TotalAmount), AverageOrderValue = orders.Any() ? orders.Average(o => o.TotalAmount) : 0, LastOrderDate = orders.Max(o => (DateTime?)o.CreatedAt) }; } } ``` ## Best Practices ### ✅ DO - Always return DTOs, never domain entities - Keep queries simple and focused - Use pagination for large result sets - Optimize database queries (projections, indexes) - Handle "not found" cases - Use async/await consistently - Accept CancellationToken ### ❌ DON'T - Don't modify state in queries - Don't return IQueryable (always materialize) - Don't include sensitive data in DTOs - Don't fetch unnecessary data - Don't skip pagination for large datasets - Don't return null (throw KeyNotFoundException instead) ## GET vs POST ### Use GET When: - ✅ Simple parameters (IDs, strings) - ✅ No sensitive data - ✅ Results can be cached - ✅ Idempotent ### Use POST When: - ✅ Complex parameters (objects, arrays) - ✅ Sensitive data - ✅ Long query strings - ✅ Need request body **Good news:** Both are generated automatically! ## What's Next? - **[Basic Queries](basic-queries.md)** - Common query patterns - **[Query Registration](query-registration.md)** - How to register - **[Query Authorization](query-authorization.md)** - Securing queries - **[Query Attributes](query-attributes.md)** - Customization ## See Also - [Commands Overview](../commands/README.md) - [Dynamic Queries](../dynamic-queries/README.md) - [Getting Started: Your First Query](../../getting-started/04-first-query.md) - [Best Practices: Query Design](../../best-practices/query-design.md)