From 80978523c3584c2d9625076685b5e34ff927a4ed Mon Sep 17 00:00:00 2001 From: Mehrdad Shirvani Date: Fri, 28 Mar 2025 13:27:42 +0330 Subject: [PATCH] vault backup: 2025-03-28 13:27:42 --- .../Session03/Session 03 Document.md | 446 ++++++++++++++++-- 1 file changed, 405 insertions(+), 41 deletions(-) diff --git a/02_ProjectOrientedSessions/Session03/Session 03 Document.md b/02_ProjectOrientedSessions/Session03/Session 03 Document.md index 5dfc701..1b06e86 100644 --- a/02_ProjectOrientedSessions/Session03/Session 03 Document.md +++ b/02_ProjectOrientedSessions/Session03/Session 03 Document.md @@ -1,77 +1,441 @@ +# Introduction to Repository Pattern +The **Repository Pattern** in ASP.NET Core is a design pattern used to separate business logic from data access logic by providing an abstraction layer over database operations. This pattern improves maintainability, testability, and flexibility in applications by encapsulating database operations in dedicated repository classes. -### 📌 **Repository Interfaces (Domain/Repositories)** +--- +## **Why Use the Repository Pattern?** + +### **Pros:** + +1. **Abstraction from ORM (Entity Framework Core)** + + - Prevents direct dependency on EF Core, making it easier to swap out the data access layer in the future. + +2. **Better Code Organization** + + - Separates concerns by keeping data logic in repositories and business logic in services/controllers. + +3. **Improved Testability** + + - Makes it easier to mock repositories in unit tests. + +4. **Encapsulation of Queries** + + - Common queries can be abstracted, reducing repetition. + +5. **Centralized Data Access Logic** + + - Ensures a single location for handling CRUD operations. + + +--- + +## **Comparison: Repository Pattern vs. Direct DbSet Operations** + +|Feature|Using Repository Pattern|Using DbSet Directly in Controllers| +|---|---|---| +|**Separation of Concerns**|✅ Maintains separation|❌ Business and data access logic mixed| +|**Testability**|✅ Easy to mock and test|❌ Harder to mock DbContext| +|**Code Reusability**|✅ Common operations are encapsulated|❌ Repetitive DbSet calls| +|**Flexibility**|✅ Can switch database providers easily|❌ Tightly coupled to EF Core| + +--- + +## **Implementation of the Repository Pattern with Unit of Work** + +We will create: + +1. **IRepository** (Generic repository interface) + +2. **Repository** (Generic repository implementation) + +3. **IUnitOfWork** (Handles transaction management) + +4. **UnitOfWork** (Implementation for database commit operations) + +5. **Entity-specific repositories** (e.g., `ICustomerRepository` and `CustomerRepository`) + + +--- + +### **Step 1: Create a Generic Repository Interface** ```csharp -namespace Domain.Repositories +public interface IRepository where T : class { - public interface IRepository where T : class + Task GetByIdAsync(int id); + Task> GetAllAsync(); + Task AddAsync(T entity); + void Update(T entity); + void Delete(T entity); +} +``` + +--- + +### **Step 2: Implement the Generic Repository** + +```csharp +public class Repository : IRepository where T : class +{ + protected readonly DbContext _context; + protected readonly DbSet _dbSet; + + public Repository(DbContext context) { - Task GetByIdAsync(int id); - Task> GetAllAsync(); - Task AddAsync(T entity); - void Remove(T entity); + _context = context; + _dbSet = _context.Set(); + } + + public async Task GetByIdAsync(int id) + { + return await _dbSet.FindAsync(id); + } + + public async Task> GetAllAsync() + { + return await _dbSet.ToListAsync(); + } + + public async Task AddAsync(T entity) + { + await _dbSet.AddAsync(entity); + } + + public void Update(T entity) + { + _dbSet.Update(entity); + } + + public void Delete(T entity) + { + _dbSet.Remove(entity); } } ``` +--- + +### **Step 3: Create an Entity-Specific Repository Interface** + ```csharp -namespace Domain.Repositories +public interface ICustomerRepository : IRepository { - public interface IUnitOfWork + Task> GetCustomersWithOrdersAsync(); +} +``` + +--- + +### **Step 4: Implement the Entity-Specific Repository** + +```csharp +public class CustomerRepository : Repository, ICustomerRepository +{ + public CustomerRepository(DbContext context) : base(context) { - Task CompleteAsync(); + } + + public async Task> GetCustomersWithOrdersAsync() + { + return await _dbSet.Include(c => c.Orders).ToListAsync(); } } ``` -### 📌 **Repository Pattern (Infrastructure/Persistence/Repositories)** +--- + +### **Step 5: Create the Unit of Work Interface** ```csharp -using Domain.Repositories; -using Microsoft.EntityFrameworkCore; - -namespace Infrastructure.Persistence.Repositories +public interface IUnitOfWork : IDisposable { - public class Repository : IRepository where T : class + ICustomerRepository Customers { get; } + Task SaveChangesAsync(); +} +``` + +--- + +### **Step 6: Implement the Unit of Work** + +```csharp +public class UnitOfWork : IUnitOfWork +{ + private readonly DbContext _context; + private CustomerRepository _customerRepository; + + public UnitOfWork(DbContext context) { - protected readonly AppDbContext _context; - protected readonly DbSet _dbSet; + _context = context; + } - public Repository(AppDbContext context) - { - _context = context; - _dbSet = context.Set(); - } + public ICustomerRepository Customers => + _customerRepository ??= new CustomerRepository(_context); - public async Task GetByIdAsync(int id) => await _dbSet.FindAsync(id); - public async Task> GetAllAsync() => await _dbSet.ToListAsync(); - public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity); - public void Remove(T entity) => _dbSet.Remove(entity); + public async Task SaveChangesAsync() + { + return await _context.SaveChangesAsync(); + } + + public void Dispose() + { + _context.Dispose(); } } ``` -### 📌 **Unit of Work Implementation** +--- + +### **Step 7: Register Dependencies in `Program.cs`** ```csharp -using Domain.Repositories; -using System.Threading.Tasks; +builder.Services.AddScoped(); +builder.Services.AddScoped(); +``` -namespace Infrastructure.Persistence +--- + +### **Step 8: Use in a Service or Controller** + +```csharp +public class CustomerService { - public class UnitOfWork : IUnitOfWork + private readonly IUnitOfWork _unitOfWork; + + public CustomerService(IUnitOfWork unitOfWork) { - private readonly AppDbContext _context; + _unitOfWork = unitOfWork; + } - public UnitOfWork(AppDbContext context) - { - _context = context; - } + public async Task> GetAllCustomersAsync() + { + return await _unitOfWork.Customers.GetAllAsync(); + } - public async Task CompleteAsync() - { - return await _context.SaveChangesAsync(); - } + public async Task AddCustomerAsync(Customer customer) + { + await _unitOfWork.Customers.AddAsync(customer); + await _unitOfWork.SaveChangesAsync(); } } ``` + +--- + +## **Conclusion** + +- The **Repository Pattern** with **Unit of Work** ensures clean architecture, better maintainability, and testability. + +- It abstracts **DbSet** operations and allows for easier database switching. + +- The **Unit of Work** manages transactional consistency by coordinating multiple repositories. + + +Would you like modifications, such as adding specifications or pagination? 🚀 + +# Introduction to Unit of Work Pattern +# 1. Implementing Repository Pattern +## Branching +- [ ] Create the feature/repositories branch based on develop + +## Create `IRepository` in Domain +- [ ] Create the interface and add the following code + +📂 Suggested Folder: `Domain/Framework/Interfaces/Respositories` + +```c# +public interface IRepository where T_Entity : class +{ + Task GetByIdAsync(U_PrimaryKey id); + Task> GetAllAsync(); + Task> FindAsync(Expression> predicate); + Task AddAsync(T_Entity entity); + void Update(T_Entity entity); + void Remove(T_Entity entity); +} +``` + +## Create a class implementing `IRepository` +- [ ] create a class named `BaseRepository` or `Repository` (choose one) in Infrastructure and implement `IRepository` + +📂 Suggested Folder: `Infrastructure/Framework/Base` + +- [ ] provide method definitions for the methods + +```c# +public class BaseRepository : IRepository + where T_Entity : class + where K_DbContext : DbContext +{ + public virtual K_DbContext DbContext { get; set; } + public virtual DbSet DBSet{ get; set; } + + public BaseRepository(K_DbContext dbContext) + { + DbContext = dbContext; + DBSet = dbContext.Set(); + } + + public async Task AddAsync(T_Entity entity) + { + await DBSet.AddAsync(entity); + } + + public async Task GetByIdAsync(U_PrimaryKey id) + { + return await DBSet.FindAsync(id); + } + public async Task> GetAllAsync() + { + var entityList = DBSet.ToListAsync(); + return await entityList; + } + public void Update(T_Entity entity) + { + DBSet.Update(entity); + } + public void Remove(T_Entity entity) + { + DBSet.Remove(entity); + } + + public async Task> FindAsync(Expression> predicate) + { + return await DBSet.Where(predicate).ToListAsync(); + } +} +``` + +## Create one interface for each entity, and name it `I[Entity]Repository` +- [ ] For each entity, create an interface that inherits `IRepository` +- [ ] (Optional): Add method definitions as you deem needed for that entity (not recommended right now. We will come back to this part later) + +📂 Suggested Folder: Domain/Framework/Base/Interfaces/{Related Folder}` +for example: +```c# +public interface IAccountRepository : IRepository +{ + +} +``` + +### Reference Project: +https://github.com/MehrdadShirvani/AlibabaClone-Backend/tree/develop/AlibabaClone.Domain/Framework/Interfaces/Repositories +## Create one class for each entity, implementing the `I[Entity]Repository` + +📂 Suggested Folder: Infrastructure/Services/{Related Folder} + +- [ ] For each entity, create an class named `[Entity]Repository` that implements `I[Entity]Repository` and inherits `BaseRepository` + +for example: +```c# +public class AccountRepository : + BaseRepository, + IAccountRepository +{ + public AccountRepository(ApplicationDBContext dbContext) : base(dbContext) + { + + } +} +``` +### Reference Project: +https://github.com/MehrdadShirvani/AlibabaClone-Backend/tree/develop/AlibabaClone.Infrastructure/Services +## Registering Services +- [ ] Modify `Program.cs` in Presentation, and register for each `I[Entity]Repository` the related `[Entity]Repository` + +```c# +//some code +//Register Repositories +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +//some code +``` +## Merge +- [ ] Create a PR and merge the current branch with develop + + +# 2. Implementing Unit of Work Pattern + +## Branching +- [ ] Create the feature/UnitOfWork branch based on develop + + +```c# +public interface IUnitOfWork : IDisposable +{ + Task SaveChangesAsync(); +} +``` + +## Create `IUnitOfWork` in Domain +- [ ] Create the interface that inherits `IDisposable` and add the following code + +📂 Suggested Folder: `Domain/Framework/Interfaces` + +```c# +public interface IUnitOfWork : IDisposable +{ + Task SaveChangesAsync(); +} +``` + +## Create a class implementing `IUnitOfWork` +- [ ] create a class named `UnitOfWork` in Infrastructure and implement `IUnitOfWork` + +📂 Suggested Folder: `Infrastructure/Framework/Base` + +- [ ] provide method definitions for the methods + +```c# +public class UnitOfWork : IUnitOfWork +{ + private readonly ApplicationDBContext _context; + + public UnitOfWork(ApplicationDBContext context) + { + _context = context; + } + + public void Dispose() + { + _context.Dispose(); + } + + public async Task SaveChangesAsync() + { + return await _context.SaveChangesAsync(); + } +} +``` + +## Registering Services +- [ ] Modify `Program.cs` in Presentation, and register the `UnitOfWork` Service + +```c# +//some code +//Register Repositories +builder.Services.AddScoped(); +//some code +``` + +## Merge +- [ ] Create a PR and merge the current branch with develop