# 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`

```csharp
[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`

```csharp
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`

```csharp
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`

```csharp
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`

```csharp
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`

```csharp
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**

```csharp
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**

```csharp
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**

```csharp
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**

```csharp
[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!** 🚀


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flexbase.in/solution-structure/getting-started/features/rest-services/get-single-example.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
