agentsclimarketplace

Optimizing ef core queries

Skill Harmeet10000/skills/skills/backend/C#_dotnet/optimizing-ef-core-queries

Collection of my Agent Skills and books.

Install
npx -y skills add Harmeet10000/skills --skill optimizing-ef-core-queries

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 7 stars7 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Optimize Entity Framework Core queries by fixing N+1 problems, choosing correct tracking modes, using compiled queries, and avoiding common performance traps. Use when EF Core queries are slow, generating excessive SQL, or causing high database load.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.6 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Optimizing EF Core Queries

When to Use

  • EF Core queries are slow or generating too many SQL statements
  • Database CPU/IO is high due to ORM inefficiency
  • N+1 query patterns are detected in logs
  • Large result sets cause memory pressure

When Not to Use

  • The user is using Dapper or raw ADO.NET (not EF Core)
  • The performance issue is database-side (missing indexes, bad schema)
  • The user is building a new data access layer from scratch

Inputs

InputRequiredDescription
Slow EF Core queriesYesThe LINQ queries or DbContext usage to optimize
SQL output or logsNoEF Core generated SQL or query execution logs

Workflow

Step 1: Enable query logging to see the actual SQL

// In Program.cs or DbContext configuration:
optionsBuilder
    .UseSqlServer(connectionString)
    .LogTo(Console.WriteLine, LogLevel.Information)
    .EnableSensitiveDataLogging()  // shows parameter values (dev only!)
    .EnableDetailedErrors();

Or use the Microsoft.EntityFrameworkCore log category:

{
  "Logging": {
    "LogLevel": {
      "Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  }
}

Step 2: Fix N+1 query patterns

The #1 EF Core performance killer. Happens when loading related entities in a loop.

Before (N+1 — 1 query for orders + N queries for items):

var orders = await db.Orders.ToListAsync();
foreach (var order in orders)
{
    // Each access triggers a lazy-load query!
    var items = order.Items.Count;
}

After (eager loading — 1 or 2 queries total):

// Option 1: Include (JOIN)
var orders = await db.Orders
    .Include(o => o.Items)
    .ToListAsync();

// Option 2: Split query (separate SQL, avoids cartesian explosion)
var orders = await db.Orders
    .Include(o => o.Items)
    .AsSplitQuery()
    .ToListAsync();

// Option 3: Explicit projection (best - only fetches needed columns)
var orderSummaries = await db.Orders
    .Select(o => new OrderSummary
    {
        OrderId = o.Id,
        Total = o.Items.Sum(i => i.Price),
        ItemCount = o.Items.Count
    })
    .ToListAsync();

When to use Split vs Single query:

ScenarioUse
1 level of IncludeSingle query (default)
Multiple Includes (Cartesian risk)AsSplitQuery()
Include with large child collectionsAsSplitQuery()
Need transaction consistencySingle query

Step 3: Use NoTracking for read-only queries

Change tracking overhead is significant. Disable it when you don't need to update entities:

// Per-query
var products = await db.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .ToListAsync();

// Global default for read-heavy apps
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

Use AsNoTrackingWithIdentityResolution() when the query returns duplicate entities to avoid duplicated objects in memory.

Step 4: Use compiled queries for hot paths

// Define once as static
private static readonly Func<AppDbContext, int, Task<Order?>> GetOrderById =
    EF.CompileAsyncQuery((AppDbContext db, int id) =>
        db.Orders
            .Include(o => o.Items)
            .FirstOrDefault(o => o.Id == id));

// Use repeatedly — skips query compilation overhead
var order = await GetOrderById(db, orderId);

Step 5: Avoid common query traps

TrapProblemFix
ToList() before Where()Loads entire table into memoryFilter first: .Where().ToList()
Count() to check existenceScans all rowsUse .Any() instead
.Select() after .Include()Include is ignored with projectionRemove Include, use Select only
string.Contains() in WhereMay not translate, falls to client evalUse EF.Functions.Like() for SQL LIKE
Calling .ToList() inside Select()Causes nested queriesUse projection with Select all the way

Step 6: Use raw SQL or FromSql for complex queries

When LINQ can't express it efficiently:

var results = await db.Orders
    .FromSqlInterpolated($@"
        SELECT o.* FROM Orders o
        INNER JOIN (
            SELECT OrderId, SUM(Price) as Total
            FROM OrderItems
            GROUP BY OrderId
            HAVING SUM(Price) > {minTotal}
        ) t ON o.Id = t.OrderId")
    .AsNoTracking()
    .ToListAsync();

Validation

  • SQL logging shows expected number of queries (no N+1)
  • Read-only queries use AsNoTracking()
  • Hot-path queries use compiled queries
  • No client-side evaluation warnings in logs
  • Include/split strategy matches data shape

Common Pitfalls

PitfallSolution
Lazy loading silently creating N+1Remove Microsoft.EntityFrameworkCore.Proxies or disable lazy loading
Global query filters forgotten in perf analysisCheck HasQueryFilter in model config; use IgnoreQueryFilters() if needed
DbContext kept alive too longDbContext should be scoped (per-request); don't cache it
Batch updates fetching then savingEF Core 7+: use ExecuteUpdateAsync / ExecuteDeleteAsync for bulk operations
String interpolation in FromSqlRawSQL injection risk — use FromSqlInterpolated (parameterized)

Gives 0 of the 12 instructions most performance cost skills give in ~1.3k tokens

Counted across 803 of the 1,058 authors here whose files we hold, read 2026-08-07

  • keep skill files under 500 linesin 82 of 803, across 16 files
  • use imperative form in instructionsin 80 of 803, across 9 files
  • draft assertions while test runs are in progressin 75 of 803, across 9 files
  • create two to three realistic test promptsin 74 of 803, across 9 files
  • write skill descriptions to be pushyin 72 of 803, across 7 files
  • save test cases to evals jsonin 72 of 803, across 6 files
  • ask questions about edge cases and input formatsin 72 of 803, across 7 files
  • save timing data immediately when runs completein 70 of 803, across 5 files
  • include all trigger conditions in the skill descriptionin 69 of 803, across 3 files
  • launch all test runs in a single turnin 69 of 803, across 3 files
  • capture intent before writing a skillin 67 of 803, across 1 file
  • import directly instead of barrel filesin 52 of 803, across 15 files

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.