Get Single Example
Overview
This document demonstrates the complete Get Single flow using the GetProductSingle feature from the EBusiness application. The flow starts with a GET request to the controller and uses Query projects instead of Handlers in the DomainHandler section for data retrieval. This returns a single entity based on specific criteria (not necessarily by ID).
Complete Flow Architecture
GET Request → Controller → Service → Query Handler → Database → Response
Detailed Flow Breakdown
1. GET Request
↓
2. Controller (API Entry Point)
↓
3. Service Layer (Business Orchestration)
↓
4. Query Handler (Data Retrieval)
↓
5. Database (Data Query)
↓
6. AutoMapper (Data Transformation)
↓
7. Response (Single Entity)
Step-by-Step Implementation
1. API Controller - The Entry Point
File: ProductsController_GetProductSingle.cs
[HttpGet()]
[Route("GetProductSingle")]
[ProducesResponseType(typeof(GetProductSingleDto), 200)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetProductSingle([FromQuery]GetProductSingleParams parameters)
{
return RunQuerySingleService<GetProductSingleParams, GetProductSingleDto>(
parameters, _processProductsService.GetProductSingle);
}
What Happens:
HTTP Method:
GET /api/Products/GetProductSingle
Input: Query parameters from URL query string
Parameter Binding: Binds query parameters to
GetProductSingleParams
Action: Calls the service layer to process the query
Response: HTTP 200 OK with
GetProductSingleDto
or 404 Not Found
2. Service Layer - Business Orchestration
File: ProcessProductsService_GetProductSingle.cs
public GetProductSingleDto GetProductSingle(GetProductSingleParams @params)
{
return _flexHost.GetFlexiQuery<GetProductSingle>().AssignParameters(@params).Fetch();
}
What Happens:
Query Resolution: Gets the FlexiQuery instance for GetProductSingle
Parameter Assignment: Assigns query parameters to the query handler
Query Execution: Calls the Fetch() method to execute the query
Result: Returns single product or null if not found
3. Query Handler - Data Retrieval
File: GetProductSingle.cs
public class GetProductSingle : FlexiQueryBridge<Product, GetProductSingleDto>
{
protected readonly ILogger<GetProductSingle> _logger;
protected GetProductSingleParams _params;
protected readonly RepoFactory _repoFactory;
public GetProductSingle(ILogger<GetProductSingle> logger, RepoFactory repoFactory)
{
_logger = logger;
_repoFactory = repoFactory;
}
public virtual GetProductSingle AssignParameters(GetProductSingleParams @params)
{
_params = @params;
return this;
}
public override GetProductSingleDto Fetch()
{
var result = Build<Product>().SelectTo<GetProductSingleDto>().FirstOrDefault();
return result;
}
protected override IQueryable<T> Build<T>()
{
_repoFactory.Init(_params);
IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
//Build Your Query Here
return query;
}
}
What Happens:
Parameter Assignment: Stores query parameters for use in query building
Query Building: Creates the database query with custom filtering
Data Projection: Uses AutoMapper to project to DTO
Single Result: Returns first matching entity or null
Custom Filtering: Applies business-specific criteria
4. Query Parameters - Input DTO
File: GetProductSingleParams.cs
public class GetProductSingleParams : DtoBridge
{
public string Sku { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
public bool? IsActive { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
}
What Happens:
Multiple Criteria: Contains various search parameters
Flexible Filtering: Allows searching by different criteria
Query Binding: Parameters are bound from URL query string
Optional Parameters: All parameters are optional for flexible searching
5. Output DTO - Data Transfer Object
File: GetProductSingleDto.cs
public partial class GetProductSingleDto : DtoBridge
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Sku { get; set; }
public string CategoryId { get; set; }
public string CategoryName { get; set; }
public int StockQuantity { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
}
What Happens:
Complete Data: Contains all relevant fields for the entity
Search Result: Represents the single entity found by criteria
Related Data: May include related entity information (CategoryName)
Audit Fields: Includes creation and modification tracking
6. AutoMapper Configuration - Data Transformation
File: GetProductSingleMapperConfiguration.cs
public partial class GetProductSingleMapperConfiguration : FlexMapperProfile
{
public GetProductSingleMapperConfiguration() : base()
{
CreateMap<Product, GetProductSingleDto>()
.ForMember(d => d.CategoryName, opt => opt.MapFrom(s => s.Category.Name))
.ForMember(d => d.IsActive, opt => opt.MapFrom(s => !s.IsSoftDeleted));
}
}
What Happens:
Entity to DTO Mapping: Maps domain entities to DTOs
Custom Mappings: Handles complex property transformations
Related Data: Maps related entity properties
Computed Fields: Handles calculated properties
Key Differences from Get By ID
Get Single vs Get By ID Characteristics
Input
Query parameters
ID from URL
Search Criteria
Multiple criteria
Single ID
Use Case
Search by business criteria
Direct ID lookup
Performance
Depends on criteria
Very fast (indexed)
Flexibility
High (multiple filters)
Low (single ID)
URL Pattern
/GetProductSingle?sku=ABC123
/GetProductById/123
Parameter Binding
[FromQuery]
URL parameter
Query Complexity
Can be complex
Simple ID filter
Get Single-Specific Features
Criteria-Based Search: Uses business criteria instead of ID
Multiple Parameters: Supports various search filters
Flexible Filtering: Can search by any combination of criteria
Business Logic: Applies business-specific search rules
Query Building: Custom query building in the
Build<T>()
method
Common Use Cases
Search by SKU: Find product by unique SKU code
Search by Name: Find product by partial or exact name match
Category Search: Find a product in a specific category
Price Range: Find product within price range
Active Products: Find active products only
Combined Criteria: Find product matching multiple criteria
Query Building Patterns
Basic Query Building
protected override IQueryable<T> Build<T>()
{
_repoFactory.Init(_params);
IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
// Filter by SKU if provided
if (!string.IsNullOrEmpty(_params.Sku))
{
query = query.Where(p => p.Sku == _params.Sku);
}
return query;
}
Advanced Query Building
protected override IQueryable<T> Build<T>()
{
_repoFactory.Init(_params);
IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
// Filter by SKU if provided
if (!string.IsNullOrEmpty(_params.Sku))
{
query = query.Where(p => p.Sku == _params.Sku);
}
// Filter by name if provided
if (!string.IsNullOrEmpty(_params.Name))
{
query = query.Where(p => p.Name.Contains(_params.Name));
}
// Filter by category if provided
if (!string.IsNullOrEmpty(_params.CategoryId))
{
query = query.Where(p => p.CategoryId == _params.CategoryId);
}
// Filter by active status if provided
if (_params.IsActive.HasValue)
{
query = query.Where(p => p.IsActive == _params.IsActive.Value);
}
// Filter by price range if provided
if (_params.MinPrice.HasValue)
{
query = query.Where(p => p.Price >= _params.MinPrice.Value);
}
if (_params.MaxPrice.HasValue)
{
query = query.Where(p => p.Price <= _params.MaxPrice.Value);
}
// Exclude soft deleted items
query = query.Where(p => !p.IsSoftDeleted);
// Order by name for consistent results
query = query.OrderBy(p => p.Name);
return query;
}
Error Handling Patterns
public override GetProductSingleDto Fetch()
{
var result = Build<Product>().SelectTo<GetProductSingleDto>().FirstOrDefault();
if (result == null)
{
_logger.LogWarning("No product found matching criteria: {Criteria}",
$"Sku: {_params.Sku}, Name: {_params.Name}, CategoryId: {_params.CategoryId}");
}
else
{
_logger.LogDebug("Product found matching criteria: {ProductId}", result.Id);
}
return result;
}
Common Search Scenarios
1. Search by SKU
GET /api/Products/GetProductSingle?sku=ABC123
Use Case: Find product by unique SKU code
Query:
WHERE Sku = 'ABC123'
Performance: Very fast (indexed field)
2. Search by Name
GET /api/Products/GetProductSingle?name=iPhone
Use Case: Find product by name (partial match)
Query:
WHERE Name LIKE '%iPhone%'
Performance: Depends on name index
3. Search by Category
GET /api/Products/GetProductSingle?categoryId=CAT001
Use Case: Find a product in specific category
Query:
WHERE CategoryId = 'CAT001'
Performance: Fast (indexed field)
4. Search by Price Range
GET /api/Products/GetProductSingle?minPrice=100&maxPrice=500
Use Case: Find product within price range
Query:
WHERE Price >= 100 AND Price <= 500
Performance: Depends on price index
5. Search by Active Status
GET /api/Products/GetProductSingle?isActive=true
Use Case: Find active products only
Query:
WHERE IsActive = true
Performance: Fast (indexed field)
6. Combined Criteria
GET /api/Products/GetProductSingle?categoryId=CAT001&isActive=true&minPrice=100
Use Case: Find active product in category with minimum price
Query:
WHERE CategoryId = 'CAT001' AND IsActive = true AND Price >= 100
Performance: Depends on combined indexes
Performance Considerations
Optimization Strategies
Database Indexing: Create indexes on commonly searched fields
Query Optimization: Use appropriate WHERE clauses
Parameter Validation: Validate parameters before query execution
Caching: Consider caching for frequently accessed data
Query Limits: Consider adding TOP 1 for performance
Indexing Recommendations
-- Create indexes for common search criteria
CREATE INDEX IX_Product_Sku ON Products (Sku);
CREATE INDEX IX_Product_Name ON Products (Name);
CREATE INDEX IX_Product_CategoryId ON Products (CategoryId);
CREATE INDEX IX_Product_IsActive ON Products (IsActive);
CREATE INDEX IX_Product_Price ON Products (Price);
CREATE INDEX IX_Product_IsSoftDeleted ON Products (IsSoftDeleted);
-- Composite index for common combinations
CREATE INDEX IX_Product_CategoryId_IsActive ON Products (CategoryId, IsActive);
CREATE INDEX IX_Product_IsActive_Price ON Products (IsActive, Price);
Error Handling
HTTP Status Codes
200 OK: Entity found and returned successfully
404 Not Found: No entity found matching criteria
400 Bad Request: Invalid parameters or missing required criteria
500 Internal Server Error: Database or server error
Controller Error Handling
[HttpGet()]
[Route("GetProductSingle")]
[ProducesResponseType(typeof(GetProductSingleDto), 200)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetProductSingle([FromQuery]GetProductSingleParams parameters)
{
// Validate that at least one search criteria is provided
if (string.IsNullOrEmpty(parameters.Sku) &&
string.IsNullOrEmpty(parameters.Name) &&
string.IsNullOrEmpty(parameters.CategoryId) &&
!parameters.IsActive.HasValue &&
!parameters.MinPrice.HasValue &&
!parameters.MaxPrice.HasValue)
{
return BadRequest("At least one search criteria must be provided");
}
return RunQuerySingleService<GetProductSingleParams, GetProductSingleDto>(
parameters, _processProductsService.GetProductSingle);
}
When to Use Get Single vs Other Queries
Use Get Single When:
Specific Search: Need to find one entity by business criteria
Unique Lookup: Search by unique business identifier (SKU, email, etc.)
Complex Criteria: Need to apply multiple search filters
Business Logic: Search requires business-specific rules
Single Result: Expect only one result or want the first match
Use Get By ID When:
Direct Lookup: Have the exact ID of the entity
Performance Critical: Need fastest possible lookup
Simple Access: Direct access to known entity
Use Get List When:
Multiple Results: Need all entities matching criteria
Dropdown Data: Populate dropdowns or lists
Small Datasets: Results are typically small
Use Get Paged List When:
Large Datasets: Results can be large
Data Grids: Display data in paginated tables
Performance: Need to limit result set size
Key Benefits
Flexible Search: Supports multiple search criteria
Business Logic: Can implement complex search rules
Single Result: Returns one entity or null
Query Parameters: Easy to use with query strings
Performance: Can be optimized with proper indexing
Type Safety: Strongly typed DTOs and parameters
AutoMapper: Automatic entity-to-DTO mapping
Error Handling: Built-in 404 handling for no results
No Side Effects: Read-only operations
Testable: Each component can be tested independently
Maintainable: Clear separation of concerns
This GetProductSingle example demonstrates how FlexBase enables flexible, criteria-based single entity retrieval operations with proper query building and performance optimization! 🚀
Last updated