Data Access with EF6
|
This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2,
and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page documents Entity Framework 6 as used from ASP.NET MVC 5.3.x on .NET Framework 4.8.1 — not EF Core (see Data Access with EF Core under ASP.NET Core).
DbContext in an MVC app
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext() : base("DefaultConnection") { } // named connection string in web.config
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
}
<!-- web.config -->
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=Shop;Integrated Security=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
A controller typically creates and disposes its own context per request (using or an IDisposable
Controller.Dispose override), or one is injected via a DI container wired through a custom
IControllerFactory (see The MVC Pattern and
Request Life Cycle):
public class ProductsController : Controller
{
private readonly ApplicationDbContext _db = new ApplicationDbContext();
protected override void Dispose(bool disposing)
{
if (disposing) _db.Dispose();
base.Dispose(disposing);
}
}
Code First, Database First, Model First
-
Code First — C# classes define the model; EF6 generates the schema (and, with migrations, evolves it). The default and most common approach for new MVC 5 projects.
-
Database First — an existing database is reverse-engineered into an
.edmxmodel and generated entity classes. -
Model First — a visual
.edmxdesigner defines the model first, then generates the database schema. Database First and Model First both center on the.edmxdesigner, which is largely legacy even within the EF6 era; Code First is what current MVC 5 guidance recommends.
Migrations
Enable-Migrations # Package Manager Console -- scaffolds Migrations/Configuration.cs
Add-Migration AddProductDescription # scaffolds a migration comparing the model to the last snapshot
Update-Database # applies pending migrations to the configured connection string
// Migrations/Configuration.cs
internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
public Configuration() => AutomaticMigrationsEnabled = false;
protected override void Seed(ApplicationDbContext context)
{
context.Categories.AddOrUpdate(c => c.Name, new Category { Name = "Uncategorized" });
}
}
Update-Database can also run automatically on application start via a Database.SetInitializer (e.g.
MigrateDatabaseToLatestVersion<TContext, TConfiguration>), though explicit Update-Database in a deployment
pipeline is generally preferred for production. See
Code First Migrations.
LINQ queries, loading strategies, and the N+1 trap
// Eager loading -- one query, via Include
var products = db.Products.Include(p => p.Category).Where(p => p.IsActive).ToList();
// Lazy loading -- a virtual navigation property triggers a query on first access
public class Product { public virtual Category Category { get; set; } }
foreach (var p in db.Products.ToList()) { var name = p.Category.Name; } // N+1: one query per product!
// Explicit loading -- load a navigation property on demand for an already-fetched entity
db.Entry(product).Reference(p => p.Category).Load();
The N+1 problem above is lazy loading’s classic trap: one query fetches N products, then N further queries
fetch each product’s category individually. Include (eager loading) collapses this back to a single query
and should be the default choice for any collection a view will iterate and dereference navigation properties
on.
Async EF6 in async actions
public async Task<ActionResult> Index()
=> View(await _db.Products.Where(p => p.IsActive).ToListAsync());
public async Task<ActionResult> Details(int id)
{
var product = await _db.Products.FindAsync(id);
return product == null ? (ActionResult)HttpNotFound() : View(product);
}
Pairing EF6’s *Async methods with async Task<ActionResult> actions (see
Controllers and Actions) frees the IIS request thread during
I/O instead of blocking it — see Caching and Performance for
why this matters under thread-pool pressure.
Connection resiliency
public class ApplicationDbConfiguration : DbConfiguration
{
public ApplicationDbConfiguration()
=> SetExecutionStrategy("System.Data.SqlClient",
() => new SqlAzureExecutionStrategy(maxRetryCount: 5, maxDelay: TimeSpan.FromSeconds(30)));
}
SetExecutionStrategy registers automatic retry-on-transient-failure (connection drops, Azure SQL throttling)
around EF6 operations; it must not be combined with a manually managed TransactionScope spanning multiple
SaveChanges calls without care, since a retried operation must itself be safely repeatable.
Repository / unit-of-work over DbContext
DbContext (via DbSet<T>) already is a unit of work and a set of repositories — a hand-rolled
IRepository<T>/IUnitOfWork wrapper is common in MVC 5-era codebases (often to ease unit testing controllers
without a real database) but adds a layer of indirection EF6 arguably already provides:
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product Find(int id);
void Add(Product product);
void SaveChanges();
}
Reach for this pattern when a genuine seam is needed (swapping EF6 for a different store in tests, sharing data
logic across MVC and Web API controllers); avoid it as a default — wrapping DbContext 1:1 in a repository
interface mostly just duplicates `DbSet<T>’s own API.
Scaffolded CRUD controllers and SelectList
Visual Studio’s Add Controller scaffolding, given a model class and a DbContext, generates a full CRUD
controller plus matching views, including a SelectList for any foreign-key dropdown:
public ActionResult Create()
{
ViewBag.CategoryId = new SelectList(db.Categories, "Id", "Name");
return View();
}
See HTML Helpers and Forms for Html.DropDownListFor consuming
that SelectList.
EF6 vs. EF Core for a migrating application
EF6 continues to receive bug fixes but no new features, and does not run on .NET (only .NET Framework); an
application migrating to ASP.NET Core (see
Migrating to ASP.NET Core) must eventually move to EF Core,
which has a different (though conceptually similar) Fluent API, no .edmx designer support at all, and its own
migration tooling (dotnet ef migrations add). EF6 can, however, keep running on .NET Core/.NET 5+ if it is
never fully retired — Microsoft ships an EntityFramework package compatible with modern .NET specifically for
this incremental-migration case, trading EF Core’s performance/feature improvements for not having to rewrite
the data layer on day one. See Entity Framework 6 and
EF Core vs. EF6.
Next: Caching and Performance covers output caching and the thread-pool implications of the async patterns above.