Entity Framework Core Interview Questions for .NET Full Stack
25 Entity Framework Core questions with answers for .net full stack roles — 5 easy, 11 medium, 9 hard.
About Entity Framework Core
Data access with EF Core: DbContext, code-first migrations, relationships, loading strategies, change tracking, raw SQL, bulk updates, transactions, concurrency tokens, query filters and performance tuning. Interviewers check whether you can avoid N+1 queries, write efficient projections and deploy schema changes safely to databases holding live production data.
Sheet progressStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
EF Core is Microsoft's object-relational mapper for .NET. It maps C# classes to database tables, translates LINQ queries into SQL, tracks changes to loaded objects and saves them with SaveChanges. It supports SQL Server, PostgreSQL, MySQL, SQLite, Cosmos DB and more through providers. It removes most hand-written data access code, while still allowing raw SQL when needed. EF Core 10 ships with .NET 10.
02
What is the difference between code-first and database-first approaches?
In code-first, you write entity classes and a DbContext, and EF Core migrations create and evolve the database schema from code; it suits new projects and keeps schema in source control. In database-first, the database already exists and you scaffold entities with dotnet ef dbcontext scaffold. It suits legacy databases managed by a DBA team. Many teams scaffold once and then continue code-first.
03
What are DbContext and DbSet, and what lifetime should DbContext have?
DbContext represents a session with the database: it holds configuration, the change tracker and connection handling. Each DbSet<T> property represents a table you can query and add to. DbContext is lightweight and not thread-safe, so it should be short-lived; in ASP.NET Core it is registered as scoped, one per request, via AddDbContext. For background services or Blazor, use IDbContextFactory to create contexts on demand.
04
Which commands do you use to create and apply EF Core migrations?
dotnet tool install --global dotnet-ef
# create a migration from model changes
dotnet ef migrations add AddOrderStatus
# apply pending migrations to the database
dotnet ef database update
# undo the last unapplied migration
dotnet ef migrations remove
# generate an idempotent SQL script for production
dotnet ef migrations script --idempotent -o deploy.sql
In Visual Studio the Package Manager Console equivalents are Add-Migration and Update-Database.
05
How do you keep up with new .NET and EF Core releases?
Microsoft ships a major .NET version every November, so I plan for it. I read the official What's New pages and the .NET blog, watch .NET Conf sessions, and try new features in a small side project before suggesting them at work. For example, when ExecuteUpdate arrived, I tested it on a sample and then proposed replacing a slow loop in our cleanup job, which cut its runtime significantly.
06
Explain eager, lazy and explicit loading in EF Core.
Eager loading fetches related data in the same query using Include and ThenInclude, for example orders with their items. Lazy loading loads a navigation property automatically the first time you access it, requiring proxies and virtual properties; it is convenient but easily causes N+1 queries. Explicit loading loads a navigation on demand with Entry(order).Collection(o => o.Items).LoadAsync(). Projection with Select is often better than all three.
07
A page listing 100 orders with customer names runs 101 SQL queries. What is the problem and how do you fix it?
This is the N+1 problem: one query loads the orders, then lazy loading fires a separate query for each order's Customer. Fix it by loading everything in one query, either with Include(o => o.Customer) or better with a projection: Select(o => new OrderListDto(o.Id, o.Total, o.Customer.Name)), which fetches only needed columns via a join. Turn on EF Core logging to spot such patterns early.
08
A read-only product listing endpoint in EF Core uses a lot of memory and CPU. What simple change helps, and why?
By default, EF Core tracks every entity it loads so it can detect changes on SaveChanges, which costs memory and CPU. AsNoTracking tells EF Core not to track the results, making read-only queries faster and lighter. Use it for lists, reports and GET endpoints where you will not update the data. AsNoTrackingWithIdentityResolution avoids duplicate instances of the same entity in results while still skipping tracking.
09
Configure a one-to-many relationship between Customer and Order using the Fluent API.
public class Customer { public int Id { get; set; } public List<Order> Orders { get; set; } = []; }
public class Order { public int Id { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } = null!; }
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Order>()
.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId)
.OnDelete(DeleteBehavior.Restrict);
mb.Entity<Order>().Property(o => o.Total).HasPrecision(18, 2);
}
Restrict prevents deleting a customer who still has orders.
10
How does EF Core handle many-to-many relationships?
Since EF Core 5, you can define collection navigations on both sides, such as Student.Courses and Course.Students, and EF Core creates a hidden join table automatically. If the join needs extra data, like EnrolledOn or Grade, create an explicit join entity Enrollment with two foreign keys and configure it with UsingEntity or two one-to-many relationships. Querying works naturally with Include or projections through the navigations.
// mark all unpaid invoices older than 30 days as overdue
int updated = await db.Invoices
.Where(i => !i.IsPaid && i.DueDate < DateTime.UtcNow.AddDays(-30))
.ExecuteUpdateAsync(s => s
.SetProperty(i => i.Status, "Overdue")
.SetProperty(i => i.UpdatedAt, DateTime.UtcNow));
// delete old logs
int deleted = await db.AuditLogs
.Where(l => l.CreatedAt < cutoff)
.ExecuteDeleteAsync();
These run a single SQL UPDATE or DELETE without loading entities, available since EF Core 7. They bypass the change tracker.
13
How do you use an explicit transaction in EF Core across multiple SaveChanges calls?
await using var tx = await db.Database.BeginTransactionAsync();
try
{
db.Orders.Add(order);
await db.SaveChangesAsync();
var stock = await db.Stocks.SingleAsync(s => s.ProductId == order.ProductId);
stock.Quantity -= order.Quantity;
await db.SaveChangesAsync();
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}
A single SaveChanges is already transactional; explicit transactions are needed when you combine several saves or raw SQL into one unit.
14
A developer runs two EF Core queries in parallel with Task.WhenAll on the same DbContext and gets an InvalidOperationException. Why?
DbContext is not thread-safe and supports only one active operation at a time, so starting a second query before the first finishes throws 'A second operation was started on this context instance'. Either await the queries one after another, or create a separate context for each parallel query using IDbContextFactory. Usually sequential queries are fine; parallelism only helps when the queries are slow and independent.
15
Explain a technical trade-off you had to justify to a manager or client.
On a reporting module (Situation) the manager wanted everything in EF Core for consistency, but one report was too slow. My task was to recommend a fix without derailing the plan. I measured the EF query against a Dapper version with a hand-tuned SQL query, showed the 8x difference and explained that we would use Dapper only for that read path. Result: the manager approved, the report loaded in under a second, and the rest stayed in EF Core.
public async Task<PagedResult<ProductDto>> GetPageAsync(int page, int size)
{
var query = db.Products.AsNoTracking().OrderBy(p => p.Id);
int total = await query.CountAsync();
var items = await query
.Skip((page - 1) * size)
.Take(size)
.Select(p => new ProductDto(p.Id, p.Name, p.Price))
.ToListAsync();
return new PagedResult<ProductDto>(items, total, page, size);
}
Always order before Skip and Take. For very deep pages, keyset paging with Where(p => p.Id > lastId) is faster than large offsets.
17
How does change tracking work and what are entity states?
The change tracker keeps a snapshot of each tracked entity and compares it on SaveChanges. Each entity has a state: Added (insert), Modified (update), Deleted (delete), Unchanged (no action) and Detached (not tracked). Calling Add, Update or Remove sets states, and changing a property of a tracked entity marks it Modified. SaveChanges then generates SQL only for changed columns, all inside one transaction.
18
Two support agents edit the same customer record at once and one agent's changes silently overwrite the other's. How do you prevent this?
Use optimistic concurrency. Add a [Timestamp] byte[] RowVersion property, which maps to SQL Server rowversion. EF Core includes it in the WHERE clause of updates; if another user changed the row, zero rows are affected and DbUpdateConcurrencyException is thrown. Catch it, reload current values and show the agent a conflict message or merge screen. This avoids locking rows while users are editing on screen.
Project only needed columns with Select instead of loading full entities. Use AsNoTracking for reads. Avoid N+1 with Include or projections. Page with Skip and Take or keyset paging. Add proper indexes via HasIndex. Use ExecuteUpdate and ExecuteDelete for bulk changes. For hot queries, compiled queries with EF.CompileAsyncQuery and DbContext pooling with AddDbContextPool reduce overhead. Always check generated SQL with logging or ToQueryString.
21
An EF Core query that includes Items and Payments for each order returns a huge, duplicated result set. What is happening and how do split queries help?
When you Include two or more collection navigations, such as Orders with Items and Payments, EF Core generates one query with joins. Rows multiply: 10 items times 5 payments return 50 rows per order, duplicating data heavily. AsSplitQuery() tells EF Core to run separate queries per collection and stitch results together, avoiding duplication. The trade-off is more round trips and possible inconsistency without a transaction.
22
Your team debates wrapping EF Core in a generic repository. What is your view?
DbContext already implements Repository and Unit of Work, so a thin generic repository with GetAll and Add often adds code while hiding useful features like Include, projections and AsNoTracking. I prefer either using DbContext directly in services, or specific repositories with meaningful methods like GetOverdueInvoicesAsync that encapsulate queries. For tests, use the SQLite in-memory provider or Testcontainers rather than mocking DbSet.
23
A complex reporting query written in EF Core is slow and hard to tune. When would you move it to Dapper?
Dapper is a micro-ORM that maps raw SQL results to objects with very low overhead. It suits read-heavy reports, complex hand-tuned queries and stored procedures where you want full SQL control. EF Core offers change tracking, migrations, LINQ and relationships, which speed up CRUD development. Many projects use both: EF Core for writes and normal features, Dapper for performance-critical queries, sharing the same connection string.
24
You must rename a column and split a Name field into FirstName and LastName in a production database with live data. How do you deploy safely?
Avoid destructive one-step migrations. Use an expand-and-contract approach: first add new nullable columns, deploy code that writes both old and new, and backfill data with a script in batches. Then switch reads to the new columns, and only in a later release drop the old column. Generate idempotent SQL scripts, review them with the DBA, take a backup and test on a production copy first.
25
Your multi-tenant SaaS app keeps all tenants in one database. How do you make sure EF Core queries never return another tenant's data?
Add a TenantId column to every tenant-owned entity and a global query filter such as HasQueryFilter(e => e.TenantId == _tenantId), where the tenant ID comes from a scoped provider that reads the authenticated user's claim. Set TenantId automatically in SaveChanges for new rows. Index TenantId, write integration tests that try cross-tenant access, audit every IgnoreQueryFilters call, and consider SQL Server row-level security as a second layer of defence.
Frequently asked questions
How important is Entity Framework Core in .NET Full Stack interviews?
Data access with EF Core: DbContext, code-first migrations, relationships, loading strategies, change tracking, raw SQL, bulk updates, transactions, concurrency tokens, query filters and performance tuning. Interviewers check whether you can avoid N+1 queries, write efficient projections and deploy schema changes safely to databases holding live production data.
How should I use the Entity Framework Core sheet?
Go top to bottom — easy to hard. Answer before opening each answer, tick what you solved and save misses to Last-Minute Revision.