Get By Id Example

Overview

This document demonstrates the complete Get By ID flow using the GetShippingInfoById 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 β†’ RESTClient β†’ External System
                                                                    ↓
Response ← Controller ← Service ← Query Handler ← RESTClient ← External System

Detailed Flow Breakdown

1. GET Request
   ↓
2. Controller (API Entry Point)
   ↓
3. Service Layer (Business Orchestration)
   ↓
4. Query Handler (RESTClient Integration)
   β”œβ”€β”€ Internal DTO β†’ Request DTO Mapping
   β”œβ”€β”€ RESTClient Call to External System
   └── Response DTO β†’ Internal DTO Mapping
   ↓
5. Response (Single Entity)

Step-by-Step Implementation

1. API Controller - The Entry Point

File: ShippingController_GetShippingInfoById.cs

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

    return RunQuerySingleService<GetShippingInfoByIdParams, GetShippingInfoByIdDto>(
                parameters, _processShippingService.GetShippingInfoById);
}

What Happens:

  • HTTP Method: GET /api/Shipping/GetShippingInfoById/{id}

  • Input: Shipping ID from URL parameter

  • Parameter Creation: Creates GetShippingInfoByIdParams with the ID

  • Action: Calls the service layer to process the query

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

2. Service Layer - Business Orchestration

File: ProcessShippingService_GetShippingInfoById.cs

public GetShippingInfoByIdDto GetShippingInfoById(GetShippingInfoByIdParams @params)
{
    return _flexHost.GetFlexiQuery<GetShippingInfoById>().AssignParameters(@params).Fetch();
}

What Happens:

  • Query Resolution: Gets the FlexiQuery instance for GetShippingInfoById

  • Parameter Assignment: Assigns query parameters to the query handler

  • Query Execution: Calls the Fetch() method to execute the query

  • Result: Returns single shipping or null if not found

3. Query Handler - RESTClient Integration

File: GetShippingInfoById.cs

public class GetShippingInfoById : FlexiQueryBridge<Shipping, GetShippingInfoByIdDto>
{
    protected readonly ILogger<GetShippingInfoById> _logger;
    protected GetShippingInfoByIdParams _params;
    protected readonly ShippingRESTClient _restClient;

    public GetShippingInfoById(ILogger<GetShippingInfoById> logger, ShippingRESTClient restClient)
    {
        _logger = logger;
        _restClient = restClient;
    }

    public virtual GetShippingInfoById AssignParameters(GetShippingInfoByIdParams @params)
    {
        _params = @params;
        return this;
    }

    public override GetShippingInfoByIdDto Fetch()
    {
        try
        {
            // Convert internal parameters to Request DTO
            GetShippingInfoByIdRequestDto requestParams = new GetShippingInfoByIdRequestDto
            {
                Id = _params.Id
            };

            // Call external REST service
            var response = _restClient.GetShippingInfoById(requestParams).Result;

            if (response.IsSuccessStatusCode)
            {
                var responseContent = response.Content.ReadAsStringAsync().Result;
                var responseDto = JsonConvert.DeserializeObject<GetShippingInfoByIdResponseDto>(responseContent);
                
                // Convert Response DTO to Internal DTO
                var result = new GetShippingInfoByIdDto
                {
                    Id = responseDto.Id,
                    OrderId = responseDto.OrderId,
                    ShippingAddress = responseDto.ShippingAddress,
                    TrackingNumber = responseDto.TrackingNumber,
                    Status = ParseShippingStatus(responseDto.Status),
                    StatusDescription = responseDto.StatusDescription,
                    TotalWeight = responseDto.TotalWeight,
                    TotalVolume = responseDto.TotalVolume,
                    CreatedDate = responseDto.CreatedDate,
                    LastModifiedDate = responseDto.LastModifiedDate,
                    CreatedBy = responseDto.CreatedBy,
                    LastModifiedBy = responseDto.LastModifiedBy,
                    ShippingItems = responseDto.ShippingItems?.Select(x => new ShippingItemDto
                    {
                        ProductId = x.ProductId,
                        Quantity = x.Quantity,
                        Weight = x.Weight,
                        Volume = x.Volume
                    }).ToList()
                };

                _logger.LogDebug("Shipping info retrieved successfully for ID: {Id}", _params.Id);
                return result;
            }
            else
            {
                _logger.LogWarning("Shipping not found for ID: {Id}. Status: {StatusCode}", _params.Id, response.StatusCode);
                return null;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving shipping info for ID: {Id}", _params.Id);
            return null;
        }
    }

    private ShippingStatus ParseShippingStatus(string status)
    {
        return status?.ToLower() switch
        {
            "created" => ShippingStatus.Created,
            "in_transit" => ShippingStatus.InTransit,
            "delivered" => ShippingStatus.Delivered,
            "cancelled" => ShippingStatus.Cancelled,
            _ => ShippingStatus.Unknown
        };
    }
}

What Happens:

  • Parameter Assignment: Stores query parameters for use in RESTClient calls

  • Request Mapping: Converts internal parameters to Request DTO

  • RESTClient Call: Calls external shipping service via RESTClient

  • Response Mapping: Converts Response DTO to Internal DTO

  • Error Handling: Handles success/failure responses from external system

  • Data Transformation: Applies business logic during mapping

4. Query Parameters - Input DTO

File: GetShippingInfoByIdParams.cs

public class GetShippingInfoByIdParams : 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: GetShippingInfoByIdDto.cs

public partial class GetShippingInfoByIdDto : DtoBridge 
{
    public string Id { get; set; }
    public string OrderId { get; set; }
    public string ShippingAddress { get; set; }
    public string TrackingNumber { get; set; }
    public ShippingStatus Status { get; set; }
    public string StatusDescription { get; set; }
    public decimal TotalWeight { get; set; }
    public decimal TotalVolume { get; set; }
    public DateTime CreatedDate { get; set; }
    public DateTime? LastModifiedDate { get; set; }
    public string CreatedBy { get; set; }
    public string LastModifiedBy { get; set; }
    public ICollection<ShippingItemDto> ShippingItems { 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 (ShippingItems)

  • Audit Fields: Includes creation and modification tracking

6. AutoMapper Configuration - Data Transformation

File: GetShippingInfoByIdMapperConfiguration.cs

public partial class GetShippingInfoByIdMapperConfiguration : FlexMapperProfile
{
    public GetShippingInfoByIdMapperConfiguration() : base()
    {
        CreateMap<Shipping, GetShippingInfoByIdDto>()
            .ForMember(d => d.StatusDescription, opt => opt.MapFrom(s => s.Status.ToString()))
            .ForMember(d => d.ShippingItems, opt => opt.MapFrom(s => s.ShippingItems));
    }
}

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 shipping

  • Edit Forms: Load shipping data for editing

  • View Pages: Show complete shipping information

  • API Integration: Retrieve specific shipping for external systems

  • Validation: Check if shipping 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.ShippingItems);
    
    return query;
}

Error Handling Patterns

public override GetShippingInfoByIdDto Fetch()
{
    var result = Build<Shipping>().SelectTo<GetShippingInfoByIdDto>().FirstOrDefault();
    
    if (result == null)
    {
        _logger.LogWarning("Shipping with ID {ShippingId} not found", _params.Id);
    }
    else
    {
        _logger.LogDebug("Shipping with ID {ShippingId} 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("GetShippingInfoById/{id}")]
[ProducesResponseType(typeof(GetShippingInfoByIdDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetShippingInfoById(string id)
{
    if (string.IsNullOrEmpty(id))
    {
        return BadRequest("Shipping ID is required");
    }
    
    GetShippingInfoByIdParams parameters = new GetShippingInfoByIdParams();
    parameters.Id = id;

    return RunQuerySingleService<GetShippingInfoByIdParams, GetShippingInfoByIdDto>(
                parameters, _processShippingService.GetShippingInfoById);
}

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 GetShippingInfoById example demonstrates how FlexBase enables clean, maintainable, and scalable single entity retrieval operations with proper error handling and performance optimization! πŸš€

Last updated