How to use Generic Repository Pattern in C#

By FoxLearn 11/27/2024 1:30:57 PM   455
The Generic Repository Pattern is a design pattern that abstracts the data access logic in an application.

When used with Entity Framework Core (EF Core), it provides a reusable and centralized data access layer, reducing boilerplate code.

What is Generic Repository Pattern?

The Generic Repository Pattern in C# centralizes and abstracts data access logic, enabling reusable, consistent, and efficient management of common operations across different entities. This reduces redundancy by consolidating CRUD operations into a single, generic repository instead of creating entity-specific repositories.

If you have a project and you realize that each repository performs similar or similar actions, if you create consecutive multiple similar repositories it will be a big waste. Instead, you only need to create one and only one repository for manipulation with all entity classes sufficient. And that is the Generic Repository Pattern.

Why Use the Generic Repository Pattern?

In projects with multiple entities, creating and maintaining separate repositories for each one can be tedious and error-prone.

  • Minimize duplication of your code
  • Ensure the coder uses the same pattern
  • Easy for maintenance and testing
  • Minimize possible errors

The Generic Repository Pattern simplifies data access by providing reusable CRUD operations. This article demonstrates how to implement this pattern using a Department entity and integrates it with a controller in an ASP.NET Core MVC application.

Here’s a step-by-step guide to implementing the Generic Repository Pattern in C# with Entity Framework.

We start by creating a BaseEntity class that contains common attributes for all entities.

public class BaseEntity
{
     public int Id { get; set; }
     public DateTime CreatedDate { get; set; }
     public DateTime ModifiedDate { get; set; }
}

The BaseEntity class contains common properties shared by all entities, such as Id, CreatedDate, and ModifiedDate.

The Department class inherits from BaseEntity and includes a specific attribute: DepartmentName.

public class Department : BaseEntity
{
     public string DepartmentName { get; set; }
}

Now we will proceed to create an interface called IGenericRepository. This interface will have the methods Get, GetAll, Add, Update and Delete.

// generic repository pattern c# with entity framework
public interface IGenericRepository<T> where T : BaseEntity
{
     Task<T> Get(int? id);
     IEnumerable<T> GetAll();
     Task Add(T entity);
     Task Update(T entity);
     Task Delete(T entity);
}

The IGenericRepository interface declares the CRUD operations applicable to any entity.

Next, Create a GenericRepository class, then implement the IGenericRepository interface.

The GenericRepository class provides the implementation of the IGenericRepository interface using Entity Framework's DbContext.

// generic repository pattern c# with entity framework
public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
    private readonly DbContext _context;
    private DbSet<T> _dbset;
    string errorMessage = string.Empty;
    public GenericRepository(ApplicationDbContext context)
    {
        _context = context;
        _dbset = context.Set<T>();
    }

    public async Task Add(T entity)
    {
        _dbset.Add(entity);
        await _context.SaveChangesAsync();
    }

    public async Task Delete(T entity)
    {
        _dbset.Remove(entity);
        await _context.SaveChangesAsync();
    }


    public async Task Update(T entity)
    {
        _dbset.Update(entity);
        await _context.SaveChangesAsync();

    }

    public async Task<T> Get(int? id)
    {
        return await _dbset.SingleOrDefaultAsync(s => s.Id == id);
    }

    public IEnumerable<T> GetAll()
    {
        return _dbset.AsEnumerable();
    }
}

You need to create a controller named DepartmentController as shown below.

public class DepartmentController : Controller
{
    private readonly IGenericRepository<Department> _context;

    public DepartmentController(IGenericRepository<Department> context)
    {
        _context = context;
    }

    // GET: Department
    public IActionResult Index()
    {
        return View(_context.GetAll());
    }

    // GET: Department/Details/5
    public async Task<IActionResult> Details(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var department = await _context.Get(id);
        if (department == null)
        {
            return NotFound();
        }

        return View(department);
    }

    // GET: Department/Create
    public IActionResult Create()
    {
        return View();
    }

    // POST: Department/Create
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,DepartmentName")] Department department)
    {
        if (ModelState.IsValid)
        {
            await _context.Add(department);
            return RedirectToAction("Index");
        }
        return View(department);
    }

    // GET: Department/Edit/5
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var department = await _context.Get(id);
        if (department == null)
        {
            return NotFound();
        }
        return View(department);
    }

    // POST: Department/Edit/5
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("Id,CreatedDate,ModifiedDate,DepartmentName")] Department department)
    {
        if (id != department.Id)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            try
            {
                await _context.Update(department);
            }
            catch (DbUpdateConcurrencyException)
            {
            }
            return RedirectToAction("Index");
        }
        return View(department);
    }

    // GET: Department/Delete/5
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var department = await _context.Get(id);
        await _context.Delete(department);
        if (department == null)
        {
            return NotFound();
        }

        return View(department);
    }

    // POST: Department/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        var department = await _context.Get(id);
        await _context.Delete(department);
        return RedirectToAction("Index");
    }
}

The DepartmentController uses IGenericRepository<Department> to handle CRUD operations for the Department entity.

Register the GenericRepository in the Startup.cs or Program.cs:

services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));

The Generic Repository Pattern in C# with Entity Framework is an efficient way to manage data access in applications with multiple entities. By centralizing and abstracting CRUD operations, this pattern enhances code reusability, maintainability, and scalability.