Using Entity Framework with IDbContext in .NET 9.0
By FoxLearn 2/21/2025 8:08:05 AM 27
A cleaner solution is to abstract DbContext using an interface, like IDbContext, to improve testability and separation of concerns. This post will explore how to implement IDbContext in .NET 9.0, promoting flexibility and testability when working with EF Core.
Why Use IDbContext?
- Separation of Concerns: Keeps repository logic distinct from EF Core operations.
- Better Testability: Easier to mock the database context, improving unit test reliability.
- Encapsulation: Restricts direct access to
DbContext
, ensuring good practices. - Loose Coupling: Reduces the dependency between your services and data layer.
Implementing IDbContext in .NET 9.0
Follow these steps to implement IDbContext
with EF Core for the Customer entity:
1. Define the IDbContext Interface
First, define an interface that abstracts the database operations, allowing interaction with EF Core without directly depending on it.
public interface IDbContext { DbSet<T> Set<T>() where T : class; Task<int> SaveChangesAsync(CancellationToken cancellationToken = default); Task ExecuteStoredProcedureAsync(string storedProcName, object parameters = null); }
2. Implement the Interface in Your DbContext
Now, extend your DbContext
to implement the IDbContext
interface. In this case, we will implement it in an ApplicationContext
for handling Customer data.
public partial class ApplicationContext : DbContext, IDbContext { public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options) { } public virtual DbSet<Customer> Customers => Set<Customer>(); public Task ExecuteStoredProcedureAsync(string storedProcName, object parameters = null) { throw new NotImplementedException(); } async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { return await base.SaveChangesAsync(cancellationToken); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Customer>(entity => { entity .HasKey(c => c.CustomerId) .ToTable("Customers", "Sales"); entity.Property(c => c.Name) .HasMaxLength(100) .IsUnicode(false); }); OnModelCreatingPartial(modelBuilder); } partial void OnModelCreatingPartial(ModelBuilder modelBuilder); }
3. Register IDbContext in the Service Container
Next, register the IDbContext
in the dependency injection container, so that it can be injected wherever needed.
public static class DataInjections { public static void AddDataServices(this IServiceCollection services, IConfiguration configuration) { services.AddDbContext<IDbContext, ApplicationContext>(options => { options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")); }); services.AddScoped<ICustomerRepository, CustomerRepository>(); } }
4. Consume IDbContext in Repositories
Inject the IDbContext
into your repository to ensure loose coupling and make testing easier.
public class CustomerRepository : ICustomerRepository { private readonly IDbContext _dbContext; public CustomerRepository(IDbContext dbContext) { _dbContext = dbContext; } public async Task<List<Customer>> GetCustomersAsync() { var result = await _dbContext.Customers.ToListAsync(); return result; } public async Task<Customer> GetCustomerByIdAsync(int id) { return await _dbContext.Customers .FirstOrDefaultAsync(c => c.CustomerId == id); } }
In this example, Scalar is utilized to document and manage API endpoints, offering an alternative to Swagger.
Once you've set up the code, you can run the application to interact with the Customer data using IDbContext
, ensuring clean separation of concerns, flexibility, and ease of testing.
Implementing IDbContext
in .NET 9.0 with EF Core allows for a clean and maintainable architecture.
- Using the OrderBy and OrderByDescending in LINQ
- Querying with LINQ
- Optimizing Performance with Compiled Queries in LINQ
- MinBy() and MaxBy() Extension Methods in .NET
- SortBy, FilterBy, and CombineBy in NET 9
- Exploring Hybrid Caching in .NET 9.0
- Primitive types in C#
- Connection string password with special characters in C#