Entity Framework Core Interview Questions – Complete Guide for .NET Developers
Entity Framework Core Interview Questions
Entity Framework Core (EF Core) is Microsoft’s modern Object-Relational Mapper (ORM) for .NET. It eliminates the need for most data-access code by letting developers work with databases using .NET objects. Understanding EF Core is essential for any .NET developer interview as it’s the most widely used ORM in the .NET ecosystem.
Q1: What is Entity Framework Core and how does it differ from traditional ADO.NET?
Answer: Entity Framework Core is a lightweight, extensible ORM that allows developers to interact with databases using C# objects instead of writing raw SQL. Unlike ADO.NET where you manually write SQL queries, manage connections, and map results to objects, EF Core handles all of this automatically through LINQ queries and change tracking.
// ADO.NET approach (manual)
using var connection = new SqlConnection(connectionString);
var command = new SqlCommand("SELECT * FROM Products WHERE Price > @price", connection);
command.Parameters.AddWithValue("@price", 100);
// EF Core approach (automatic)
var products = await dbContext.Products
.Where(p => p.Price > 100)
.ToListAsync();
Q2: What is Code First vs Database First approach?
Answer: In Code First, you define C# classes (entities) first, and EF Core generates the database schema from them using migrations. In Database First, the database already exists, and you scaffold C# models from it. Code First is preferred for new projects as it gives full control over the domain model.
// Code First: Define entity class
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
// DbContext configuration
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
}
}
// Database First: Scaffold from existing DB
// dotnet ef dbcontext scaffold "ConnectionString" Microsoft.EntityFrameworkCore.SqlServer
Q3: What are Migrations in EF Core and how do you use them?
Answer: Migrations track changes to your data model and generate SQL scripts to update the database schema incrementally. They maintain a history of schema changes, making it easy to version-control your database alongside your code.
// Step 1: Add a migration after model changes
// dotnet ef migrations add AddProductTable
// Step 2: Apply migration to database
// dotnet ef database update
// Step 3: Generate SQL script for production
// dotnet ef migrations script
// Programmatic migration in Program.cs
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.Migrate();
}
Q4: What is Change Tracking in EF Core?
Answer: Change Tracking is EF Core’s mechanism for detecting what has changed in your entities since they were loaded from the database. It tracks entity states — Added, Modified, Deleted, Unchanged, and Detached — and generates appropriate SQL statements when SaveChanges() is called.
var product = await dbContext.Products.FindAsync(1);
product.Price = 29.99m; // EF Core detects this change
// Check entity state
var entry = dbContext.Entry(product);
Console.WriteLine(entry.State); // Modified
// SaveChanges generates: UPDATE Products SET Price = 29.99 WHERE Id = 1
await dbContext.SaveChangesAsync();
// Disable tracking for read-only queries (performance boost)
var products = await dbContext.Products
.AsNoTracking()
.ToListAsync();
Q5: What is AsNoTracking() and when should you use it?
Answer: AsNoTracking() tells EF Core not to track entities in the change tracker. This improves performance for read-only queries because EF Core skips change detection overhead. Use it whenever you only need to display data without modifying it, like in API GET endpoints or reports.
// With tracking (default) - slower, needed for updates
var product = await dbContext.Products.FirstAsync(p => p.Id == 1);
product.Name = "Updated"; // Change is tracked
await dbContext.SaveChangesAsync(); // UPDATE executed
// Without tracking - faster, read-only
var products = await dbContext.Products
.AsNoTracking()
.Where(p => p.IsActive)
.ToListAsync();
// Global no-tracking (in DbContext configuration)
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
Q6: What is Lazy Loading vs Eager Loading vs Explicit Loading?
Answer: These are three strategies for loading related data. Eager Loading uses Include() to load related data in a single query. Lazy Loading loads related data on-demand when the navigation property is accessed. Explicit Loading loads related data manually using the Entry API.
// Eager Loading - loads related data in one query (recommended)
var orders = await dbContext.Orders
.Include(o => o.Customer)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.ToListAsync();
// Explicit Loading - load related data on demand
var order = await dbContext.Orders.FindAsync(1);
await dbContext.Entry(order)
.Collection(o => o.OrderItems)
.LoadAsync();
// Lazy Loading - requires virtual navigation properties
// and Microsoft.EntityFrameworkCore.Proxies package
public class Order
{
public int Id { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
Q7: How do you handle database transactions in EF Core?
Answer: EF Core wraps each SaveChanges() call in a transaction automatically. For operations spanning multiple SaveChanges calls or combining EF Core with raw SQL, use explicit transactions via BeginTransaction().
// Automatic transaction (single SaveChanges)
dbContext.Products.Add(new Product { Name = "Item A" });
dbContext.Products.Add(new Product { Name = "Item B" });
await dbContext.SaveChangesAsync(); // Both in one transaction
// Explicit transaction (multiple operations)
using var transaction = await dbContext.Database.BeginTransactionAsync();
try
{
dbContext.Orders.Add(newOrder);
await dbContext.SaveChangesAsync();
dbContext.Inventory.Update(updatedStock);
await dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
Q8: What are Global Query Filters in EF Core?
Answer: Global Query Filters apply automatic WHERE conditions to all queries for an entity. They are commonly used for soft-delete patterns and multi-tenant applications where you always want to filter by tenant or active status.
public class AppDbContext : DbContext
{
public int CurrentTenantId { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Soft delete filter - always exclude deleted records
modelBuilder.Entity<Product>()
.HasQueryFilter(p => !p.IsDeleted);
// Multi-tenant filter
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == CurrentTenantId);
}
}
// All queries automatically apply the filter
var products = await dbContext.Products.ToListAsync();
// Generated SQL: SELECT * FROM Products WHERE IsDeleted = 0
// Bypass filter when needed
var allProducts = await dbContext.Products
.IgnoreQueryFilters()
.ToListAsync();
Q9: What are some EF Core performance optimization techniques?
Answer: Key optimizations include using AsNoTracking for read-only queries, Select projections to load only needed columns, compiled queries for repeated patterns, batching operations, and avoiding the N+1 query problem with proper Include usage.
// 1. Projection - load only needed fields
var productNames = await dbContext.Products
.Select(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// 2. Compiled Queries - pre-compile frequently used queries
private static readonly Func<AppDbContext, int, Task<Product>> GetProductById =
EF.CompileAsyncQuery((AppDbContext ctx, int id) =>
ctx.Products.FirstOrDefault(p => p.Id == id));
var product = await GetProductById(dbContext, 42);
// 3. Batch operations with ExecuteUpdate/ExecuteDelete (.NET 7+)
await dbContext.Products
.Where(p => p.Price < 10)
.ExecuteDeleteAsync();
await dbContext.Products
.Where(p => p.CategoryId == 5)
.ExecuteUpdateAsync(s => s.SetProperty(p => p.IsActive, false));
// 4. Split queries to avoid cartesian explosion
var orders = await dbContext.Orders
.Include(o => o.OrderItems)
.AsSplitQuery()
.ToListAsync();
Q10: What is the difference between Add, Attach, and Update methods?
Answer: Add() marks an entity as Added — EF Core will INSERT it. Attach() marks an entity as Unchanged — useful when you have a disconnected entity and want to start tracking it without changes. Update() marks an entity as Modified — EF Core will UPDATE all its properties.
// Add - INSERT new entity
var newProduct = new Product { Name = "New Item", Price = 19.99m };
dbContext.Products.Add(newProduct);
// INSERT INTO Products (Name, Price) VALUES ('New Item', 19.99)
// Attach - start tracking without marking as modified
var existingProduct = new Product { Id = 1, Name = "Existing" };
dbContext.Products.Attach(existingProduct);
// No SQL generated until you modify a property
// Update - mark all properties as modified
var updatedProduct = new Product { Id = 1, Name = "Updated", Price = 29.99m };
dbContext.Products.Update(updatedProduct);
// UPDATE Products SET Name='Updated', Price=29.99 WHERE Id=1
await dbContext.SaveChangesAsync();
Key Takeaways for Interviews
- Always use AsNoTracking() for read-only queries to improve performance
- Prefer Eager Loading with Include() over Lazy Loading to avoid N+1 problems
- Use Migrations for database version control in Code First approach
- Project only needed columns using Select() to reduce data transfer
- Use Global Query Filters for cross-cutting concerns like soft delete and multi-tenancy
- Batch operations with ExecuteUpdate/ExecuteDelete for bulk changes without loading entities