Anti-Pattern: Leaky Service Abstraction
Description
A Leaky Abstraction occurs when the implementation details of a lower layer (e.g., Data Access or Service) are exposed to or required by a higher layer (e.g., Controller or View). Ideally, a Service should provide a curated, finalized result.
Evidences in Codebase
Returning IQueryable<T>
Many services in the codebase return EITServiceResponse<IQueryable<Entity>>.
- The Leak:
IQueryableis a promise of a query, not the data itself. It exposes the underlying ORM capabilities (EF) to the consumer. - The Consumer: The Controller or View can append
.Where(),.OrderBy(), or loop through it.
Impact
- Unpredictable Execution: The SQL query is not executed inside the Service method, but triggered later, often in the View (
.cshtml) when the loop starts. - N+1 Queries: If the View accesses navigation properties (e.g.,
item.Order.Customer.Name), EF lazy-loads them one by one, causing performance disasters. - Loss of Encapsulation: The Service loses control over what data is actually fetched. Database optimization becomes impossible because the queries are constructed dynamically in the Presentation layer.
Remediation
- Return Materialized Data: Services should return
List<T>,IEnumerable<T>(materialized), or Paged results. - Return DTOs: Never return usage of
IQueryable<Entity>. Project toIQueryable<Dto>and then.ToList()or.ToArray()inside the Service boundary. Use Performance Stabilization Pillar 2: Prefetching to handle graph complexity within the Service.
2026 Audit Findings
- Occurrences: ~43 Service methods returning
IQueryable<T>. - Analysis: Forces Controllers to execute queries, promoting N+1 issues and transaction scope ambiguity.