DbContext in Entity Framework Core

By FoxLearn 1/4/2025 2:41:30 AM   79
Entity Framework (EF) Core is an Object-Relational Mapper (ORM) that helps developers interact with databases using .NET objects, eliminating the need to deal directly with SQL queries for CRUD operations.

The DbContext serves as a bridge between domain classes and the database. It's a key component of Entity Framework, representing a session with the database. It allows you to query data into entities or save entities to the database, and it has several responsibilities in Entity Framework Core.

  • It manages database connections.
  • It tracks changes made to entities (objects representing database records).
  • It handles queries and saves data to the database.
  • It handles transactions, concurrency, and change tracking.

There are several ways to instantiate and manage a DbContext in EF Core.

To get started with a project, open Visual Studio 2022 and create a new ASP.NET Core 8 Web API project by following these steps:

  1. Launch Visual Studio and select Create a new project.
  2. Choose ASP.NET Core Web API from the available templates.
  3. Follow the setup prompts, selecting .NET 8.0 as the framework and unchecking options for controllers and features like OpenAPI or Docker if not needed.

What Is DbContext?

The DbContext class in EF Core follows the Unit of Work and Repository patterns, encapsulating database logic within the application. This approach simplifies database interactions, promotes code reusability, and maintains separation of concerns.

DbContext Lifetime

The lifetime of a DbContext instance starts when it’s created and ends when it’s disposed. The process of managing DbContext usually involves these steps:

  1. Instantiating a DbContext object.
  2. Tracking entities that are being modified.
  3. Calling SaveChanges() to persist those changes to the database.
  4. Disposing of the DbContext when done to free up resources.

Best Practices for Managing DbContext

  • Avoid using DbContext in a using block: Although DbContext should be disposed of when no longer needed, disposing of it prematurely can cause issues. For instance, if there are pending changes to entities or the context is disposed while still in use, it could lead to exceptions. It’s better to use dependency injection (DI) to manage the lifecycle of DbContext.

  • Use Dependency Injection (DI): The DI container handles creating and disposing of DbContext instances based on the scope of the request. This ensures the correct lifespan for each request, improving performance and reducing overhead.

Different Ways to Instantiate DbContext

1. Using Dependency Injection (DI)

You can register your DbContext with the DI container in Startup.cs or Program.cs:

services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer("YourConnectionStringHere"));

In your controller, you can then inject DbContext:

public class MyController : ControllerBase
{
    private readonly MyDbContext _context;
    public MyController(MyDbContext context)
    {
        _context = context;
    }
}

2. Manually Instantiate DbContext

You can manually create a DbContext by passing DbContextOptions to the constructor:

var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlServer("YourConnectionStringHere");

var context = new MyDbContext(optionsBuilder.Options);

3. Using OnConfiguring

Another way to initialize a DbContext is to override the OnConfiguring method in your custom DbContext class, where you can directly specify the connection string:

public class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("YourConnectionStringHere");
    }
}

4. Using DbContext Factory

For cases where multiple units of work need to be performed, you can use a DbContextFactory. This is especially useful when your application needs to manage multiple DbContext instances within a scope:

services.AddDbContextFactory<MyDbContext>(options =>
    options.UseSqlServer("YourConnectionStringHere"));

In the controller, inject the factory:

private readonly IDbContextFactory<MyDbContext> _contextFactory;

public MyController(IDbContextFactory<MyDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

Sensitive Data and Logging

To help with debugging and error tracking, you can enable sensitive data logging:

optionsBuilder.EnableSensitiveDataLogging()
    .UseSqlServer("YourConnectionStringHere");

This can include application data in the logs when exceptions occur, but be cautious as it can expose sensitive information.

Important Notes on DbContext Usage

  • Thread Safety: DbContext is not thread-safe. It’s recommended not to use the same DbContext instance across multiple threads. Always use separate instances for parallel operations.

  • Dispose Correctly: When not using DI, manually dispose of the DbContext to free up resources. However, avoid disposing of it prematurely, especially if there are uncommitted changes or queries still running.

Example in C#: Managing DbContext with Dependency Injection

Let’s now consider a scenario where you need to manage the DbContext using Dependency Injection (DI) in an ASP.NET Core Web API.

1. DbContext Class:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) { }

    public DbSet<User> Users { get; set; }
}

2. Startup Configuration (Program.cs in ASP.NET Core 6+):

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        
        builder.Services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer("YourConnectionStringHere"));

        builder.Services.AddControllers();

        var app = builder.Build();

        app.MapControllers();

        app.Run();
    }
}

3. Controller:

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly ApplicationDbContext _context;

    public UsersController(ApplicationDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> GetUsers()
    {
        var users = await _context.Users.ToListAsync();
        return Ok(users);
    }
}

In this example, the ApplicationDbContext is injected into the UsersController via the constructor, and the DbContext is managed by ASP.NET Core’s Dependency Injection container.