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

Aspect
Get Single
Get By ID

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

  1. Criteria-Based Search: Uses business criteria instead of ID

  2. Multiple Parameters: Supports various search filters

  3. Flexible Filtering: Can search by any combination of criteria

  4. Business Logic: Applies business-specific search rules

  5. 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

  1. Database Indexing: Create indexes on commonly searched fields

  2. Query Optimization: Use appropriate WHERE clauses

  3. Parameter Validation: Validate parameters before query execution

  4. Caching: Consider caching for frequently accessed data

  5. 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