SCRUM-390 refactor(persistence): replace IQueryable exposure with specifications - #366
Mahmoud-Alim wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Automated PR Validation FeedbackAction Needed on this Pull Request:
Please push a fix to your branch to re-run validation! |
cf571fc to
0f2172b
Compare
Automated PR Validation FeedbackAction Needed on this Pull Request:
Please push a fix to your branch to re-run validation! |
0f2172b to
beab186
Compare
Automated PR Validation FeedbackAction Needed on this Pull Request:
Please push a fix to your branch to re-run validation! |
beab186 to
837013f
Compare
Automated PR Validation FeedbackAction Needed on this Pull Request:
Please push a fix to your branch to re-run validation! |
|
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. |
Summary
IRepository<T>.Query()exposed rawIQueryable<T>, so ~90 Application filescomposed EF-specific queries inline (
Include/ThenInclude,AsNoTracking,IgnoreQueryFilters,ToListAsync, dynamicOrderBy/Skip/Take), andBuy2.Applicationreferenced theMicrosoft.EntityFrameworkCorepackage.This PR removes
Query()entirely: handlers now describe queries withISpecification<T>(filter + include paths + ordering + paging + tracking),interpreted in one place by
GenericRepository. The Application and Api layerscontain zero EF references; the package reference is removed from
Buy2.Application.csproj.What Changed
Application Layer
Common/Specifications/:ISpecification<T>,Specification<T>fluentbuilder (
Where/Include("JobRole.Department")/OrderBy/ThenBy/Page/AsTracked/IgnoreFilters),Ordering<T>,PagedResult<T>.IRepository<T>rewritten withoutIQueryable:FirstOrDefaultAsync,ListAsync(entity, predicate, and projection overloads),CountAsync,SumAsync(int/int?/decimal/decimal?),PagedAsync,AnyAsync, plusunchanged CRUD. ~90 handlers/validators/services migrated to it.
Common/Exceptions/:DataIntegrityException,ConcurrencyConflictExceptionreplacing caughtDbUpdateException/DbUpdateConcurrencyException(4 handlers).GetJobsQueryno longerIncludes the wholeEmployeescollection to countit (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
JobRoleIdprojection grouped in memory (2 queries, no N+1).GetEmployees,GetPointsTransactions,GetJobs, …) usePagedAsync, preserving their original page/page-size clamps.Persistence / Database
GenericRepositoryis now the only place that translates specifications toEF Core (
Include(string),AsNoTracking,IgnoreQueryFilters,OrderBy/ThenBy,Skip/Take).UnitOfWorktranslatesDbUpdateConcurrencyException→ConcurrencyConflictExceptionandDbUpdateException→DataIntegrityExceptionon every save path (including transactions).Testing
performance-guard tests (
#310–#316,#331–#336), which now assert oncaptured specifications instead of intercepted
IQueryableexpressions.TrackingRepositorytest fakes reimplemented asGenericRepositorydecorators counting reads;
SaveAsTemplateTestsupdated to the translatedexception type;
GetJobsQueryHandlerconstructor call sites updated for theadded
IRepository<Employee>dependency.Backward Compatibility
IRepository<T>surface changed (implementers mustimplement the new members) and persistence failures now surface as
DataIntegrityException/ConcurrencyConflictExceptioninstead of EF types —any caller catching
DbUpdateExceptionmust be updated (all in-repo callerswere).
Important Implementation Details
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).
Selectprojections became fetch-with-includes + in-memory mappingwith null-guards (SQL LEFT-JOIN semantics previously tolerated null navigations).
dotnet format --verify-no-changesstill reports violations, but they arepre-existing repo-wide (including untouched files); the new core files were
verified clean. No formatting changes included here.
refactor/apply-template-focused-services(merge that first):the ApplyTemplate services in that branch already consume this API.
Checklist