# 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!** 🚀
