# Getting Started with Dynamic Queries Create your first dynamic query with filtering and sorting. ## Prerequisites - Svrnty.CQRS.DynamicQuery package installed - Basic understanding of CQRS queries - Entity Framework Core (or other IQueryable source) ## Installation ### Install Packages ```bash dotnet add package Svrnty.CQRS.DynamicQuery dotnet add package Svrnty.CQRS.DynamicQuery.MinimalApi ``` ### Package References ```xml ``` ## Step 1: Define Your Entity ```csharp public class Product { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Category { get; set; } = string.Empty; public decimal Price { get; set; } public int Stock { get; set; } public bool IsActive { get; set; } public DateTime CreatedAt { get; set; } } ``` ## Step 2: Create DTO ```csharp public record ProductDto { public int Id { get; init; } public string Name { get; init; } = string.Empty; public string Category { get; init; } = string.Empty; public decimal Price { get; init; } public int Stock { get; init; } } ``` ## Step 3: Define Dynamic Query ```csharp using Svrnty.CQRS.DynamicQuery.Abstractions; public record ProductDynamicQuery : IDynamicQuery { public List? Filters { get; set; } public List? Sorts { get; set; } public List? Groups { get; set; } public List? Aggregates { get; set; } } ``` That's it! The `IDynamicQuery` interface defines the structure. The framework provides the implementation. ## Step 4: Implement Queryable Provider ```csharp using Svrnty.CQRS.DynamicQuery.Abstractions; public class ProductQueryableProvider : IQueryableProvider { private readonly ApplicationDbContext _context; public ProductQueryableProvider(ApplicationDbContext context) { _context = context; } public IQueryable GetQueryable() { return _context.Products.AsNoTracking(); } } ``` ## Step 5: Register Services ```csharp var builder = WebApplication.CreateBuilder(args); // Register CQRS discovery builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultQueryDiscovery(); // Register dynamic query builder.Services.AddDynamicQuery() .AddDynamicQueryWithProvider(); // Add HTTP endpoints var app = builder.Build(); // Map dynamic query endpoints app.MapSvrntyDynamicQueries(); app.Run(); ``` **This creates endpoints:** - `GET /api/query/productDynamicQuery` (with query string parameters) - `POST /api/query/productDynamicQuery` (with JSON body) ## Step 6: Test Your Dynamic Query ### Simple Filter Query ```bash curl -X POST http://localhost:5000/api/query/productDynamicQuery \ -H "Content-Type: application/json" \ -d '{ "filters": [ { "path": "category", "operator": "Equal", "value": "Electronics" } ] }' ``` **Response:** ```json { "data": [ { "id": 1, "name": "Laptop", "category": "Electronics", "price": 999.99, "stock": 50 }, { "id": 2, "name": "Mouse", "category": "Electronics", "price": 29.99, "stock": 200 } ], "totalCount": 2 } ``` ### Filter with Sorting ```bash curl -X POST http://localhost:5000/api/query/productDynamicQuery \ -H "Content-Type: application/json" \ -d '{ "filters": [ { "path": "category", "operator": "Equal", "value": "Electronics" }, { "path": "price", "operator": "LessThanOrEqual", "value": 1000 } ], "sorts": [ { "path": "price", "descending": false } ] }' ``` ### Multiple Filters with Pagination ```bash curl -X POST http://localhost:5000/api/query/productDynamicQuery \ -H "Content-Type: application/json" \ -d '{ "filters": [ { "path": "isActive", "operator": "Equal", "value": true }, { "path": "stock", "operator": "GreaterThan", "value": 0 } ], "sorts": [ { "path": "name", "descending": false } ], "page": 1, "pageSize": 20 }' ``` ## Common Scenarios ### Scenario 1: Search by Name ```json { "filters": [ { "path": "name", "operator": "Contains", "value": "Laptop" } ] } ``` ### Scenario 2: Price Range ```json { "filters": [ { "path": "price", "operator": "GreaterThanOrEqual", "value": 100 }, { "path": "price", "operator": "LessThanOrEqual", "value": 500 } ] } ``` ### Scenario 3: Multiple Categories ```json { "filters": [ { "path": "category", "operator": "In", "value": ["Electronics", "Books", "Toys"] } ] } ``` ### Scenario 4: Recent Products ```json { "filters": [ { "path": "createdAt", "operator": "GreaterThanOrEqual", "value": "2024-01-01T00:00:00Z" } ], "sorts": [ { "path": "createdAt", "descending": true } ] } ``` ## Adding Pagination ### Built-in Pagination ```csharp public record ProductDynamicQuery : IDynamicQuery { public List? Filters { get; set; } public List? Sorts { get; set; } public List? Groups { get; set; } public List? Aggregates { get; set; } // Pagination properties public int? Page { get; set; } public int? PageSize { get; set; } } ``` ### Request with Pagination ```json { "filters": [ { "path": "category", "operator": "Equal", "value": "Electronics" } ], "page": 2, "pageSize": 10 } ``` ### Response with Pagination ```json { "data": [ /* 10 products */ ], "totalCount": 45, "page": 2, "pageSize": 10 } ``` ## Client-Side Integration ### JavaScript/TypeScript ```typescript interface DynamicQueryRequest { filters?: Array<{ path: string; operator: string; value: any; }>; sorts?: Array<{ path: string; descending: boolean; }>; page?: number; pageSize?: number; } interface DynamicQueryResponse { data: T[]; totalCount: number; page?: number; pageSize?: number; } async function searchProducts( category: string, maxPrice: number ): Promise> { const request: DynamicQueryRequest = { filters: [ { path: "category", operator: "Equal", value: category }, { path: "price", operator: "LessThanOrEqual", value: maxPrice } ], sorts: [ { path: "price", descending: false } ], page: 1, pageSize: 20 }; const response = await fetch('/api/query/productDynamicQuery', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }); return await response.json(); } ``` ### C# HttpClient ```csharp public class ProductApiClient { private readonly HttpClient _httpClient; public async Task> SearchProductsAsync( string category, decimal maxPrice) { var request = new { filters = new[] { new { path = "category", @operator = "Equal", value = category }, new { path = "price", @operator = "LessThanOrEqual", value = maxPrice } }, sorts = new[] { new { path = "price", descending = false } }, page = 1, pageSize = 20 }; var response = await _httpClient.PostAsJsonAsync( "/api/query/productDynamicQuery", request); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync>(); } } public class DynamicQueryResponse { public List Data { get; set; } = new(); public int TotalCount { get; set; } public int? Page { get; set; } public int? PageSize { get; set; } } ``` ## Next Steps Now that you have a basic dynamic query working: 1. **[Filters and Sorts](filters-and-sorts.md)** - Learn all filter operators and advanced sorting 2. **[Groups and Aggregates](groups-and-aggregates.md)** - Add grouping and aggregation 3. **[Queryable Providers](queryable-providers.md)** - Advanced queryable provider patterns 4. **[Alter Queryable Services](alter-queryable-services.md)** - Add security filters and tenant isolation 5. **[Interceptors](interceptors.md)** - Customize query behavior ## Troubleshooting ### No Results Returned **Issue:** Query returns empty array even though data exists. **Solution:** Check your queryable provider is returning data: ```csharp public IQueryable GetQueryable() { var query = _context.Products.AsNoTracking(); // Debug: Log count var count = query.Count(); _logger.LogInformation("Queryable returned {Count} products", count); return query; } ``` ### Filter Not Working **Issue:** Filter doesn't seem to apply. **Solution:** Ensure property names match exactly (case-insensitive): ```json { "filters": [ { "path": "category", "operator": "Equal", "value": "Electronics" } // ✅ "category" matches Product.Category // ❌ "Category" - works (case-insensitive) // ❌ "cat" - won't work ] } ``` ### Performance Issues **Issue:** Query is slow. **Solution:** Add database indexes: ```csharp protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.HasIndex(e => e.Category); entity.HasIndex(e => e.Price); entity.HasIndex(e => e.IsActive); }); } ``` ## See Also - [Dynamic Queries Overview](README.md) - [Filters and Sorts](filters-and-sorts.md) - [Basic Queries](../queries/basic-queries.md) - [Query Registration](../queries/query-registration.md)