Get By Id Example

Overview

This document demonstrates the complete Get By ID flow using the GetProductById 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 by its unique identifier.

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_GetProductById.cs

[HttpGet()]
[Route("GetProductById/{id}")]
[ProducesResponseType(typeof(GetProductByIdDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetProductById(string id)
{
    GetProductByIdParams parameters = new GetProductByIdParams();
    parameters.Id = id;

    return RunQuerySingleService<GetProductByIdParams, GetProductByIdDto>(
                parameters, _processProductsService.GetProductById);
}

What Happens:

  • HTTP Method: GET /api/Products/GetProductById/{id}

  • Input: Product ID from URL parameter

  • Parameter Creation: Creates GetProductByIdParams with the ID

  • Action: Calls the service layer to process the query

  • Response: HTTP 200 OK with GetProductByIdDto or 404 Not Found

2. Service Layer - Business Orchestration

File: ProcessProductsService_GetProductById.cs

public GetProductByIdDto GetProductById(GetProductByIdParams @params)
{
    return _flexHost.GetFlexiQuery<GetProductById>().AssignParameters(@params).Fetch();
}

What Happens:

  • Query Resolution: Gets the FlexiQuery instance for GetProductById

  • 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: GetProductById.cs

public class GetProductById : FlexiQueryBridge<Product, GetProductByIdDto>
{
    protected readonly ILogger<GetProductById> _logger;
    protected GetProductByIdParams _params;
    protected readonly RepoFactory _repoFactory;

    public GetProductById(ILogger<GetProductById> logger, RepoFactory repoFactory)
    {
        _logger = logger;
        _repoFactory = repoFactory;
    }

    public virtual GetProductById AssignParameters(GetProductByIdParams @params)
    {
        _params = @params;
        return this;
    }

    public override GetProductByIdDto Fetch()
    {
        var result = Build<Product>().SelectTo<GetProductByIdDto>().FirstOrDefault();

        return result;
    }

    protected override IQueryable<T> Build<T>()
    {
        _repoFactory.Init(_params);

        IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>().Where(t => t.Id == _params.Id);
        return query;
    }
}

What Happens:

  • Parameter Assignment: Stores query parameters for use in query building

  • Query Building: Creates the database query with ID filtering

  • Data Projection: Uses AutoMapper to project to DTO

  • Single Result: Returns first matching entity or null

  • ID Filtering: Filters by the specific ID parameter

4. Query Parameters - Input DTO

File: GetProductByIdParams.cs

public class GetProductByIdParams : DtoBridge
{
    public string Id { get; set; }
}

What Happens:

  • ID Parameter: Contains the unique identifier for the entity

  • Simple Structure: Minimal parameters for single entity lookup

  • URL Binding: ID is automatically bound from URL parameter

5. Output DTO - Data Transfer Object

File: GetProductByIdDto.cs

public partial class GetProductByIdDto : DtoBridge 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public string CategoryId { get; set; }
    public string CategoryName { get; set; }
    public string Sku { get; set; }
    public int StockQuantity { get; set; }
    public bool IsActive { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime? LastModifiedDate { get; set; }
    public string CreatedBy { get; set; }
    public string LastModifiedBy { get; set; }
}

What Happens:

  • Complete Data: Contains all relevant fields for the entity

  • Detailed Structure: Includes all properties for full entity display

  • Related Data: May include related entity information (CategoryName)

  • Audit Fields: Includes creation and modification tracking

6. AutoMapper Configuration - Data Transformation

File: GetProductByIdMapperConfiguration.cs

public partial class GetProductByIdMapperConfiguration : FlexMapperProfile
{
    public GetProductByIdMapperConfiguration() : base()
    {
        CreateMap<Product, GetProductByIdDto>()
            .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 Other Query Operations

Get By ID vs Other Query Characteristics

Aspect
Get By ID
Get List
Get Paged List

Return Type

T (single entity)

IEnumerable<T>

FlexiPagedList<T>

Input

ID from URL

Query parameters

Query parameters

Result Count

0 or 1

0 to many

0 to many (paginated)

Use Case

Single entity details

Dropdown/lookup

Data grids/tables

Performance

Very fast (indexed lookup)

Fast for small datasets

Optimized for large datasets

Controller Method

RunQuerySingleService

RunQueryListService

RunQueryPagedService

Query Base

FlexiQueryBridge

FlexiQueryEnumerableBridge

FlexiQueryPagedListBridge

HTTP Status

200 OK or 404 Not Found

200 OK

200 OK

Get By ID-Specific Features

  1. Single Entity: Returns one entity or null

  2. ID-Based Lookup: Uses unique identifier for fast retrieval

  3. 404 Handling: Returns 404 when entity not found

  4. Complete Data: Returns all entity details

  5. Fast Performance: Optimized for indexed lookups

Common Use Cases

  • Entity Details: Display detailed information about a specific entity

  • Edit Forms: Load entity data for editing

  • View Pages: Show complete entity information

  • API Integration: Retrieve specific entity for external systems

  • Validation: Check if entity exists before operations

Flow Summary

Synchronous Flow (Data Retrieval)

  1. GET Request β†’ Controller receives request with ID parameter

  2. Service Processing β†’ Business orchestration and query resolution

  3. Query Handler β†’ Database query building and execution

  4. AutoMapper β†’ Entity-to-DTO transformation

  5. Response β†’ HTTP 200 OK with entity or 404 Not Found

No Asynchronous Flow

  • No Events: Query operations don't publish events

  • No Subscribers: No side effects or event processing

  • Immediate Response: Data is returned immediately

Query Building Patterns

Basic Query Building

protected override IQueryable<T> Build<T>()
{
    _repoFactory.Init(_params);
    IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>().Where(t => t.Id == _params.Id);
    return query;
}

Advanced Query Building

protected override IQueryable<T> Build<T>()
{
    _repoFactory.Init(_params);
    IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
    
    // Filter by ID
    query = query.Where(t => t.Id == _params.Id);
    
    // Exclude soft deleted items
    query = query.Where(t => !t.IsSoftDeleted);
    
    // Include related entities if needed
    // query = query.Include(t => t.Category);
    
    return query;
}

Error Handling Patterns

public override GetProductByIdDto Fetch()
{
    var result = Build<Product>().SelectTo<GetProductByIdDto>().FirstOrDefault();
    
    if (result == null)
    {
        _logger.LogWarning("Product with ID {ProductId} not found", _params.Id);
    }
    else
    {
        _logger.LogDebug("Product with ID {ProductId} retrieved successfully", _params.Id);
    }
    
    return result;
}

Performance Considerations

Optimization Strategies

  1. Database Indexing: Ensure ID field is properly indexed

  2. Selective Fields: Only return necessary fields in DTO

  3. Related Data: Use Include() judiciously for related entities

  4. Caching: Consider caching for frequently accessed entities

  5. Query Optimization: Use FirstOrDefault() for single entity retrieval

When to Use Get By ID vs Other Queries

Scenario
Use Get By ID
Use Get List
Use Get Paged List

Entity Details Page

βœ… Yes

❌ No

❌ No

Edit Form Loading

βœ… Yes

❌ No

❌ No

API Integration

βœ… Yes

❌ No

❌ No

Dropdown Population

❌ No

βœ… Yes

❌ No

Data Grids

❌ No

❌ No

βœ… Yes

Search Results

❌ No

βœ… Yes

βœ… Yes

Lookup Tables

❌ No

βœ… Yes

❌ No

Error Handling

HTTP Status Codes

  • 200 OK: Entity found and returned successfully

  • 404 Not Found: Entity with specified ID does not exist

  • 400 Bad Request: Invalid ID format or missing ID parameter

  • 500 Internal Server Error: Database or server error

Controller Error Handling

[HttpGet()]
[Route("GetProductById/{id}")]
[ProducesResponseType(typeof(GetProductByIdDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetProductById(string id)
{
    if (string.IsNullOrEmpty(id))
    {
        return BadRequest("Product ID is required");
    }
    
    GetProductByIdParams parameters = new GetProductByIdParams();
    parameters.Id = id;

    return RunQuerySingleService<GetProductByIdParams, GetProductByIdDto>(
                parameters, _processProductsService.GetProductById);
}

Key Benefits

  • Performance: Very fast retrieval using indexed ID lookup

  • Simplicity: Simple single entity retrieval

  • Complete Data: Returns full entity details

  • Type Safety: Strongly typed DTOs and parameters

  • AutoMapper: Automatic entity-to-DTO mapping

  • Error Handling: Built-in 404 handling for missing entities

  • No Side Effects: Read-only operations

  • Testable: Each component can be tested independently

  • Maintainable: Clear separation of concerns


This GetProductById example demonstrates how FlexBase enables clean, maintainable, and scalable single entity retrieval operations with proper error handling and performance optimization! πŸš€

Last updated