Building Robust .NET Core Applications with Entity Framework Core and Repository Pattern for Scalable Data Access
Rao Pratham Singh
C# | .Net core | SQL | Azure | ETL | Azure Data Factory | Data Engineering
When developing .NET Core applications that require database operations, choosing the right approach to connect to the database is crucial. Among the various options available, Entity Framework Core (EF Core) stands out due to its simplicity, efficiency, and powerful capabilities. In this blog, we'll explore the use of Entity Framework Core with the Repository Pattern, how it facilitates clean, maintainable code, and how LINQ queries can be used to interact with databases seamlessly. We will also dive into a complete example demonstrating how EF Core can be integrated with .NET Core for efficient data access.
Why Choose Entity Framework Core?
Entity Framework Core (EF Core) is a lightweight, extensible, open-source version of Entity Framework that works across multiple platforms. It's the recommended data access technology for .NET Core applications. Here are some key reasons why EF Core is a great choice:
The Repository Pattern with EF Core
The Repository Pattern is a design pattern that promotes a clean separation between the data access layer and the business logic of an application. By using this pattern, you can manage data in a more structured way, making it easier to maintain and test.
In the Repository Pattern, the repository serves as an abstraction layer for the data access code. It allows you to interact with the database through a uniform interface, enabling the underlying data store to be swapped without affecting the rest of the application.
Here is a breakdown of the structure we'll use in this blog:
Creating the EF Core Configuration with Repository Pattern
Let's now walk through how to set up a generic repository class using Entity Framework Core to interact with a database.
Step 1: Define the Database Entity
Let's assume we have a database for a task management system. We'll create a table called TaskItem to represent tasks in the system. Here's the entity class for the TaskItem table.
namespace TaskManagement.DbEntities
{
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
}
}
Step 2: Create the Database Context
Now, let's create the TaskDbContext, which will configure the EF Core DbSet for TaskItem and handle the interaction with the database.
领英推荐
using Microsoft.EntityFrameworkCore;
namespace TaskManagement.DbContext
{
public class TaskDbContext : DbContext
{
public TaskDbContext(DbContextOptions<TaskDbContext> options) : base(options) { }
public DbSet<TaskItem> TaskItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TaskItem>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Title).IsRequired().HasMaxLength(200);
entity.Property(e => e.Description).HasMaxLength(1000);
entity.Property(e => e.CreatedOn).HasDefaultValueSql("GETDATE()");
entity.Property(e => e.ModifiedOn).HasDefaultValueSql("GETDATE()");
});
}
}
}
Step 3: Create the Generic Repository
Next, we create the generic repository class that will handle basic CRUD operations for all entities. This repository class will be extended by specific repositories for each entity.
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
namespace TaskManagement.Repositories
{
public interface ITaskRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task InsertAsync(T entity);
Task InsertAsync(IEnumerable<T> entity);
Task UpdateAsync(T entity);
Task UpdateAsync(IEnumerable<T> entity);
Task DeleteAsync(int id);
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
}
public class TaskRepository<T> : ITaskRepository<T> where T : class
{
protected readonly TaskDbContext _context;
private readonly DbSet<T> _dbSet;
public TaskRepository(TaskDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T> GetByIdAsync(int id)
{
return await _dbSet.FindAsync(id);
}
public async Task InsertAsync(T entity)
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
}
public async Task InsertAsync(IEnumerable<T> entities)
{
await _dbSet.AddRangeAsync(entities);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(T entity)
{
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(IEnumerable<T> entities)
{
_dbSet.UpdateRange(entities);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null)
{
_dbSet.Remove(entity);
await _context.SaveChangesAsync();
}
}
public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate)
{
return await _dbSet.Where(predicate).ToListAsync();
}
}
}
Step 4: Define a Specific Repository
For the TaskItem table, we can now create a dedicated repository to manage task-specific business logic, including handling CRUD operations.
using TaskManagement.DbEntities;
using Microsoft.EntityFrameworkCore;
namespace TaskManagement.Repositories
{
public interface ITaskItemRepository : ITaskRepository<TaskItem> { }
public class TaskItemRepository : TaskRepository<TaskItem>, ITaskItemRepository
{
public TaskItemRepository(TaskDbContext context) : base(context) { }
public async Task<TaskItem> GetTaskByTitle(string title)
{
return await _context.TaskItems.Where(t => t.Title == title).FirstOrDefaultAsync();
}
}
}
Step 5: Register Dependencies in Startup
In your Startup.cs or Program.cs file, register the database context and repositories for dependency injection.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TaskDbContext>(options =>
options.UseSqlServer("ConnectionString"));
services.AddScoped<ITaskItemRepository, TaskItemRepository>();
}
Step 6: Using the Repository in a Service
Here's how you would use the repository in your service layer to perform CRUD operations on the TaskItem table.
namespace TaskManagement.Services
{
public class TaskService
{
private readonly ITaskItemRepository _taskItemRepository;
public TaskService(ITaskItemRepository taskItemRepository)
{
_taskItemRepository = taskItemRepository;
}
// Create a new task
public async Task CreateTask(string title, string description)
{
var task = new TaskItem
{
Title = title,
Description = description,
CreatedOn = DateTime.UtcNow,
ModifiedOn = DateTime.UtcNow
};
await _taskItemRepository.InsertAsync(task);
}
// Update an existing task
public async Task UpdateTask(int id, string title, string description)
{
var task = await _taskItemRepository.GetByIdAsync(id);
if (task == null)
{
throw new Exception("Task not found");
}
task.Title = title;
task.Description = description;
task.ModifiedOn = DateTime.UtcNow; // Update the ModifiedOn timestamp
await _taskItemRepository.UpdateAsync(task);
}
// Delete a task
public async Task DeleteTask(int id)
{
var task = await _taskItemRepository.GetByIdAsync(id);
if (task == null)
{
throw new Exception("Task not found");
}
await _taskItemRepository.DeleteAsync(id);
}
// Get all tasks with a filter (for example, tasks modified within the last 7 days)
public async Task<IEnumerable<TaskItem>> GetTasks()
{
return await _taskItemRepository.FindAsync(t => t.ModifiedOn >= DateTime.UtcNow.AddDays(-7));
}
}
}
Benefits of Using EF Core with Repository Pattern
Conclusion
In this blog, we explored how to integrate Entity Framework Core with the Repository Pattern to simplify database operations in a .NET Core application. We also highlighted the benefits of using EF Core, including its powerful features for querying, performance optimization, and automatic change tracking. The repository pattern provides a clean, maintainable structure for handling data access in a decoupled manner, allowing you to focus on the business logic of your application.
By following this approach, you can build efficient, scalable, and testable applications that work seamlessly with databases using Entity Framework Core.