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
Flexible Filtering: Multiple ways to identify the entity
Complex Criteria: Can combine multiple filter conditions
Business Logic: Can implement complex lookup rules
Alternative Keys: Can use non-primary key fields
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)
GET Request → Controller receives request with query parameters
Service Processing → Business orchestration and query resolution
Query Handler → Database query building and execution
AutoMapper → Entity-to-DTO transformation
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
Advanced Query Building with Multiple Criteria
Business Logic Query Building
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
Performance Considerations
Optimization Strategies
Database Indexing: Ensure proper indexes on filter fields
Query Optimization: Use efficient WHERE clauses
Parameter Validation: Validate parameters early
Caching: Consider caching for frequently accessed data
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! 🚀
public GetShippingSingleDto GetShippingSingle(GetShippingSingleParams @params)
{
return _flexHost.GetFlexiQuery<GetShippingSingle>().AssignParameters(@params).Fetch();
}
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
};
}
}
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; }
}
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; }
}
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;
}
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;
}
[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);
}