EF Core Interview Questions
Commonly asked EF Core interview questions with clear, practical answers.
A curated set of EF Core interview questions covering the migrations workflow, query loading strategies, and change-tracking behavior.
Migrations
Q: Walk through the EF Core migrations workflow.
Change your entity classes, run dotnet ef migrations add <Name> to generate a migration file (diffing the current model against the last snapshot), review the generated Up()/Down() methods, then run dotnet ef database update to actually apply it to the database. Nothing touches the real schema until that last step.
Q: Why should you always review a generated migration before applying it? EF Core infers intent from a diff of your model, and some changes are ambiguous — most notably, renaming a property can be (mis)detected as dropping the old column and adding a new one, which silently loses that column's data instead of preserving it as a rename.
Loading strategies
Q: What's the difference between eager loading and lazy loading?
Eager loading (.Include(...)) fetches related data up front, in the same query, via a SQL join. Lazy loading fetches related data automatically, on-demand, the moment a navigation property is first accessed — convenient, but a common cause of accidental N+1 query patterns when it's enabled and a loop touches a navigation property on each iteration. EF Core requires lazy loading to be explicitly opted into (via proxies); eager loading with Include is the default, recommended approach.
Q: What is the "N+1 query problem" and how does Include prevent it?
It's the pattern of running one query to get a list of N entities, then running one additional query per entity to fetch each one's related data — N+1 total round trips instead of one. .Include() avoids this by fetching the related data in the same single query (via a join) up front.
Change tracking
Q: What's the difference between a tracking and a no-tracking query?
By default, entities returned by a query are "tracked" — EF Core keeps a reference to them and monitors their properties for changes so a later SaveChanges() knows what to update. .AsNoTracking() skips that bookkeeping entirely, returning plain entities EF Core no longer watches — faster and lower-memory for read-only queries that will never be saved back.
Q: When would you use AsNoTracking()?
For any query whose results you're only going to read and display, never modify and save — a GET endpoint, a report, a page of search results. Skipping change tracking there removes memory and CPU overhead with no downside, since there's nothing to track changes for.
Q: What does SaveChanges() actually do?
It looks at every entity the context is currently tracking, determines each one's state (Added, Modified, Deleted, Unchanged), generates the corresponding SQL for anything that changed, and executes all of it inside a single database transaction — either every pending change commits, or none of them do.
Performance and concurrency
Q: What's the difference between AsNoTracking() and AsSplitQuery()?
They solve unrelated problems. AsNoTracking() skips change-tracking bookkeeping for a query whose results are only read, never saved back. AsSplitQuery() changes how EF Core loads multiple Include()d collections — issuing one query per collection instead of a single JOIN — to avoid the row duplication a single JOIN produces when more than one collection navigation is included at once.
Q: How does EF Core detect an optimistic concurrency conflict, and what exception does it throw?
Through a concurrency token column — typically a [Timestamp]/rowversion column the database changes automatically on every write. EF Core includes that column's last-known value in the generated UPDATE's WHERE clause; if another transaction already changed the row, zero rows match, and EF Core raises DbUpdateConcurrencyException instead of silently succeeding with no effect.