Entity Framework Core interview questions with answers
Questions about how EF Core behaves in real applications: performance traps, safe migrations and concurrency. Each answer explains the reasoning and shows a short example.
Intermediate · 12 questions · Tap a question to see the answer.
Q1. What lifetime should a
DbContexthave in an ASP.NET Core app, and why is sharing one instance across threads a problem?Register it as Scoped with
AddDbContext, so each HTTP request gets its own instance. ADbContextis a lightweight unit of work, not thread-safe, and it tracks every entity it loads.A shared or Singleton context fails with 'A second operation was started on this context instance' under concurrent use, and its change tracker grows without limit. In background services, create a scope or use
IDbContextFactory<T>to create short-lived contexts.builder.Services.AddDbContextFactory<AppDbContext>(o => o.UseSqlServer(conn)); // in a singleton/background service await using var db = await factory.CreateDbContextAsync(ct);Q2. What does change tracking do, and when should you use
AsNoTracking()?By default, entities returned by a query are tracked: EF Core keeps a snapshot and detects changes at
SaveChanges. That costs memory and CPU.For read-only queries (API GETs, reports), use
AsNoTracking()(orAsNoTrackingWithIdentityResolution()if you need one instance per key). For whole read-heavy contexts, setQueryTrackingBehavior.NoTrackingas the default.var products = await db.Products .AsNoTracking() .Where(p => p.IsActive) .ToListAsync(ct);Q3. Compare eager, explicit and lazy loading of related data.
Eager loading (
Include/ThenInclude) loads related data in the same query. Explicit loading (db.Entry(order).Collection(o => o.Lines).LoadAsync()) loads it later on demand. Lazy loading (proxies) loads a navigation automatically the first time it is accessed.Lazy loading easily causes N+1 queries, especially during JSON serialization, so most web APIs avoid it and use projections or
Includeinstead.var orders = await db.Orders .Include(o => o.Customer) .Include(o => o.Lines).ThenInclude(l => l.Product) .Where(o => o.Id == id) .ToListAsync(ct);Q4. A query with two collection
Includes returns slowly and transfers far more data than expected. What is happening?Including two collections in one query joins them, so each order row is repeated for every combination of lines × payments: a cartesian explosion.
Use
AsSplitQuery()to load each collection with its own SQL query, or project only the data you need. Split queries cost extra round trips and can see inconsistent data if rows change between them; you can set split queries as the default for the context.var orders = await db.Orders .Include(o => o.Lines) .Include(o => o.Payments) .AsSplitQuery() .ToListAsync(ct);Q5. Why is projecting to a DTO with
Selectoften better than returning entities from an API?Selectmakes EF Core fetch only the columns you use, joins related data in SQL withoutInclude, and returns untracked results. It also avoids exposing your database model and serialization cycles.Entity graphs returned directly from an API often cause over-fetching, lazy loading surprises, or cycles in JSON.
var dto = await db.Orders .Where(o => o.Id == id) .Select(o => new OrderDto(o.Id, o.Total, o.Customer.Name, o.Lines.Count)) .SingleOrDefaultAsync(ct);Q6. How should you apply EF Core migrations to a production database?
Not with
Database.Migrate()at app startup: several instances may race, and the app needs schema-change permissions. Generate an idempotent SQL script (dotnet ef migrations script --idempotent) or a migration bundle (dotnet ef migrations bundle) and run it as a deployment step.Review the generated SQL, especially for renames (EF may drop and add a column) and data-losing changes.
dotnet ef migrations bundle --self-contained -r linux-x64 ./efbundle --connection "$CONNECTION_STRING"Q7. Two users edit the same product at the same time and one silently overwrites the other. How does EF Core prevent this?
Add a concurrency token, for example a SQL Server
rowversioncolumn mapped with[Timestamp]. EF Core adds it to the UPDATE's WHERE clause; if another user changed the row, zero rows are affected and EF throwsDbUpdateConcurrencyException.Catch it and decide: reload and show the user the new values, retry, or merge.
public class Product { public int Id { get; set; } public string Name { get; set; } = ""; [Timestamp] public byte[] Version { get; set; } = []; }Q8. How do you update or delete thousands of rows efficiently in EF Core 7+?
Use
ExecuteUpdateAsyncandExecuteDeleteAsync. They translate to a single UPDATE or DELETE statement and run immediately without loading entities into memory.They bypass the change tracker, so tracked entities in the same context aren't updated, and they don't run in the same transaction as
SaveChangesunless you open one explicitly.await db.Orders .Where(o => o.Status == Status.Draft && o.CreatedAt < cutoff) .ExecuteDeleteAsync(ct); await db.Products .Where(p => p.CategoryId == id) .ExecuteUpdateAsync(s => s.SetProperty(p => p.Price, p => p.Price * 1.1m), ct);Q9. When and how do you use raw SQL with EF Core safely?
Use it for queries LINQ can't express well or for tuned SQL.
FromSqlwith an interpolated string turns each interpolated value into a parameter, so it is safe from SQL injection; you can compose LINQ on top of it.FromSqlRawwith string concatenation is not safe.SqlQuery<T>(EF Core 8+) maps results to unmapped types such as DTOs.var term = "%" + search + "%"; var blogs = await db.Blogs .FromSql(quot;SELECT * FROM dbo.Blogs WHERE Name LIKE {term}") .Where(b => b.IsActive) .ToListAsync(ct);Q10. A LINQ query throws 'could not be translated' at runtime. Why, and how do you fix it?
EF Core must translate the whole Where/OrderBy to SQL. Calling a custom C# method or an unsupported function inside it can't be translated, and since EF Core 3.0 it throws instead of silently loading everything into memory.
Rewrite the condition with translatable operations, move the custom logic after a projection to the final
Select, or explicitly switch to client evaluation withAsEnumerable()after filtering as much as possible in SQL.Q11. How do you map a value object like an Address that has no identity of its own?
Use an owned type (
OwnsOne) or, in EF Core 8+, a complex type (ComplexProperty). Both store the properties in the owner's table (Address_Street, Address_City...).Complex types are the better fit for true value objects: they have no key and can be shared by value. Owned types are still entity types with a hidden key, and can also map to a separate table or JSON column.
modelBuilder.Entity<Customer>().ComplexProperty(c => c.Address);Q12. Your API runs the same EF Core query thousands of times per second. What can reduce EF overhead?
Use DbContext pooling (
AddDbContextPool), which reuses context instances instead of creating new ones, and compiled queries (EF.CompileAsyncQuery), which skip LINQ translation on each call.Measure first: usually indexes, projections and no-tracking matter far more. Pooled contexts must not keep per-request state in fields.
private static readonly Func<AppDbContext, int, Task<Product?>> ById = EF.CompileAsyncQuery((AppDbContext db, int id) => db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id));
Original questions written to explain concepts; not taken from any certification exam. Spotted a mistake? See our corrections policy.