Repository navigation
EFDoctor 0.2.0
The first release built from the public repository, and the first stable 0.x release: install it with dotnet tool install --global EFDoctor, without --prerelease. Rules, options, and output may still change before 1.0; see the roadmap.
Changed
- The tool package includes repository and project URLs, with Source Link, when it is built with the
EFDoctorRepositoryUrlproperty set. By default it still includes none. - Queries stored in a local variable are now analyzed. Every rule that proves an EF Core query chain follows a local whose value is statically determined, and analyzes
var query = …; query = query.OrderBy(…); query.ToList()like the equivalent inline query. The affected rules are EFD004, EFD005, EFD006, EFD009, EFD010, EFD013, EFD014, EFD017, EFD019, EFD020, EFD022, EFD023, and EFD025. Evidence names the followed local. A local reassigned inside anif, a loop, or a lambda is not followed. EFD006 and EFD025 still report each finding once when includes are split across a local. - In JSON mode, standard output now holds only the error envelope when no installed SDK satisfies the target's
global.json. Before, the .NET host appended the installed-SDK list to standard output. When aglobal.jsongoverns the target, EFDoctor now checks SDK resolution first by runningdotnet --versionin the target's directory, with its output captured. - The CLI now analyzes a multi-targeted project whose
TargetFrameworkslist is written across several lines, as in OpenIddict. Before, Roslyn's MSBuildWorkspace passed each untrimmed entry as the target framework, the design-time build failed, and the project couldn't be analyzed. EFDoctor normalizes the list during loading through a targets file that ships with the tool, and never modifies the analyzed repository. - The CLI no longer reports a misleading clean result when EF Core types can't be resolved. A project that uses EF Core but can't resolve
DbContext, because it isn't restored or its design-time build failed, gets anEFDoctor warningon standard error. If no EF Core project can be analyzed, the run exits with2. - Queries that use
AsTracking(),IgnoreQueryFilters(), orIgnoreAutoIncludes()are now analyzed. Before, these methods broke the inline query-chain proof, so EFD004, EFD005, EFD009, EFD010, EFD013, EFD014, EFD019, EFD020, EFD022, and EFD023 silently skipped every query that used them. - EFD006 no longer reports a query that calls
AsSingleQuery()explicitly. As withAsSplitQuery(), it records a deliberate loading choice, and EF Core itself stays silent in that case. - EFD010 no longer reports the synchronous arm of an explicit sync/async switch, such as
async ? await q.FirstOrDefaultAsync(p) : q.FirstOrDefault(p), or anif/elsewhose other branch awaits the same EF counterpart. - EFD020 now counts only real re-executions. A use inside another query's expression-tree lambda (for example
Where(x => ids.Contains(x.Id))) and a predicate terminal (for exampleAny(x => …)) no longer count as executions, and executions in the two arms of the same conditional count as one path. - EFD001 no longer reports a save that runs once per batch. That covers a loop over chunks (
Chunk(n)), a pager loop whose condition declares the page, a loop that loads a page and iterates it before saving, and a threshold flush such asif (processed >= BatchSize). A per-item save, including one nested inside a batch loop, is still reported. - EFD005 proves keys from fluent configuration in the analyzed project (single-column
HasKey,HasAlternateKey,HasForeignKey, andHasIndex(...).IsUnique()), and from EF Core's<Navigation>Idforeign-key convention. Queries filtered by such a key, for example loading the children of one known parent, are no longer reported. - EFD005 reports a query whose only bound is equality on a key recognized by name (
Id,<Entity>Id,*Id) at Advisory confidence andInfoseverity instead of Medium andWarning. The finding is still reported, because such a query usually loads one row or the children of one known parent.
Added
- EFD009:
ToLower()orToUpper()applied to a column in an EF Core predicate, which prevents an index seek. Reported atWarningseverity with medium confidence under thePerformancecategory, with provider-aware remediation. - EFD014:
Skippagination with no precedingOrderBy, producing non-deterministic pages. - EFD021: EF Core
DbContextheld in astaticfield, which is not thread-safe and outlives the intended unit of work. Reported atWarningseverity with high confidence under theReliabilitycategory. - EFD022:
StringComparisonstring overload used inside an EF Core query predicate, which EF Core cannot translate to SQL and which throws at runtime. Reported atWarningseverity with high confidence under theCorrectnesscategory. - EFD020: an EF Core
IQueryablelocal enumerated more than once in the same method, re-executing the query against the database each time. Reported atWarningseverity with medium confidence under thePerformancecategory. - EFD010: a synchronous EF Core database call (
ToList,Count,SaveChanges,Find, and peers) inside anasyncmethod or lambda where an EF*Asynccounterpart exists. Reported atWarningseverity with medium confidence under thePerformancecategory. - EFD023:
Contains,EndsWith, or a leading-wildcardEF.Functions.Likeon a column in an EF Core predicate, which cannot seek an index. Reported at advisory confidence andInfoseverity under thePerformancecategory, with provider-aware remediation. - EFD025:
Includepath that duplicates, or is already covered by, anotherIncludepath in the same query. Reported atInfoseverity with high confidence under theMaintainabilitycategory.
Fixed
- EFD005 no longer reports a false positive when a key-equality predicate compares an entity key against a property of another object (for example
Where(sa => sa.ProductId == product.ProductId)). Such a comparand is now recognized as a bounding scalar, so the canonical "hydrate children for a known parent" query is no longer flagged as unbounded. A comparison between two properties of the filtered entity itself remains unbounded. - EFD005 recognizes key equality written with a type's own
==operator, such asGuid,DateTimeOffset, and their nullable forms, or with.Equals(). Before, these were reported as unbounded even when the key was proven.