DEV Community

Sugumar
Sugumar

Posted on

🧩 DAY 3 β€” Repository & Seed Data Setup (with Unit of Work)

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
Enter fullscreen mode Exit fullscreen mode

βœ… 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… 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();
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Ή 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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… 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>();
Enter fullscreen mode Exit fullscreen mode

βœ… 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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… This ensures your database always has:


βš™οΈ 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);
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ STEP 8️⃣ β€” Test the Setup

Run your API:

dotnet run
Enter fullscreen mode Exit fullscreen mode

Open Swagger β†’ https://localhost:5001/swagger

Then check your database:

psql -d accountingdb
\dt
select * from "Roles";
select * from "Users";
Enter fullscreen mode Exit fullscreen mode

βœ… You should see:

 id | name
----+----------
 1  | Admin
 2  | Accountant
 3  | Staff
Enter fullscreen mode Exit fullscreen mode

and

 id | fullname     | email
----+---------------+----------------------
 1  | Super Admin   | admin@accounting.com
Enter fullscreen mode Exit fullscreen mode

βœ… 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)