Building Robust .NET Core Applications with Entity Framework Core and Repository Pattern for Scalable Data Access

Building Robust .NET Core Applications with Entity Framework Core and Repository Pattern for Scalable Data Access

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:

  1. Cross-Platform Support: EF Core supports a wide range of databases, including SQL Server, SQLite, PostgreSQL, and more, allowing your application to run on Windows, Linux, and macOS.
  2. Performance: EF Core is optimized for high performance, with features like change tracking, lazy loading, and batch updates, ensuring fast and efficient database operations.
  3. Ease of Use: EF Core integrates seamlessly with LINQ, making queries intuitive and simple. It handles common database operations like CRUD (Create, Read, Update, Delete) using a clean API and avoids the need for raw SQL queries in most cases.
  4. Database Migrations: EF Core supports automatic migrations, which allow you to evolve your database schema along with the application code easily.
  5. Support for Change Tracking: EF Core automatically tracks changes made to entities, which simplifies data modifications and allows easy auditing.


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:

  • DbContext: Acts as a bridge between your application and the database. It provides methods for querying and saving data.
  • Repository: Implements CRUD operations and abstracts the interaction with the DbContext.
  • Service Layer: Utilizes repositories to manage the application's business logic.


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

  1. Separation of Concerns: By using the repository pattern, your data access logic is separate from the business logic, making the code more maintainable and testable.
  2. Ease of Testing: The repository pattern makes it easy to mock the data access layer during unit testing. You can test your business logic without hitting the actual database.
  3. Maintainability: With EF Core and the repository pattern, you only need to write data access logic once, and you can reuse the same repository across different layers of the application.
  4. Improved Performance: EF Core’s change tracking and query optimization capabilities ensure that your application can scale efficiently, while repositories abstract away the complexity.


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.

要查看或添加评论,请登录

Rao Pratham Singh的更多文章

社区洞察

其他会员也浏览了