Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

EF Core 10 queries usually become faster when they make the database do less work: use the right indexes, return fewer rows and columns, avoid unnecessary roundtrips, and verify the generated SQL. EF Core runtime overhead matters, but it is rarely the first bottleneck. Start with a query plan and timings, then apply narrower ORM optimizations only where profiling shows a remaining cost.

EF Core 10.0 shipped in November 2025 as a Long Term Support release supported through November 10, 2028. It requires the .NET 10 SDK to build and the .NET 10 runtime to run; it does not run on earlier .NET versions or .NET Framework. Microsoft recommends the latest patch of a supported release and testing major-version upgrades for breaking changes. See the EF Core releases and planning guidance.

Start with evidence, not micro-optimizations

Capture the SQL EF Core sends, the database execution plan, elapsed time, logical reads, rows returned, allocations, and the number of commands issued for one request. Test with production-like data volumes: a query that looks fine against a small development database can become expensive when it scans millions of rows or returns a large collection.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Use your provider’s plan tool (for example, the execution-plan viewer in your database platform) to check for scans, expensive sorts, key lookups, and missing or unused indexes.
  • Enable EF Core command logging in a non-production environment and count statements, parameters, and result sizes.
  • Measure a representative request repeatedly, including network latency and concurrent activity; a local benchmark can hide roundtrip costs.
  • Change one query shape at a time, then compare the plan and end-to-end result.

Microsoft’s efficient-querying documentation summarizes the priority well: “The main deciding factor in whether a query runs fast is whether it will properly utilize indexes where appropriate.”

1. Make filters and ordering index-friendly

Inspect every predicate, join key, and OrderBy used by a hot query. An index that matches those operations can turn a table scan into a targeted seek. Indexes also consume storage and make inserts, updates, and deletes more expensive, so add them for a measured workload rather than for every column.

Check composite-index order

For an index on (A, B), a query filtering by A can generally use the leading portion of the index. A query filtering only by B generally cannot use that same index as efficiently. Put the column with the most useful leading restriction first for the actual query patterns, and include ordering columns when that avoids a separate sort.

Validate the plan after migration

Model configuration is not proof that the database will choose an index. Generate the SQL, run it with realistic parameters, and inspect the plan. Recheck plans after data growth because selectivity and statistics change over time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Project only the columns the caller needs

Use Select to shape a result instead of materializing a full entity when the caller needs only a few fields. Projection reduces rows’ payload, network transfer, deserialization, and memory use.

var summaries = await db.Orders
    .Where(o => o.CustomerId == customerId)
    .Select(o => new OrderSummary
    {
        Id = o.Id,
        PlacedAt = o.PlacedAt,
        Total = o.Total
    })
    .ToListAsync();

A projection into a non-entity type is not a tracked mutable entity. If the operation must update an entity, load or attach it deliberately; do not assume a DTO projection will participate in normal change tracking.

3. Bound every result set

Combine a selective Where clause with Take (or a page size enforced by application policy) instead of calling ToListAsync on an unbounded query. This protects memory, response time, and network bandwidth when an unexpected data condition matches many rows.

var recent = await db.Events
    .Where(e => e.TenantId == tenantId && e.CreatedAt >= cutoff)
    .OrderByDescending(e => e.CreatedAt)
    .Take(100)
    .Select(e => new { e.Id, e.CreatedAt, e.Type })
    .ToListAsync();

Make the limit explicit in APIs that expose search or history. If clients need more data, require pagination rather than silently increasing a single request’s maximum.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Choose offset or keyset pagination intentionally

Skip/Take offset pagination is easy to implement, but deep pages can require the database to process and discard many earlier rows. It can also produce duplicates or omissions when rows are inserted or deleted between requests.

Approach How it works Advantages Costs and risks
Offset OrderBy(...).Skip(offset).Take(size) Direct page-number UI and simple random access Work can grow with the offset; concurrent changes can shift page boundaries
Keyset (seek) Filter on the last seen ordered key, then Take(size) Efficient sequential next/previous navigation and stable continuation Requires a suitable index and a continuation token; jumping to page 50 is not natural

For keyset pagination, order by a fully unique, indexed tuple (for example, timestamp plus ID) and request rows strictly after the last tuple. Pick the method that matches the navigation requirement instead of treating one as universally faster.

5. Eliminate accidental N+1 queries

Lazy loading can issue another database roundtrip each time code touches a navigation property. A loop over 500 parent entities can therefore send hundreds of additional commands. Log EF Core statements while exercising the endpoint to verify the command count.

Load known relationships deliberately

Use eager loading with Include when the relationship is known up front, or explicit loading when you need a controlled follow-up. Projecting a parent and the required child fields into one result shape can be even more efficient when entities are not being edited. Keep lazy loading disabled for paths where an accidental loop would be costly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

6. Filter and limit eager-loaded collections

If a parent needs only part of a collection, filtered eager loading keeps the relationship explicit while applying filtering, ordering, and limits inside the related query.

var blogs = await db.Blogs
    .Include(b => b.Posts
        .Where(p => p.IsPublished)
        .OrderByDescending(p => p.PublishedAt)
        .Take(5))
    .ToListAsync();

This avoids transferring every child row merely because the parent navigation exists. Confirm the generated SQL and behavior for your provider, especially when combining multiple includes or tracking queries.

7. Compare single and split queries for collection includes

Joining multiple sibling collections in one SQL statement can multiply rows. Microsoft’s example shows one blog with 10 posts and 10 contributors producing 100 result rows because each post is paired with each contributor. The database and network transfer that repeated data even though EF Core later reconstructs the object graph.

Choice Database and network behavior When it helps Trade-offs
Single query (default for many include shapes) One statement; sibling collection joins can create a cross product and duplicate parent columns Fewer roundtrips and a consistent single SQL statement Potentially many repeated rows, larger payloads, and expensive joins
AsSplitQuery() Separate SQL statements for the root and included collections Avoids the cross product for large sibling collections Each query adds a network roundtrip; results can observe changes between statements and may require buffering
var blogs = await db.Blogs
    .Include(b => b.Posts)
    .Include(b => b.Contributors)
    .AsSplitQuery()
    .ToListAsync();

Choose using measured row counts, latency, consistency requirements, and memory behavior. A split query is not automatically faster: on a high-latency connection, its extra commands may outweigh the savings from avoiding duplicated rows. The official trade-offs are documented in Single vs. Split Queries.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

8. Use no-tracking for read-only work

Tracking maintains snapshots and identity information so changes can be detected and related entities can be fixed up. For a read-only endpoint, AsNoTracking removes that work when the result does not need to be edited through the context.

var products = await db.Products
    .AsNoTracking()
    .Where(p => p.IsActive)
    .Select(p => new { p.Id, p.Name, p.Price })
    .ToListAsync();

No-tracking can create separate object instances when the same database row appears multiple times. If read-only results need identity resolution without normal change tracking, evaluate AsNoTrackingWithIdentityResolution and benchmark its memory cost.

Microsoft’s sample that loads 10 blogs with 20 posts each measured 1,414.7 microseconds for tracking versus 993.3 microseconds for no-tracking. Those are figures from that particular benchmark, not a prediction for every provider, schema, or network.

9. Stream large results instead of buffering them

ToListAsync buffers the complete result set, so memory grows with the number of rows. Streaming processes rows as they arrive and is preferable when a response or pipeline can consume items incrementally.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await foreach (var row in db.LogEntries
    .AsNoTracking()
    .Where(x => x.CreatedAt >= cutoff)
    .OrderBy(x => x.Id)
    .AsAsyncEnumerable())
{
    await ProcessAsync(row);
}

Keep the context and database connection alive for the duration of the enumeration, and do not start another operation on the same context before the current enumeration finishes. Buffer deliberately when you need random access, multiple passes, or a transaction boundary that requires all rows to be read first.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

10. Use asynchronous database APIs for scalable I/O

Call ToListAsync, FirstOrDefaultAsync, SaveChangesAsync, and related APIs in request paths that wait on database I/O. Async releases the request thread while the database and network are working, allowing the server to handle more concurrent requests. It does not make the database execution plan intrinsically faster, so keep query-shape and index work separate from scalability improvements.

Propagate cancellation tokens from the request to the EF Core call so abandoned requests do not continue consuming database resources. Avoid mixing synchronous and asynchronous calls on the same context, and never run two concurrent operations on one DbContext instance.

11. Benchmark context pooling and compiled queries only on hot paths

Context pooling

AddDbContextPool reuses context instances, reducing setup and allocation overhead. A pooled context is reset between uses, but application state that varies per request must be handled carefully. Tenant IDs, user-specific filters, and other mutable state must not leak between requests; configure or inject such state in a way compatible with pooling.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Compiled queries

Compiled queries bypass the normal query-cache lookup for a repeatedly executed query shape. They are most plausible for very hot paths with stable expression trees; they do not repair a missing index, excessive result set, or N+1 pattern.

In Microsoft’s 10-blog sample, a compiled query measured 645.3 microseconds and 13 KB allocated versus 709.8 microseconds and 18 KB for the non-compiled form. The documentation advises benchmarking on the target platform; treat these as sample results, not universal gains. See Advanced Performance Topics for pooling, query caching, and compiled-query considerations.

12. Check EF Core 10’s parameterized-collection SQL after upgrading

EF Core 10 defaults to translating parameterized collections into multiple scalar parameters. The change gives the query planner collection-cardinality information and can improve plans in many workloads, while balancing plan-cache reuse. Applications that relied on EF Core 8 or 9’s JSON-array translation can see different plans or regressions.

A safe upgrade workflow

  1. Capture generated SQL and representative parameter sizes before upgrading.
  2. Run the same workload on EF Core 10 and compare execution plans, logical reads, duration, and allocations.
  3. Test small, medium, and large collection parameters; a translation that works well for five values may behave differently for thousands.
  4. If a regression appears, evaluate the documented configuration or per-query translation controls for your database provider, then keep the choice that wins on production-like data.

Read the version-specific details in Microsoft’s EF Core 10 breaking changes documentation. EF Core 10 also adds first-class .NET 10 LeftJoin and RightJoin LINQ operators. They simplify query expressions; assume no speedup unless the generated SQL and database plan demonstrate one. Release features are listed in What’s New in EF Core 10.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A practical tuning order for production incidents

  1. Identify the slow endpoint and capture command timings, SQL, parameters, row counts, and the database plan.
  2. Fix non-sargable filters, missing or misordered indexes, unnecessary joins, and avoidable sorts.
  3. Reduce transferred data with projection, result limits, and an appropriate pagination method.
  4. Count roundtrips; remove lazy-loading loops and decide between a single query, filtered include, projection, or split query.
  5. Choose tracking, no-tracking, streaming, and async semantics that match the endpoint’s behavior.
  6. Only after those changes, benchmark pooling or compiled queries on the hot path.
  7. After every EF Core or provider upgrade, compare generated SQL and plans, including parameterized-collection queries.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.