Interview Handbook by Sayed Elmahdy العربية

Topic 09 · Data & databases

Entity Framework Core

DbContext, tracking, loading strategies, N+1, migrations, concurrency, Dapper, performance.

20 questionsJunior · 6Mid · 9Senior · 510 quiz

Concepts

What you will learn: Follow data between C# objects and database tables: how translates , tracks changes and writes them, and when a simple request can issue many queries or return duplicate rows. We will cover loading, tracking, concurrency and migrations while keeping visible.

Before you start: An maps application objects to database tables. An entity represents domain data, and a is a short-lived unit of database work. Basic table and SQL knowledge helps; if needed, study topic 10 alongside this one.

Select a term with a dotted underline for its meaning, then continue reading from the same place.

#EF Core and DbContext as a unit of work

Repeated mapping and cost development time. is an that translates supported , materializes objects, tracks changes and persists them. It does not replace SQL knowledge or provider-specific behavior.

A is a short-lived work folder: collect related changes, then submit them through SaveChanges.

using Microsoft.EntityFrameworkCore;

public sealed class StoreDb(DbContextOptions<StoreDb> options) : DbContext(options)
{
    public DbSet<Product> Products => Set<Product>();       // (1)
}
public sealed class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public decimal Price { get; set; }
}

// In Program.cs, with the SQL Server provider installed:
builder.Services.AddDbContext<StoreDb>(o => o.UseSqlServer(connectionString)); // (2)
// In a scoped handler receiving StoreDb db:
db.Products.Add(new Product { Name = "Mug", Price = 120m });
await db.SaveChangesAsync(ct);                             // (3)
🧾 Code walkthrough
  1. (1) A DbSet is a query root and an entry point for adding entities; mappings need not always be one table per set.
  2. (2) AddDbContext registers a scoped context. Never share it across threads or run concurrent operations on it. Background jobs can use IDbContextFactory and dispose each context.
  3. (3) The unit of work persists tracked changes. Relational providers use a by default for a SaveChanges call when atomicity is needed.
BenefitCost
LINQ, mapping, tracking and migrationsMaterialization and tracking overhead
Value parameterizationIncorrectly assembled raw SQL remains unsafe
Multiple providersDifferent translations, types and capabilities

A generic repository duplicating DbSet and SaveChanges may add little. Introduce abstractions when they protect a boundary or meaningful business queries.

#Fluent API and schema ownership

Conventions are a useful start, but precision, relationships and indexes need deliberate design. Fluent separates persistence configuration from entity definitions.

using Microsoft.EntityFrameworkCore.Metadata.Builders;

public sealed class ProductConfig : IEntityTypeConfiguration<Product>
{
    public void Configure(EntityTypeBuilder<Product> e)
    {
        e.ToTable("Products");
        e.HasKey(p => p.Id);                              // (1)
        e.Property(p => p.Name).HasMaxLength(200).IsRequired();
        e.Property(p => p.Price).HasPrecision(18, 2);       // (2)
        e.HasIndex(p => p.Name);                          // (3)
    }
}
// Inside StoreDb:
// protected override void OnModelCreating(ModelBuilder b) =>
//     b.ApplyConfigurationsFromAssembly(typeof(StoreDb).Assembly);
🧾 Code walkthrough
  1. (1) A key provides identity; independent business uniqueness needs unique constraints.
  2. (2) Name length and price precision avoid accidental defaults. Application validation remains necessary.
  3. (3) Index by access pattern, not every field. A migration applies the change.

Configure relationships with HasMany, WithOne and HasForeignKey, choosing DeleteBehavior deliberately. Database-first scaffolds from an existing schema; code-first evolves a model through migrations. Choose one schema authority and plan how customizations survive repeated scaffolding.

#Loading strategies and projection

A worked example
  1. An orders screen needs the order ID, customer name and line count, not every line's details. Order.Lines is a : a property representing related data.
  2. Eager loading requests relationships up front; explicit loading deliberately requests one later; lazy loading may query automatically when a navigation is read.
  3. uses Select to describe the result shape: just the ID, name and count. Inspect generated ; a smaller shape may avoid transferring many objects entirely.

An order summary does not need every product and payment. Fetch the smallest useful shape and keep database round trips visible.

// These snippets assume an Order model with Lines and Customer navigations.
var entity = await db.Orders.Include(o => o.Lines)
    .SingleAsync(o => o.Id == id, ct);                     // (1)

await db.Entry(entity).Reference(o => o.Customer).LoadAsync(ct); // (2)

var summary = await db.Orders.Where(o => o.Id == id)
    .Select(o => new { o.Id, o.Total, LineCount = o.Lines.Count })
    .SingleAsync(ct);                                     // (3)
🧾 Code walkthrough
  1. (1) Include performs eager loading; ThenInclude follows another level. It may use a single or split query.
  2. (2) Explicit loading deliberately requests another relationship; use Collection for collections.
  3. (3) Projection translates the requested columns and count into SQL. Since no entity is returned, there is no entity tracking.

Lazy loading queries when a navigation is accessed. Proxies require their package, configuration and overridable navigations; ILazyLoader is an alternative. Both can hide and cause . A projection containing an entity, such as new { Order = o }, can still track that entity.

#Tracking, no-tracking and identity resolution

When editing an entity, needs its original and current state. A read-only catalog usually does not need that bookkeeping.

Tracking keeps a before-edit copy so changes can be identified rather than guessed.

var product = await db.Products.SingleAsync(p => p.Id == id, ct); // (1)
product.Price = newPrice;
await db.SaveChangesAsync(ct);

var catalog = await db.Products.AsNoTracking()
    .OrderBy(p => p.Id).Take(20).ToListAsync(ct);             // (2)

var graph = await db.Orders.AsNoTrackingWithIdentityResolution()
    .Include(o => o.Customer).Take(20).ToListAsync(ct);       // (3)
🧾 Code walkthrough
  1. (1) A tracked entity moves from Unchanged to Modified when changes are detected. Other states include Added, Deleted and Detached. SaveChanges normally invokes DetectChanges.
  2. (2) Full entities are not attached to the context; edits are not saved automatically.
  3. (3) A temporary identity map reuses objects with the same key within the result without retaining context tracking.

A tracking query may return an existing context instance with its current values; it is not a guaranteed refresh. For detached updates, attach carefully and specify allowed fields and original concurrency . Calling Update on a client-provided graph can mark unwanted fields and enable over-posting.

#N+1, cartesian explosion and split queries

A worked example
  1. Assume 10 orders and enabled lazy loading. One query fetches the orders; reading Lines for each order triggers another query.
  2. That is 1 + 10 = 11 queries. N is the order count, hence ; 100 orders can produce 101 round trips.
  3. If only counts are needed, a translatable Select can calculate them in . Cartesian explosion is a different issue: multiple relationships multiply result rows even when the query count is small.

An endpoint can be slow because of too many round trips or too much data in one query. Those are different problems.

❌ With lazy loading: one query per order
var orders = await db.Orders.ToListAsync(ct);
foreach (var order in orders)
    Console.WriteLine(order.Lines.Count);
✅ Compute the count in SQL
var counts = await db.Orders
    .Select(o => new { o.Id, Count = o.Lines.Count })
    .ToListAsync(ct);
var orders = await db.Orders
    .Include(o => o.Lines)
    .Include(o => o.Payments)
    .AsSplitQuery()                                       // (1)
    .OrderBy(o => o.Id).Take(20).ToListAsync(ct);
🧾 Code walkthrough
  1. (1) Joining sibling collections can produce 10 lines × 5 payments = 50 rows per order. Split queries retrieve roots and collections separately and assemble them.

Splitting reduces duplication but adds round trips and buffering. A Read Committed alone does not create a shared snapshot; if statements must agree, consider suitable snapshot or serializable and its cost. Inspect SQL logs, query counts and actual plans. Do not enable sensitive data logging in production merely to investigate performance.

#Set-based changes in EF Core 10

Loading thousands of entities just to modify them costs memory, tracking and work. Set-based operations change matching rows directly in the database.

int changed = await db.Orders.Where(o => o.Status == "Pending")
    .ExecuteUpdateAsync(setters =>                         // (1)
    {
        setters.SetProperty(o => o.Status, "Expired");
        if (stampTime)
            setters.SetProperty(o => o.UpdatedAt, now);
    }, ct);

int deleted = await db.Orders
    .Where(o => o.Status == "Draft" && o.CreatedAt < cutoff)
    .ExecuteDeleteAsync(ct);                              // (2)
🧾 Code walkthrough
  1. (1) 10 accepts a regular block with conditionals to build setters. Property expressions still need SQL translation.
  2. (2) Delete executes immediately, without SaveChanges. Both methods return affected row counts.
Before EF 10EF 10
Expression-only setter builder; conditional composition needs expression manipulationA regular lambda simplifies conditional setters
The tracker remains unchanged and SaveChanges interceptors do not run; command interceptors and database triggers can still run. Automatic checks are absent: include the in WHERE and check the count. Calls are separate operations; group them in an explicit if needed. Reload stale tracked objects or use a fresh context.

#Compiled queries and DbContext poolingBonus

Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.

After fixing and indexes, measurement may identify query cache lookup or context setup as meaningful overhead. These optimizations target those costs.

// A field inside a service class; all contexts must use the same EF model.
private static readonly Func<StoreDb, int, Task<Product?>> FindProduct =
    EF.CompileAsyncQuery((StoreDb db, int id) =>
        db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id)); // (1)

// Usage:
var product = await FindProduct(db, id);

// Alternative registration to AddDbContext:
builder.Services.AddDbContextPool<StoreDb>(
    o => o.UseSqlServer(connectionString));                // (2)
🧾 Code walkthrough
  1. (1) already caches queries. A compiled bypasses lookup and comparison; it does not improve bad SQL. This point-lookup example is intentionally small; cancellation and model requirements need an appropriate overload and design.
  2. (2) Reuses context instances after resetting EF state. It is distinct from driver connection pooling.

OnConfiguring does not run for every lease. Set tenant identifiers and other custom mutable state safely per use and clean it up; EF cannot reset arbitrary application or manually changed connection state. A pooled context is not a shared singleton. Benchmark representative workloads before adopting either optimization.

#Production migrations and expand-contract

A change can lock a table or break old instances during a rolling deployment. A migration is reviewable deployment input, not an instruction for every instance to alter production automatically.

# Requires matching dotnet-ef tools and EF Core Design package.
dotnet ef migrations add AddMobileNumber
dotnet ef migrations script --idempotent --output migrate.sql
# Alternative deployment artifact; self-contained requires an explicit option.
dotnet ef migrations bundle --self-contained -r linux-x64 -o efbundle

Review , test production-like volumes, and plan backups, forward repair and lock duration. Execute a script or bundle in one controlled deployment step with separate privileges. Modern migration locking exists for some providers, but cannot solve compatibility, application privileges or every risk.

  1. Expand: add nullable MobileNumber while retaining Phone.
  2. Deploy compatible code. While old writers remain, keep the old field authoritative or maintain reliable synchronization.
  3. After old writers retire, dual-write and backfill in batches while handling concurrent writes.
  4. Verify completeness, switch reads and monitor.
  5. Contract: remove the old field in a later release after rollback paths and old consumers no longer need it.

Code-first compares the model with its migration snapshot, not the entire live database. Define ownership and detect drift. Database-first suits externally managed schemas. Do not mix EnsureCreated with migrations for the same database.

#Optimistic and pessimistic concurrency

Two users edit the same product; the last save should not silently erase the first change. detects a changed version when writing.

// On an order entity, add: public byte[] Version { get; set; } = [];
// SQL Server configuration:
modelBuilder.Entity<Order>().Property(o => o.Version).IsRowVersion(); // (1)

try
{
    order.Status = "Confirmed";
    await db.SaveChangesAsync(ct);                         // (2)
}
catch (DbUpdateConcurrencyException)
{
    return Results.Conflict(new { code = "order.changed" }); // (3)
}
🧾 Code walkthrough
  1. (1) Server generates rowversion; it is not a timestamp date. Other providers may use application-managed configured with IsConcurrencyToken.
  2. (2) UPDATE or DELETE includes the key and original version in WHERE. If the expected row is not affected, raises an exception.
  3. (3) Return a conflict or implement an explicit merge. Blind retries can overwrite user intent; reload and revalidate before repeating.
-- PostgreSQL: inside a short transaction, lock before read-modify-write.
SELECT id, available FROM products WHERE id = @id FOR UPDATE;

-- For a simple reservation, an atomic statement can be enough.
UPDATE products SET available = available - @qty
WHERE id = @id AND available >= @qty;

Pessimistic locking blocks competing changes until the ends, at the cost of blocking and . EF has no universal lock hint across providers. For conditional updates, validate positive quantity and inspect affected rows; zero can mean either a missing product or insufficient stock.

#Transactions and execution strategies

One SaveChanges call often suffices. Multiple saves or bulk statements may need a shared ; a retry strategy must replay the entire unit.

// probe and factory use identical provider options with retries configured.
await using var probe = await factory.CreateDbContextAsync(ct);
var strategy = probe.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>                    // (1)
{
    await using var db = await factory.CreateDbContextAsync(ct);
    await using var tx = await db.Database.BeginTransactionAsync(ct); // (2)
    var order = await db.Orders.SingleAsync(o => o.Id == orderId, ct);
    order.Status = "Confirmed";
    await db.SaveChangesAsync(ct);
    await db.OrderLines.Where(l => l.OrderId == orderId)
        .ExecuteUpdateAsync(s => s.SetProperty(l => l.IsReserved, true), ct);
    await tx.CommitAsync(ct);                              // (3)
});
🧾 Code walkthrough
  1. (1) The entire can run again; a fresh context avoids reusing tracked state from a failed attempt.
  2. (2) Start the transaction inside the strategy. Disposal without a successful commit leaves it uncommitted and rolls it back according to provider behavior.
  3. (3) Losing the connection during commit can leave the outcome unknown. These changes set fixed values; creating payments needs or a verification strategy.

Never send email or charge a gateway inside the retry delegate; persist an entry in the transaction. SaveChanges inside an existing transaction may use savepoints; Server MARS prevents automatic savepoints. Cancellation does not prove that commit failed. Do not retry every exception, especially validation and business conflicts.

#Safe raw SQL and named query filters

A complex query may need direct . That escape hatch must preserve parameterization and tenant .

var products = await db.Products
    .FromSql($"SELECT * FROM Products WHERE Price > {minimum}") // (1)
    .AsNoTracking().ToListAsync(ct);

var prices = await db.Database
    .SqlQuery<decimal>($"SELECT Price AS Value FROM Products WHERE Id = {id}")
    .ToListAsync(ct);                                     // (2)

// In OnModelCreating; tenantId is current context state.
modelBuilder.Entity<Order>()
    .HasQueryFilter("SoftDelete", o => !o.IsDeleted)
    .HasQueryFilter("Tenant", o => o.TenantId == tenantId);  // (3)
var archived = await db.Orders.IgnoreQueryFilters(["SoftDelete"])
    .ToListAsync(ct);                                     // (4)
🧾 Code walkthrough
  1. (1) FromSql receives an interpolated FormattableString directly and parameterizes minimum. Entity queries must return the columns required by mapping; this uses the simple Product model.
  2. (2) SqlQuery supports scalar or unmapped results. Value is useful when composing over a scalar query. SQL Server procedure calls are not composable like SELECT.
  3. (3) 10 introduces named filters. Earlier versions combine conditions with && in one filter.
  4. (4) Disables only soft deletion while retaining Tenant. The parameterless IgnoreQueryFilters disables everything.
Do not turn interpolation into a string and pass it to FromSqlRaw. Parameterize values and allowlist identifiers. Filters are not a complete security boundary: raw commands, bypass paths and writes need checks; database row-level security can add protection. Required with filters can unexpectedly remove parent rows through INNER JOIN.

#Value mapping and choosing a data tool

A shipping address is a value within an order and need not be an independent entity. Choose mapping by identity and query requirements, then choose tooling for the workload.

// EF Core 10, with a relational provider supporting complex JSON mapping.
modelBuilder.Entity<Order>()
    .ComplexProperty(o => o.ShippingAddress, address => address.ToJson()); // (1)

// Earlier owned-entity approach, as an ALTERNATIVE mapping:
// modelBuilder.Entity<Order>().OwnsOne(o => o.ShippingAddress);
🧾 Code walkthrough
  1. (1) A complex type has no entity identity; 10 expands support and adds mapping. An owned type is an entity with an owner and identity, possibly implicit, and can use table splitting or JSON depending on provider.

JSON can reduce tables for data read together. Query translation, indexing, constraints and partial updates depend on provider and database version. Avoid giant documents that are constantly rewritten or need extensive relational reporting.

EF CoreDapper
Query with provider translation; raw availableHandwritten SQL with lightweight object mapping
WritesTracking, unit of work and migrationsApplication manages SQL, and concurrency
FitBusiness and model evolutionSpecific complex queries requiring direct SQL control
EF6EF Core 10
PlatformOlder common in legacy apps; some releases support modern Redesigned ORM for modern .NET
ModelCode First and designer workflowsCode models, migrations or scaffolding; no EDMX designer
MigrationDifferent and behaviorsA porting project requiring translation, loading and tests

Dapper is not universally faster; SQL, round trips and payload often dominate. Mixing tools requires a shared connection and transaction when changes must commit atomically.

References: EF Core 10 changes, safe SQL queries, transactions, migration deployment, and compiled queries and pooling. Snippets are independent focused excerpts. Order examples assume the named entities, relationships and DbSets exist in an appropriate context; they are not automatically part of the initial Product-only model.

Interview questions

Check your reasoning

Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?

9.1What does EF Core do, and what are its main trade-offs?Junior
Model answer

I use to map a domain model to a database, translate supported , results and persist tracked changes. It improves productivity through type-safe queries, relationships and migrations. The trade-offs are tracking and materialization overhead, translation limits and the possibility of inefficient queries. Provider differences still matter, so I inspect generated and execution plans. I use projections or targeted SQL when they better express a particular read workload.

9.2Why is DbContext a unit of work, and how long should it live?Junior
Model answer

I treat as a short-lived unit of work because it tracks related entity changes and submits them through SaveChanges. In a web application it is commonly scoped to a request, while a background worker can create one per operation through a factory. I dispose it promptly and never share it across concurrent operations. Long-lived contexts accumulate state and stale entities, so I avoid using them as an application cache or singleton.

9.3What is the Fluent API used for?Junior
Model answer

I use Fluent to configure mapping details that conventions do not capture, such as keys, relationship ownership, delete behavior, string lengths, decimal precision and indexes. Separate configuration classes keep persistence concerns organized without filling entities with attributes. The model configuration describes intent; a migration or another deployment process applies it to the database. I still validate user input in the application, because database mapping rules are not a complete input-validation strategy.

9.4How do eager, lazy and explicit loading differ?Junior
Model answer

I use eager loading when I know I need related entities, expressing that through Include and ThenInclude. Lazy loading fetches a when accessed, which is convenient but can hide queries. Explicit loading deliberately loads a relationship through the context entry . For read endpoints I often prefer into a because it fetches only the required values. I choose based on the required shape and measured query behavior, not convenience alone.

9.5When should you use AsNoTracking?Junior
Model answer

I use AsNoTracking when I need entity results for reading but will not modify and save them through that context. It avoids persistent change-tracker bookkeeping. A pure scalar or already has no entities to track, but a projection containing an entity can still track it. If repeated keys in a read-only graph should share one object instance, I consider AsNoTrackingWithIdentityResolution. I measure memory and query behavior rather than assuming every no-tracking query must be faster.

9.6What is the difference between code-first and database-first?Junior
Model answer

I use code-first when the application team owns the model and evolves the through reviewed migrations. Database-first starts from an existing database and scaffolds context and entity code, which fits a separately managed schema. Either approach still needs careful mapping and deployment. I establish a clear schema owner, manage customizations so regeneration does not overwrite them, and review drift. Code-first migration generation compares the model with its snapshot, not with every live database difference.

9.7How do you diagnose and fix an N+1 query problem?Mid
Model answer

I look at database command logs or for one list query followed by repeated queries with different identifiers. That usually comes from lazy loading or explicit loading inside a loop. I replace it with a that computes the required values in , or eager loading if entities are needed. I then verify query count, payload and . Running those queries concurrently on one is invalid and does not address the underlying access pattern.

9.8When do split queries help, and what do they cost?Mid
Model answer

I consider split queries when multiple collection includes create a large cross product and duplicate parent columns. Separate statements can transfer much less data, but add round trips and buffering. They can also observe different database states. A Read Committed alone does not guarantee one shared snapshot; stronger may be necessary if consistency across statements matters. I compare the real execution plans, row counts and before making split queries a default.

9.9How do ExecuteUpdate and ExecuteDelete differ from SaveChanges?Mid
Model answer

I use ExecuteUpdate and ExecuteDelete for set-based changes without materializing entities. They execute immediately and return affected row counts rather than waiting for SaveChanges. They bypass change tracking and SaveChanges interceptors, although database triggers and command interception can still apply. Automatic concurrency- checks are not included, so I add predicates and check counts when needed. Multiple calls need an explicit for shared atomicity, and tracked entities can become stale afterwards.

9.10What changed in EF Core 10 bulk update lambdas?Mid
Model answer

In 10 I can build ExecuteUpdate setters with a regular , including a block and conditional statements. Earlier expression-only construction made conditional updates harder to compose. The important distinction is that this flexibility applies to building the setter list; expressions describing database values must still be translatable. The operation remains an immediate set-based update with the same tracking, and concurrency considerations, so the new syntax does not remove those responsibilities.

9.11How does optimistic concurrency work in EF Core?Mid
Model answer

I configure a concurrency , such as Server rowversion or an application-managed version. remembers the original value and includes it with the key in update and delete predicates. If another writer changes that version, the expected row is not affected and EF raises DbUpdateConcurrencyException. I then return a conflict or reload and reconcile according to business rules. I do not blindly retry a user edit, because that could overwrite the newer decision.

9.12When would you use pessimistic locking or a conditional update?Mid
Model answer

I consider pessimistic locking when contention is high and the operation needs a short read-modify-write sequence protected from competing writers. The syntax and lock behavior depend on the database, and blocking and are real costs. For a simple stock reservation I often prefer one conditional update that decrements stock only when enough remains. I validate positive quantity and inspect affected rows. More complex may still need a wider or stronger .

9.13How can you use raw SQL without SQL injection?Mid
Model answer

I pass interpolated directly to FromSql or SqlQuery so receives the format and values separately and creates parameters. I never concatenate untrusted values into a string sent to a raw . Database identifiers cannot generally be parameters, so dynamic column names come from a strict allowlist. I also check mapping completeness and composability: an entity query needs its mapped columns, and a SQL Server stored procedure cannot be composed like an ordinary SELECT.

9.14What are global query filters and EF Core 10 named filters?Mid
Model answer

I use global filters for default entity predicates such as soft deletion and tenant scope. 10 lets me name filters and selectively disable one, for example including deleted orders while keeping tenant filtering. Earlier versions commonly combined predicates in one expression. I treat filters as defense in depth rather than a complete security boundary, because bypass methods, raw commands and writes need separate controls. I also test required , whose joins can unexpectedly remove rows.

9.15What is the difference between owned and complex types?Mid
Model answer

I use owned types when modeling an entity whose lifetime and identity depend on its owner, even if that identity is implicit. Complex types model values without entity identity, such as an address. 10 expands complex-type support, including mapping with compatible providers. I choose table columns or JSON according to query, indexing and update requirements. I would not assume that every provider supports identical mapping or that JSON automatically improves performance.

9.16How do you deploy migrations safely during rolling releases?Senior
Model answer

I review a generated script or bundle and run it as a controlled deployment step with dedicated privileges. I test locking, duration, data conversion and recovery on realistic volumes. For incompatible changes I expand the , deploy compatible code, retire old writers, backfill safely, switch reads and contract later. Migration locking can reduce simultaneous execution, but does not make a destructive change compatible with old instances. I keep clear migration ownership and a forward-repair plan.

9.17How do explicit transactions interact with connection retries?Senior
Model answer

I run the whole inside the execution strategy so a transient failure can replay a complete unit. I create fresh context state for each attempt and keep external side effects out of the retry , using an where needed. I also consider commit ambiguity: a connection failure during commit does not prove rollback. Operations need or outcome verification. I avoid broad retries for business conflicts and choose according to the actual .

9.18When are compiled queries and context pooling worthwhile?Senior
Model answer

I use them only when profiling shows the targeted overhead matters. already caches query shapes; compiled queries bypass cache lookup work but do not improve plans. Context pooling reuses initialized instances and differs from connection pooling. I check that the model is stable and that tenant identifiers or custom state are correctly assigned and reset for every lease. Neither optimization permits concurrent operations on one context, and both require representative benchmarks.

9.19Would you replace EF Core with Dapper to fix a slow endpoint?Senior
Model answer

I first identify whether the bottleneck is database execution, missing indexes, excessive data, queries or application overhead. A and a suitable index may solve it without changing tools. I use Dapper when handwritten gives clearer control for a specific query, then compare equivalent workloads. Replacing the while keeping the same poor SQL will not fix the cause. If both tools participate in one write operation, I explicitly coordinate their connection and .

9.20Why is upgrading EF6 to EF Core more than changing a package?Senior
Model answer

I treat as a redesigned rather than a drop-in EF6 package upgrade. Model configuration, query translation, lazy-loading behavior and provider capabilities differ, and designer workflows do not transfer directly. I inventory the existing mappings and queries, port a representative vertical slice and compare generated and behavior. I plan ownership and migration history carefully rather than recreating a production database. Integration tests against the real provider are essential for confidence.

Test yourself

Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.

Score: 0 / 0

Can two queries run concurrently on one DbContext?

The context itself is not thread-safe, regardless of tracking.

Which projection can still track an entity by default?

An entity inside the result can still be tracked.

What can AsSplitQuery reduce?

It separates collection retrieval, usually adding queries.

After ExecuteUpdateAsync, an already tracked entity is...

The bulk operation does not synchronize the tracker.

What does EF Core 10 add to ExecuteUpdate setter construction?

The new builds setters; translation remains required.

A configured version changed since the entity was loaded. Saving normally raises...

The original in WHERE no longer matches.

Where should an explicit transaction start when retries are enabled?

The entire unit must be replayable, with safe side effects.

IgnoreQueryFilters(["SoftDelete"]) in EF Core 10...

Named filters allow selective disabling.

Which raw SQL pattern parameterizes the input value?

FromSql receives format and values rather than assembled .

What does DbContext pooling reuse?

It differs from connection pooling; custom state remains your responsibility.