# 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)