Skip to content

SCRUM-390 refactor(persistence): replace IQueryable exposure with specifications - #366

Closed
Mahmoud-Alim wants to merge 1 commit into
mainfrom
refactor/decouple-application-from-ef-core
Closed

Mahmoud-Alim wants to merge 1 commit into
mainfrom
refactor/decouple-application-from-ef-core

Conversation

@Mahmoud-Alim

Copy link
Copy Markdown
Collaborator

Summary

IRepository<T>.Query() exposed raw IQueryable<T>, so ~90 Application files
composed EF-specific queries inline (Include/ThenInclude, AsNoTracking,
IgnoreQueryFilters, ToListAsync, dynamic OrderBy/Skip/Take), and
Buy2.Application referenced the Microsoft.EntityFrameworkCore package.
This PR removes Query() entirely: handlers now describe queries with
ISpecification<T> (filter + include paths + ordering + paging + tracking),
interpreted in one place by GenericRepository. The Application and Api layers
contain zero EF references; the package reference is removed from
Buy2.Application.csproj.

What Changed

Application Layer

  • New Common/Specifications/: ISpecification<T>, Specification<T> fluent
    builder (Where/Include("JobRole.Department")/OrderBy/ThenBy/Page/
    AsTracked/IgnoreFilters), Ordering<T>, PagedResult<T>.
  • IRepository<T> rewritten without IQueryable: FirstOrDefaultAsync,
    ListAsync (entity, predicate, and projection overloads), CountAsync,
    SumAsync (int/int?/decimal/decimal?), PagedAsync, AnyAsync, plus
    unchanged CRUD. ~90 handlers/validators/services migrated to it.
  • New Common/Exceptions/: DataIntegrityException,
    ConcurrencyConflictException replacing caught DbUpdateException /
    DbUpdateConcurrencyException (4 handlers).
  • GetJobsQuery no longer Includes the whole Employees collection to count
    it (perf issue [Performance] Loading Full Employee Entities Solely for Counting in GetJobsQuery #316): jobs are paged without the collection, counts come from
    a second filtered JobRoleId projection grouped in memory (2 queries, no N+1).
  • Paged handlers (GetEmployees, GetPointsTransactions, GetJobs, …) use
    PagedAsync, preserving their original page/page-size clamps.

Persistence / Database

  • GenericRepository is now the only place that translates specifications to
    EF Core (Include(string), AsNoTracking, IgnoreQueryFilters,
    OrderBy/ThenBy, Skip/Take).
  • UnitOfWork translates DbUpdateConcurrencyException
    ConcurrencyConflictException and DbUpdateException
    DataIntegrityException on every save path (including transactions).
  • No schema change, no migration, no new indexes.

Testing

  • Unit tests: full suite passes — 710/710, including the rewritten
    performance-guard tests (#310#316, #331#336), which now assert on
    captured specifications instead of intercepted IQueryable expressions.
  • TrackingRepository test fakes reimplemented as GenericRepository
    decorators counting reads; SaveAsTemplateTests updated to the translated
    exception type; GetJobsQueryHandler constructor call sites updated for the
    added IRepository<Employee> dependency.

Backward Compatibility

  • No API endpoint, DTO, or response-shape change.
  • Internal breaking change: IRepository<T> surface changed (implementers must
    implement the new members) and persistence failures now surface as
    DataIntegrityException/ConcurrencyConflictException instead of EF types —
    any caller catching DbUpdateException must be updated (all in-repo callers
    were).

Important Implementation Details

  • Tracking semantics preserved exactly: fetches that were Query(false)
    and feed Update()/Delete() use .AsTracked(); pure reads stay untracked.
    Getting this wrong caused identity-conflict failures mid-migration and was
    fixed per handler (UpdateRole, DeleteRole, Update/DeleteReward, CommitCopyShifts,
    DeleteSite).
  • Server-side Select projections became fetch-with-includes + in-memory mapping
    with null-guards (SQL LEFT-JOIN semantics previously tolerated null navigations).
  • dotnet format --verify-no-changes still reports violations, but they are
    pre-existing repo-wide (including untouched files); the new core files were
    verified clean. No formatting changes included here.
  • Depends on refactor/apply-template-focused-services (merge that first):
    the ApplyTemplate services in that branch already consume this API.

Checklist

  • Build passes (0 warnings, 0 errors)
  • Tests pass (710/710)
  • Existing data impact is understood (read-path only, no schema change)
  • No unintended breaking changes
  • Security implications have been reviewed (no auth/data-access rule changes)

@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hr-system Ready Ready Preview Sep 15, 2026 10:09pm UTC

@Mahmoud-Alim Mahmoud-Alim changed the title refactor: decouple Application layer from EF Core with specifications SCRUM-390 refactor(persistence): replace IQueryable exposure with specifications Sep 15, 2026
@github-actions

Copy link
Copy Markdown

Automated PR Validation Feedback

Action Needed on this Pull Request:

  • Code Formatting Failed: 100 changed C# file(s) do not match formatting rules. Run dotnet format locally on your changed files and commit the result.
  • Build Failure: The .NET 10 solution failed to compile or analyzers found errors. Run dotnet build locally and fix the issues.
  • Security Issues: Trivy found High/Critical vulnerabilities in 1 changed dependency file(s). Please check the workflow logs for details.

Please push a fix to your branch to re-run validation!

@github-actions

Copy link
Copy Markdown

Automated PR Validation Feedback

Action Needed on this Pull Request:

  • Code Formatting Failed: 100 changed C# file(s) do not match formatting rules. Run dotnet format locally on your changed files and commit the result.
  • Build Failure: The .NET 10 solution failed to compile or analyzers found errors. Run dotnet build locally and fix the issues.
  • Security Issues: Trivy found High/Critical vulnerabilities in 1 changed dependency file(s). Please check the workflow logs for details.

Please push a fix to your branch to re-run validation!

@github-actions

Copy link
Copy Markdown

Automated PR Validation Feedback

Action Needed on this Pull Request:

  • Code Formatting Failed: 101 changed C# file(s) do not match formatting rules. Run dotnet format locally on your changed files and commit the result.
  • Security Issues: Trivy found High/Critical vulnerabilities in 1 changed dependency file(s). Please check the workflow logs for details.

Please push a fix to your branch to re-run validation!

@github-actions

Copy link
Copy Markdown

Automated PR Validation Feedback

Action Needed on this Pull Request:

  • Code Formatting Failed: 101 changed C# file(s) do not match formatting rules. Run dotnet format locally on your changed files and commit the result.
  • Security Issues: Trivy found High/Critical vulnerabilities in 1 changed dependency file(s). Please check the workflow logs for details.

Please push a fix to your branch to re-run validation!

@Moha-sami

Copy link
Copy Markdown
Owner

Declined: We prefer keeping our persistence model simple without the Specification pattern. Wrapping EF Core LINQ inside specifications across 78 files introduces significant bloat, reduces LINQ query flexibility, and creates unnecessary regression risk across the entire system. In CQRS, MediatR handlers already isolate queries cleanly.

@Moha-sami Moha-sami closed this Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants