Install
$ agentstack add skill-tunahanaliozturk-secure-dotnet-skills-ef-core-review ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
EF Core Review
Directs the agent to perform a systematic review of Entity Framework Core usage across query loading strategy, write correctness, raw-SQL safety, and migration hygiene, producing a severity-rated finding with a named EF Core API fix for each issue.
When to use
- A PR introduces or modifies EF Core queries,
DbContextconfiguration, orSaveChangescall sites. - A service shows slow database response times and the cause may be N+1 loading or missing
AsNoTracking. - Raw-SQL via
FromSqlRaworExecuteSqlRawappears anywhere in the diff. - A new migration is being reviewed before it runs in staging or production.
Process
- Find the query hotspots and write paths. Locate every
DbSetaccess, everySaveChanges/SaveChangesAsynccall, and anyFromSqlRaw/ExecuteSqlRawusage. Note which queries are inside loops. - Check the loading strategy. For each navigation property access, determine whether EF Core will lazy-load (issuing a separate query per row), eager-load via
Include, or explicitly load. Flag every place where a navigation is accessed inside a loop without a priorInclude. - Check write and transaction correctness. Confirm multi-entity writes are wrapped in a transaction and that
SaveChangesis called once per unit of work, not once per entity or per loop iteration. Verify concurrency tokens are present on entities that can be updated concurrently. - Check raw-SQL safety. For every
FromSqlRaw/ExecuteSqlRawcall, verify the SQL string is a compile-time literal or uses onlySqlParameter/DbParameterobjects — never string interpolation or concatenation of user-supplied values. PreferFromSqlInterpolatedwhen interpolation is genuinely needed; it extracts each hole as a parameterizedDbParameterautomatically. - Check migrations for data loss and idempotency. Review each
MigrationBuildermethod for destructive operations (column drops, renames, type changes) that could lose data. Confirm that migrations are idempotent when generated with--idempotentfor deployment. Check thatEnableRetryOnFailureis configured for transient-fault resilience and thatDbContextlifetime and pooling match the application host model. - Output findings with fixes. Rate each finding (Critical / High / Medium / Low), name the EF Core API that resolves it, and note whether there are sibling queries with the same defect that need the same fix.
.NET / Azure checks
- N+1 from lazy loading or missing
Include. Check whetherUseLazyLoadingProxies()is enabled and whether navigation properties are accessed inside loops. Aforeachover anOrderlist that readsorder.Customer.Namewithout.Include(o => o.Customer)issues oneSELECTper row. Fix with.Include(o => o.Customer)(eager) orentry.Reference(o => o.Customer).LoadAsync()(explicit, single call before the loop). Prefer projecting to a DTO withSelectto fetch only the columns needed. AsNoTracking()for read-only queries. AnyDbSetquery whose results are never passed toSaveChangesshould call.AsNoTracking()or useUseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)at the context level for read-heavy contexts. Tracked queries allocate change-tracking snapshots — on large result sets this is measurable GC pressure with no benefit. Note:AsNoTrackingdoes not change which rows are returned; it only omits the identity map and snapshot.- DTO projection instead of materializing full entities. A
.ToListAsync()that returnsListwhen the caller only needs order id and total unnecessarily fetches every column. Use.Select(o => new OrderSummaryDto { Id = o.Id, Total = o.Total }).ToListAsync()to push projection to the database. Returning EF entities directly from controllers also exposes unmapped columns and circular-reference serialization issues. - Raw-SQL injection via
FromSqlRaw/ExecuteSqlRaw. Any call of the formcontext.Orders.FromSqlRaw($"SELECT … WHERE Status = '{status}'")or+ userInputis SQL-injectable. RequireFromSqlInterpolated($"SELECT … WHERE Status = {status}")— EF Core extracts each{…}hole as aDbParameter, so the database always treats it as a bound value. ForExecuteSqlRaw, passSqlParameterobjects as theparams object[]argument. LINQ queries are safe because EF Core always parameterizes them. - Client-side evaluation forced by unsupported expressions. When a LINQ
Wherepredicate contains a .NET method EF Core cannot translate (e.g.,o.Description.Contains(someRegex)using a regex overload, or a custom extension method), EF Core 3+ throws at runtime rather than silently pulling all rows to the client. Run the query in development and confirm noInvalidOperationExceptionabout client-side evaluation. Rewrite using translatable members or a raw-SQL alternative. SaveChangesinside loops. Callingcontext.SaveChangesAsync()inside aforeachissues oneUPDATE/INSERTround-trip per iteration and wraps each in its own implicit transaction. Accumulate all changes and callSaveChangesAsync()once after the loop. For very large batches, considerExecuteUpdateAsync/ExecuteDeleteAsync(EF Core 7+) which translate to set-based SQL without loading entities.- Concurrency tokens and transactions for multi-entity writes. Entities that can be updated by concurrent requests need a concurrency token: either a
[Timestamp]/byte[]property mapped with.IsRowVersion()(SQL Serverrowversion) or a[ConcurrencyCheck]scalar property. Without a token, the last writer silently wins. Multi-entity write operations that must be atomic must use an explicitIDbContextTransactionviacontext.Database.BeginTransactionAsync()and commit or roll back as a unit. - Migrations: destructive operations, idempotency, and resilience. Review
MigrationBuilder.DropColumn,RenameColumn, and column-type changes for data loss. A column drop with no preceding data-migration step loses data permanently. Confirmcontext.Database.MigrateAsync()is not called on startup in a multi-instance deployment (use a one-shot migration job instead). ConfirmEnableRetryOnFailure(maxRetryCount: 5)is set inUseSqlServer/UseNpgsqloptions for transient Azure SQL / Postgres errors. ConfirmDbContextis registered withAddDbContext(scoped lifetime) orAddDbContextPool(pooled, scoped, all state reset between requests) — never as a singleton, which causes cross-request state pollution.
Red flags
| Signal | Why it matters | |--------|----------------| | context.Orders.FromSqlRaw($"… WHERE Status = '{status}'") | String-interpolated raw SQL passes user input directly into the query; the interpolated hole is not parameterized by FromSqlRaw, making it trivially injectable. Use FromSqlInterpolated. | | Navigation property accessed inside foreach with no prior Include | Issues one SELECT per loop iteration (N+1). On a list of 500 rows this is 501 round-trips; on a large dataset it is a liveness risk. | | .ToList() followed by .Where(…) in memory | EF Core fetches every row from the database and then filters in the .NET process. Use .Where(…).ToListAsync() to push the predicate to SQL. | | await context.SaveChangesAsync() inside a loop body | Each call opens and closes an implicit transaction. Accumulate changes first; call SaveChangesAsync once outside the loop. | | Controller action returns IEnumerable (EF entity) directly | Exposes every column including internal fields, risks serialization cycles on navigation properties, and leaks the data model to the API contract. Project to a DTO. | | Migration with DropColumn and no prior data-migration step | Drops data permanently on the next deploy. Add a data-migration migration before the destructive one, or move the data in the same migration using migrationBuilder.Sql. | | No .AsNoTracking() on read-only queries | Every tracked entity allocates a change-tracking snapshot. On a query returning thousands of rows this wastes memory and GC time with no benefit when results are never saved. | | DbContext registered as AddSingleton | A singleton DbContext is shared across all requests and across Task.WhenAll parallel paths. DbContext is not thread-safe; concurrent access corrupts its internal state map. |
Example
See [examples/ef-core-review/](../../examples/ef-core-review/).
Related skills
- [dotnet-performance-review](../dotnet-performance-review/SKILL.md) — use for broad .NET performance review beyond EF Core queries (allocations, async, caching).
- [dotnet-security-review](../dotnet-security-review/SKILL.md) — use to catch raw-SQL injection and other security issues across the full service.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: tunahanaliozturk
- Source: tunahanaliozturk/secure-dotnet-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.