Get Single Example

Overview

This document demonstrates the complete Get Single flow using the GetShippingSingle 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, similar to Get By ID but with more flexible filtering.

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

[HttpGet()]
[Route("GetShippingSingle")]
[ProducesResponseType(typeof(GetShippingSingleDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetShippingSingle([FromQuery]GetShippingSingleParams parameters)
{
    return RunQuerySingleService<GetShippingSingleParams, GetShippingSingleDto>(
                parameters, _processShippingService.GetShippingSingle);
}

What Happens:

  • HTTP Method: GET /api/Shipping/GetShippingSingle

  • Input: GetShippingSingleParams from query parameters

  • Action: Calls the service layer to process the query

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

2. Service Layer - Business Orchestration

File: ProcessShippingService_GetShippingSingle.cs

public GetShippingSingleDto GetShippingSingle(GetShippingSingleParams @params)
{
    return _flexHost.GetFlexiQuery<GetShippingSingle>().AssignParameters(@params).Fetch();
}

What Happens:

  • Query Resolution: Gets the FlexiQuery instance for GetShippingSingle

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

public class GetShippingSingle : FlexiQueryBridge<Shipping, GetShippingSingleDto>
{
    protected readonly ILogger<GetShippingSingle> _logger;
    protected GetShippingSingleParams _params;
    protected readonly ShippingRESTClient _restClient;

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

    public virtual GetShippingSingle AssignParameters(GetShippingSingleParams @params)
    {
        _params = @params;
        return this;
    }

    public override GetShippingSingleDto Fetch()
    {
        try
        {
            // Convert internal parameters to Request DTO
            GetShippingSingleRequestDto requestParams = new GetShippingSingleRequestDto
            {
                Id = _params.Id,
                OrderId = _params.OrderId,
                TrackingNumber = _params.TrackingNumber,
                Status = _params.Status?.ToString(),
                CreatedDate = _params.CreatedDate,
                CreatedBy = _params.CreatedBy
            };

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

            if (response.IsSuccessStatusCode)
            {
                var responseContent = response.Content.ReadAsStringAsync().Result;
                var responseDto = JsonConvert.DeserializeObject<GetShippingSingleResponseDto>(responseContent);
                
                // Convert Response DTO to Internal DTO
                var result = new GetShippingSingleDto
                {
                    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 single retrieved successfully for criteria: {Criteria}", 
                    _params.Id ?? _params.OrderId ?? _params.TrackingNumber);
                return result;
            }
            else
            {
                _logger.LogWarning("Shipping not found for criteria. Status: {StatusCode}", response.StatusCode);
                return null;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving shipping single for criteria: {Criteria}", 
                _params.Id ?? _params.OrderId ?? _params.TrackingNumber);
            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: GetShippingSingleParams.cs

public class GetShippingSingleParams : DtoBridge
{
    // Primary identifier (optional - can use other criteria)
    public string Id { get; set; }
    
    // Alternative filtering criteria
    public string OrderId { get; set; }
    public string TrackingNumber { get; set; }
    public ShippingStatus? Status { get; set; }
    
    // Additional criteria
    public DateTime? CreatedDate { get; set; }
    public string CreatedBy { get; set; }
}

What Happens:

  • Flexible Parameters: Multiple ways to identify the entity

  • Primary ID: Can use ID for direct lookup

  • Alternative Criteria: Can use OrderId, TrackingNumber, etc.

  • Query Parameters: Automatically bound from URL query string

5. Output DTO - Data Transfer Object

File: GetShippingSingleDto.cs

public partial class GetShippingSingleDto : 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: GetShippingSingleMapperConfiguration.cs

public partial class GetShippingSingleMapperConfiguration : FlexMapperProfile
{
    public GetShippingSingleMapperConfiguration() : base()
    {
        CreateMap<Shipping, GetShippingSingleDto>()
            .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 Get By ID

Get Single vs Get By ID Characteristics

Aspect
Get Single
Get By ID

Input

Query parameters

URL parameter (ID)

Filtering

Multiple criteria

Single ID only

Flexibility

High (multiple ways to find)

Low (ID only)

Use Case

Complex lookups

Direct ID lookup

Performance

Depends on criteria

Very fast (indexed)

Controller Method

RunQuerySingleService

RunQuerySingleService

Query Base

FlexiQueryBridge

FlexiQueryBridge

Get Single-Specific Features

  1. Flexible Filtering: Multiple ways to identify the entity

  2. Complex Criteria: Can combine multiple filter conditions

  3. Business Logic: Can implement complex lookup rules

  4. Alternative Keys: Can use non-primary key fields

  5. Conditional Logic: Can apply different logic based on parameters

Common Use Cases

  • Lookup by Alternative Key: Find by tracking number, order ID, etc.

  • Conditional Lookups: Different logic based on user role or context

  • Business Rules: Complex business logic for entity identification

  • API Integration: Flexible lookup for external systems

  • Search Scenarios: Find entity based on multiple criteria

Flow Summary

Synchronous Flow (Data Retrieval)

  1. GET Request β†’ Controller receives request with query parameters

  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>();
    
    // Primary ID lookup
    if (!string.IsNullOrEmpty(_params.Id))
    {
        query = query.Where(s => s.Id == _params.Id);
    }
    // Alternative criteria
    else if (!string.IsNullOrEmpty(_params.OrderId))
    {
        query = query.Where(s => s.OrderId == _params.OrderId);
    }
    else if (!string.IsNullOrEmpty(_params.TrackingNumber))
    {
        query = query.Where(s => s.TrackingNumber == _params.TrackingNumber);
    }
    
    return query;
}

Advanced Query Building with Multiple Criteria

protected override IQueryable<T> Build<T>()
{
    _repoFactory.Init(_params);
    IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
    
    // Exclude soft deleted items
    query = query.Where(s => !s.IsSoftDeleted);
    
    // Primary ID lookup (highest priority)
    if (!string.IsNullOrEmpty(_params.Id))
    {
        query = query.Where(s => s.Id == _params.Id);
    }
    else
    {
        // Alternative lookup criteria
        if (!string.IsNullOrEmpty(_params.OrderId))
        {
            query = query.Where(s => s.OrderId == _params.OrderId);
        }
        
        if (!string.IsNullOrEmpty(_params.TrackingNumber))
        {
            query = query.Where(s => s.TrackingNumber == _params.TrackingNumber);
        }
        
        if (_params.Status.HasValue)
        {
            query = query.Where(s => s.Status == _params.Status.Value);
        }
        
        if (_params.CreatedDate.HasValue)
        {
            query = query.Where(s => s.CreatedDate.Date == _params.CreatedDate.Value.Date);
        }
        
        if (!string.IsNullOrEmpty(_params.CreatedBy))
        {
            query = query.Where(s => s.CreatedBy == _params.CreatedBy);
        }
    }
    
    // Order by creation date for consistent results
    query = query.OrderByDescending(s => s.CreatedDate);
    
    return query;
}

Business Logic Query Building

protected override IQueryable<T> Build<T>()
{
    _repoFactory.Init(_params);
    IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
    
    // Exclude soft deleted items
    query = query.Where(s => !s.IsSoftDeleted);
    
    // Business logic: Find active shipping for order
    if (!string.IsNullOrEmpty(_params.OrderId))
    {
        query = query.Where(s => s.OrderId == _params.OrderId && 
                                s.Status != ShippingStatus.Cancelled &&
                                s.Status != ShippingStatus.Delivered);
    }
    
    // Business logic: Find shipping by tracking number (any status)
    if (!string.IsNullOrEmpty(_params.TrackingNumber))
    {
        query = query.Where(s => s.TrackingNumber == _params.TrackingNumber);
    }
    
    // Business logic: Find latest shipping for user
    if (!string.IsNullOrEmpty(_params.CreatedBy))
    {
        query = query.Where(s => s.CreatedBy == _params.CreatedBy)
                    .OrderByDescending(s => s.CreatedDate);
    }
    
    return query;
}

Error Handling

HTTP Status Codes

  • 200 OK: Entity found and returned successfully

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

  • 400 Bad Request: Invalid parameters or missing required criteria

  • 500 Internal Server Error: Database or server error

Controller Error Handling

[HttpGet()]
[Route("GetShippingSingle")]
[ProducesResponseType(typeof(GetShippingSingleDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetShippingSingle([FromQuery]GetShippingSingleParams parameters)
{
    // Validate that at least one criteria is provided
    if (string.IsNullOrEmpty(parameters.Id) && 
        string.IsNullOrEmpty(parameters.OrderId) && 
        string.IsNullOrEmpty(parameters.TrackingNumber))
    {
        return BadRequest("At least one search criteria must be provided");
    }
    
    return RunQuerySingleService<GetShippingSingleParams, GetShippingSingleDto>(
                parameters, _processShippingService.GetShippingSingle);
}

Performance Considerations

Optimization Strategies

  1. Database Indexing: Ensure proper indexes on filter fields

  2. Query Optimization: Use efficient WHERE clauses

  3. Parameter Validation: Validate parameters early

  4. Caching: Consider caching for frequently accessed data

  5. Query Planning: Plan queries based on most common use cases

When to Use Get Single vs Get By ID

Scenario
Use Get Single
Use Get By ID

Alternative Keys

βœ… Yes

❌ No

Complex Lookups

βœ… Yes

❌ No

Business Rules

βœ… Yes

❌ No

Direct ID Lookup

❌ No

βœ… Yes

Simple Lookups

❌ No

βœ… Yes

Performance Critical

❌ No

βœ… Yes

Key Benefits

  • Flexibility: Multiple ways to find entities

  • Business Logic: Can implement complex lookup rules

  • Alternative Keys: Support for non-primary key lookups

  • 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 GetShippingSingle example demonstrates how FlexBase enables clean, maintainable, and scalable single entity retrieval operations with flexible filtering and business logic support! πŸš€

Last updated