Perfect, Sathish π₯π
I see youβve already implemented a professional-level Repository + Unit of Work pattern β thatβs excellent! Youβre now coding like a real enterprise .NET backend engineer.
Letβs repeat and reframe your π§© Day 3 β Repository & Seed Data Setup, but now using your upgraded structure (GenericRepository + UnitOfWork) so everything is clean and clear for you step-by-step π
π§© DAY 3 β Repository & Seed Data Setup (with Unit of Work)
π― Goal
β
Enable clean database CRUD access using
β‘οΈ IGenericRepository<T> and IUnitOfWork
β
Add seed data for Admin Role and Admin User
β
Verify the data in Swagger and PostgreSQL
βοΈ STEP 1οΈβ£ β Folder Setup
Inside Accounting.Infrastructure, ensure this structure:
Repositories/
βββ Interfaces/
β βββ IGenericRepository.cs
β βββ IUnitOfWork.cs
βββ Implementations/
β βββ GenericRepository.cs
β βββ UnitOfWork.cs
Seed/
βββ DataSeeder.cs
β We already have these 4 working perfectly.
π STEP 2οΈβ£ β Verify Your IGenericRepository (Generic Contract)
using System.Linq.Expressions;
namespace Accounting.Infrastructure.Repositories.Interfaces
{
public interface IGenericRepository<T> where T : class
{
Task<IEnumerable<T>> GetAllAsync();
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
Task AddAsync(T entity);
void Update(T entity);
void Remove(T entity);
}
}
β This defines all your CRUD functions for any entity.
π STEP 3οΈβ£ β Implement GenericRepository
using Accounting.Infrastructure.Data;
using Accounting.Infrastructure.Repositories.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
namespace Accounting.Infrastructure.Repositories.Implementations
{
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly AppDbContext _context;
private readonly DbSet<T> _dbSet;
public GenericRepository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate) => await _dbSet.Where(predicate).ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public void Remove(T entity) => _dbSet.Remove(entity);
}
}
β This class handles all database operations.
π STEP 4οΈβ£ β Add IUnitOfWork + Implementation
πΉ IUnitOfWork.cs
using System.Threading.Tasks;
namespace Accounting.Infrastructure.Repositories.Interfaces
{
public interface IUnitOfWork
{
Task<int> SaveChangesAsync();
}
}
πΉ UnitOfWork.cs
using Accounting.Infrastructure.Data;
using Accounting.Infrastructure.Repositories.Interfaces;
namespace Accounting.Infrastructure.Repositories.Implementations
{
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
public UnitOfWork(AppDbContext context)
{
_context = context;
}
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
}
}
β
UnitOfWork ensures all repository operations commit in one transaction.
π STEP 5οΈβ£ β Register Services in Program.cs
In Accounting.API β open Program.cs, and add:
using Accounting.Infrastructure.Repositories.Interfaces;
using Accounting.Infrastructure.Repositories.Implementations;
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
β This enables Dependency Injection (DI) for repositories and Unit of Work.
π STEP 6οΈβ£ β Create Seed Data for Roles & Admin User
π Accounting.Infrastructure/Seed/DataSeeder.cs
using Accounting.Core.Entities;
using Accounting.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Accounting.Infrastructure.Seed
{
public static class DataSeeder
{
public static async Task SeedAsync(AppDbContext context)
{
if (!await context.Roles.AnyAsync())
{
context.Roles.AddRange(
new Role { Name = "Admin" },
new Role { Name = "Accountant" },
new Role { Name = "Staff" }
);
}
if (!await context.Users.AnyAsync())
{
context.Users.Add(new User
{
FullName = "Super Admin",
Email = "admin@accounting.com",
PasswordHash = "admin123", // later replace with BCrypt hash
RoleId = 1,
CreatedAtUtc = DateTime.UtcNow
});
}
await context.SaveChangesAsync();
}
}
}
β This ensures your database always has:
- Roles β Admin, Accountant, Staff
- Default user β admin@accounting.com / admin123
βοΈ STEP 7οΈβ£ β Run Seeder Automatically
Add this inside Program.cs, after Swagger setup:
using Accounting.Infrastructure.Seed;
using Accounting.Infrastructure.Data;
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await DataSeeder.SeedAsync(db);
}
π§ͺ STEP 8οΈβ£ β Test the Setup
Run your API:
dotnet run
Open Swagger β https://localhost:5001/swagger
Then check your database:
psql -d accountingdb
\dt
select * from "Roles";
select * from "Users";
β
You should see:
id | name
----+----------
1 | Admin
2 | Accountant
3 | Staff
and
id | fullname | email
----+---------------+----------------------
1 | Super Admin | admin@accounting.com
β Day 3 Outcome Summary
| Feature | Status |
|---|---|
| Generic Repository ready | β |
| Unit of Work added | β |
| DI registered | β |
| Seed data inserted | β |
| Tested in Swagger + DB | β |
π End Result:
π― You now have a reusable data layer foundation that any entity (Customer, Vendor, Invoice, etc.) can use for CRUD operations.
Would you like me to start π
Day 4 β Customer & Vendor API (CRUD using Repository + UnitOfWork) next?
This will show you how to use your repository to build REST APIs step-by-step.
Top comments (0)