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

Improve Entity Framework Core (EF Core) performance by finding the actual bottleneck first, then reducing unnecessary database work, roundtrips, transferred data, and object tracking. Start with command timings and the database execution plan; only consider compiled queries, context pooling, or other EF runtime optimizations after measuring a representative workload.

1. Find the slow layer before changing code

A slow operation that uses EF Core is not automatically an EF Core problem. Database execution, network latency, repeated commands, application-side processing, and EF overhead can all contribute. Diagnose a repeatable slow request or operation before choosing an optimization.

  1. Capture EF Core command logs briefly. Look for slow SQL statements, unexpected repeated commands, and the time taken by each command. Logging adds overhead and can consume disk space, so use it for a short diagnostic interval or in preproduction rather than leaving verbose logging enabled indiscriminately.
  2. Connect SQL to its LINQ call site. Query tags can help identify which part of the application generated a logged query.
  3. Inspect the database execution plan. Check whether the query uses appropriate indexes and whether its access path fits the data. A plan can change with data size and distribution, so a tiny development database may give a misleading picture.
  4. Check EF-specific behavior. EF metrics can help expose issues such as query-cache behavior or undisposed contexts.
  5. Benchmark plausible fixes. Use data that resembles production. Microsoft recommends BenchmarkDotNet for controlled benchmarking, but single-thread measurements do not replace tests under concurrent load.

Microsoft’s EF Core performance guidance advises developers to diagnose carefully rather than assume where the root cause lies. Its 2022 diagnosis sample illustrates the possible impact of reducing materialization and doing work in the database: for averaging blog rankings, the reported times were 2,860.4 μs for loading tracked entities, 1,353.0 μs for no-tracking entities, 910.9 μs for projecting only the ranking, and 627.1 μs for calculating the average in the database. These are results from that sample setup, not predictions for another application.

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

2. Improve the database query path

Verify index use in the execution plan

LINQ syntax alone does not tell you whether a database can use an index efficiently. Inspect the plan for the actual query and your data. In Microsoft’s SQL Server example, a filter using StartsWith can use an index where a similar EndsWith filter cannot. The point is not that one method is always faster, but that seemingly similar predicates can lead to different access paths.

Index design involves tradeoffs. Indexes can speed selected reads, but they add work when data is updated. For a composite index on (A, B), column order matters: it can support filters on both columns and often on A alone, but not a filter on B alone. Expressions over columns may also prevent use of a simple index; depending on the database provider, a persisted computed column or an expression index may be an option. Verify provider support and confirm the result in the plan rather than adding indexes speculatively.

Select only the values the caller needs

If a screen, API response, or calculation needs only a few fields, project those fields with Select instead of materializing complete entities and transferring unused columns. For several values, project to a DTO or anonymous type. This is particularly straightforward for read-only work; EF Core change tracking operates on entity instances, so use entity results when the operation needs tracked entities for later changes.

Put a deliberate bound on result sets

Large, unbounded results increase database work, network transfer, memory use, and downstream processing. Set a result limit when the application only needs a bounded number of rows, and paginate when users need to navigate a larger result set.

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

Skip/Take pagination, commonly translated to SQL OFFSET/LIMIT, is intuitive but can become inefficient on deep pages. For sequential navigation, keyset pagination is often a better fit. The right choice depends on how users move through results and on provider behavior.

Choose relationship loading to control roundtrips and duplication

When related data is known to be needed, eager loading can avoid the repeated roundtrips associated with lazy loading. But loading several related collections in one query can duplicate parent data through join expansion, sometimes called cartesian explosion. Split queries can reduce that duplication, at the cost of potentially adding roundtrips. Compare the generated SQL, transferred rows, and command count for the actual shape of the result.

Use tracking only when the operation needs it

For read-only entity queries, AsNoTracking avoids change-tracking work. Keep tracking when the application will modify the returned entities and rely on change detection. If a no-tracking result contains repeated references to the same entity and identity resolution matters, no-tracking with identity resolution is a possible middle ground. These are workload choices, not universal defaults.

Manage memory, asynchronous I/O, and raw SQL deliberately

  • Buffering: Methods such as ToListAsync retain the result set in memory. This can be appropriate when the full result is needed, but large results consume more memory.
  • Streaming: Async enumeration can keep memory use bounded while processing a large result set. The application still has to consume and process all requested rows.
  • Async database calls: Use async database APIs in scalable applications so threads are not blocked during I/O, and avoid accidental mixing of synchronous and asynchronous paths. Microsoft notes known async issues in some Microsoft.Data.SqlClient scenarios, especially with large text or binary values; investigate unexpected behavior against the exact driver and version.
  • Raw SQL: Consider it when EF Core cannot express or translate a needed database-specific construct and the performance gain justifies the added maintenance cost. First inspect the SQL EF Core already generates; raw SQL is a last resort, not a default optimization.

3. Make updates with fewer unnecessary operations

Understand SaveChanges batching for your provider

EF Core can batch multiple statements from SaveChanges into fewer roundtrips, but batching behavior depends on the database provider. Microsoft’s SQL Server guidance says batching tends to be less efficient below four statements, with benefits diminishing after about 40; the cited SQL Server default maximum batch size is 42. Those figures are provider-specific guidance, not universal thresholds or a reason to change settings without a benchmark.

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.

Use set-based operations for uniform changes

ExecuteUpdateAsync and ExecuteDeleteAsync, available starting with EF Core 7.0, can update or delete many rows in a single SQL statement without loading every entity or running change tracking for the operation. They suit uniform set-based changes; they are not interchangeable with every workflow that edits individual tracked entities.

Because these methods change the execution model, account for transaction boundaries and concurrency expectations. Any matching entities already tracked by the current context may be stale after the set-based operation; do not assume EF refreshed their in-memory values.

4. Reduce EF Core runtime overhead only after query work

Microsoft’s overall optimization order is useful: query efficiency, indexes, and fewer roundtrips usually matter more than EF Core’s own runtime overhead, while database I/O and network latency often dominate. If measurement shows EF overhead is material in a hot path, consider these options.

Keep query shapes reusable; benchmark compiled queries

EF Core caches query compilation by expression-tree shape. Parameterize changing values so structurally identical queries can reuse compiled results. Dynamically embedding changing constants in expression trees can create cache misses and distinct SQL.

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

Compiled queries bypass cache lookup for selected hot query shapes. They require a single EF model and simple scalar parameters, and their benefit should be measured. Microsoft’s sample benchmark reports the following compiled versus non-compiled times:

Sample query size Compiled Non-compiled
One blog 564.2 μs 671.6 μs
Ten blogs 645.3 μs 709.8 μs

These are Microsoft sample measurements, not expected gains for an application with different queries, data, or infrastructure.

Consider DbContext pooling for measured setup costs

DbContext pooling reuses initialized context instances and may reduce setup overhead in high-performance, low-latency workloads. It is separate from database connection pooling. In Microsoft’s single-threaded benchmark fetching one row from a local SQL Server database, the reported result was 701.6 μs and 50.38 KB allocated without context pooling, compared with 350.1 μs and 4.63 KB with pooling. The source cautions that results vary with row count, network latency, and contention; treat this as a sample, not a forecast.

A pooled context is reused across scopes, and OnConfiguring runs only when the context is first created. Do not put per-request or tenant-varying state there. Pool sizing and state reset need care so one use of a context cannot leak state into another.

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

Do not disable safety checks casually

Disabling EF Core thread-safety checks is another possible runtime-overhead reduction, but it can hide concurrent use of a DbContext, which is unsupported. Consider it only after thorough testing for concurrency bugs.

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

5. Weigh model changes against consistency and maintenance

Denormalization and cached values

Denormalization or cached aggregate values can reduce joins or repeated calculations, but they add synchronization and consistency work. A stored computed column is suitable for a value derived from columns in the same row. A cached value that depends on other rows needs a reliable update mechanism. Database triggers can update values in the transaction and avoid extra application roundtrips, though EF Core has no dedicated trigger-authoring API. Materialized or indexed views can cache query results, but refresh and update behavior varies by database.

Inheritance mapping

Inheritance mapping affects query shape: table-per-hierarchy (TPH) stores a hierarchy in one table; table-per-type (TPT) splits types across tables and may require joins; table-per-concrete-type (TPC) uses tables for concrete types. Microsoft’s 2023 sample loaded all rows from a seven-type hierarchy with 5,000 rows per type, or 35,000 total:

Mapping strategy Microsoft sample time
TPH 149.0 ms
TPT 312.9 ms
TPC 158.2 ms

The example demonstrates a tradeoff in that particular all-rows query; actual results depend on the query and the number of hierarchy tables. It is not a blanket performance verdict for every model.

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

6. Use a measured optimization loop

  1. Reproduce a slow operation with representative data and capture command timings.
  2. Inspect generated SQL and the database execution plan; identify expensive access paths, missing or ineffective indexes, repeated roundtrips, and oversized results.
  3. Change one relevant factor at a time: projection, result bounds, relationship loading, tracking, pagination, or update strategy.
  4. Benchmark the changed version under realistic conditions, then test concurrent load when production concurrency matters.
  5. Only if the remaining measured cost is EF runtime overhead, test query-shape reuse, compiled queries, or context pooling.

Confirm API availability, provider behavior, and provider-specific defaults for the EF Core and database versions you deploy. The Microsoft figures here are sample benchmark results published in 2022 and 2023, not universal gains.

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.