RepoWrapper 1.0.0
dotnet add package RepoWrapper --version 1.0.0
NuGet\Install-Package RepoWrapper -Version 1.0.0
<PackageReference Include="RepoWrapper" Version="1.0.0" />
<PackageVersion Include="RepoWrapper" Version="1.0.0" />
<PackageReference Include="RepoWrapper" />
paket add RepoWrapper --version 1.0.0
#r "nuget: RepoWrapper, 1.0.0"
#:package RepoWrapper@1.0.0
#addin nuget:?package=RepoWrapper&version=1.0.0
#tool nuget:?package=RepoWrapper&version=1.0.0
RepoWrapper
Auto-generates repository wrapper with lazy initialization using C# source generators. Simplifies Entity Framework Core repository pattern implementation.
🎯 What is RepoWrapper?
RepoWrapper is a NuGet package that eliminates boilerplate code when implementing the Repository pattern with Entity Framework Core.
Problem It Solves
Traditional repository pattern implementation requires:
- Manual registration of each repository in DI container
- Creating wrapper classes manually
- Writing lazy initialization code
- Managing multiple repository injections in services
// ❌ Traditional way (lots of boilerplate)
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IPaymentRepository, PaymentRepository>();
public class UserService
{
private readonly IUserRepository _userRepo;
private readonly IOrderRepository _orderRepo;
private readonly IPaymentRepository _paymentRepo;
public UserService(IUserRepository userRepo, IOrderRepository orderRepo, IPaymentRepository paymentRepo)
{
_userRepo = userRepo;
_orderRepo = orderRepo;
_paymentRepo = paymentRepo;
}
}
RepoWrapper Solution
// ✅ With RepoWrapper (one line!)
services.AddRepoWrapper();
public class UserService
{
private readonly IRepoWrapper _wrapper;
public UserService(IRepoWrapper wrapper)
{
_wrapper = wrapper;
}
public async Task GetUserDetails(int id)
{
var user = await _wrapper.UserRepo.GetByIdAsync(id); // Lazy-loaded
var orders = await _wrapper.OrderRepo.GetByUserIdAsync(id); // Lazy-loaded
var payments = await _wrapper.PaymentRepo.GetByOrderIdAsync(orders[0].Id); // Lazy-loaded
}
}
✨ Features
- 🔍 Auto-Discovery: Automatically discovers repositories by naming convention
- ⚡ Lazy Initialization: Repositories are created only when accessed
- 🎁 Zero Configuration: Works out of the box
- 📝 Source Generator: Compile-time code generation, no runtime reflection
- 🔒 Type-Safe: Full IntelliSense support, strongly-typed DbContext (no
dynamic) - 📦 Scoped Lifetime: Integrated with ASP.NET Core DI
- 📚 Well-Documented: Comprehensive XML documentation on generated code
📦 Installation
Install the NuGet package:
dotnet add package RepoWrapper
Or via Package Manager Console:
Install-Package RepoWrapper
🚀 Quick Start
Step 1: Define Repositories
Follow the naming convention: I{Name}Repository for interface, {Name}Repository for implementation. Each repository's constructor receives your DbContext.
// Repositories/IUserRepository.cs
public interface IUserRepository
{
Task<User?> GetByIdAsync(int id);
Task<List<User>> GetAllAsync();
Task AddAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}
// Repositories/UserRepository.cs
public class UserRepository : IUserRepository
{
private readonly AppDbContext _context;
public UserRepository(AppDbContext context)
{
_context = context;
}
public async Task<User?> GetByIdAsync(int id)
=> await _context.Users.FindAsync(id);
public async Task<List<User>> GetAllAsync()
=> await _context.Users.ToListAsync();
public async Task AddAsync(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
}
public async Task UpdateAsync(User user)
{
_context.Users.Update(user);
await _context.SaveChangesAsync();
}
public async Task DeleteAsync(int id)
{
var user = await GetByIdAsync(id);
if (user != null)
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
}
}
}
Step 2: Configure in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Register DbContext
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Register RepoWrapper - that's it!
builder.Services.AddRepoWrapper();
var app = builder.Build();
app.Run();
Note:
AddRepoWrapper(),IRepoWrapperandRepoWrapperare generated into your project at compile time. They appear in IntelliSense after the first build.
Step 3: Use in Services
public class UserService
{
private readonly IRepoWrapper _wrapper;
public UserService(IRepoWrapper wrapper)
{
_wrapper = wrapper;
}
public async Task GetUserDetails(int id)
{
// Repositories are lazy-initialized when accessed
var user = await _wrapper.UserRepo.GetByIdAsync(id);
var orders = await _wrapper.OrderRepo.GetByUserIdAsync(id);
return new { user, orders };
}
}
📋 Naming Convention
The package auto-discovers repositories using a simple naming convention:
| What | Pattern | Example |
|---|---|---|
| Repository Interface | I{Name}Repository |
IUserRepository |
| Repository Class | {Name}Repository |
UserRepository |
| Generated Property | {Name}Repo |
UserRepo |
Examples
IUserRepository → UserRepository → _wrapper.UserRepo
IOrderRepository → OrderRepository → _wrapper.OrderRepo
IPaymentRepository → PaymentRepository → _wrapper.PaymentRepo
IProductRepository → ProductRepository → _wrapper.ProductRepo
🔄 How It Works
- Source Generator Scans: When you build, the source generator scans your project for classes ending with
Repository - Auto-Discovery: Finds corresponding
I{Name}Repositoryinterfaces - DbContext Discovery: Locates your
DbContext(a class deriving fromMicrosoft.EntityFrameworkCore.DbContext);AppDbContextwins if several exist - Code Generation: Generates:
IRepoWrapperinterface with properties for each repositoryRepoWrapperimplementation class with lazy initialization and a strongly-typed constructorRepoWrapperExtensionswith theAddRepoWrapper()DI registration method
- Dependency Injection:
AddRepoWrapper()registersIRepoWrapperas Scoped - Usage: Inject
IRepoWrapperin your services
Generated Code Example
After building with repositories like UserRepository, OrderRepository, PaymentRepository, the generator creates:
// Generated: IRepoWrapper.g.cs
public interface IRepoWrapper
{
IOrderRepository OrderRepo { get; }
IPaymentRepository PaymentRepo { get; }
IUserRepository UserRepo { get; }
}
// Generated: RepoWrapper.g.cs
public sealed class RepoWrapper : IRepoWrapper
{
private readonly AppDbContext _context;
private IUserRepository? _userRepo;
private IOrderRepository? _orderRepo;
private IPaymentRepository? _paymentRepo;
public RepoWrapper(AppDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public IUserRepository UserRepo
=> _userRepo ??= new UserRepository(_context);
// Similar for OrderRepo and PaymentRepo...
}
💡 Use Cases
Scenario 1: Web API with Multiple Repositories
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IRepoWrapper _wrapper;
public UsersController(IRepoWrapper wrapper)
{
_wrapper = wrapper;
}
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _wrapper.UserRepo.GetByIdAsync(id);
if (user == null)
return NotFound();
return Ok(MapToDto(user));
}
[HttpGet("{id}/orders")]
public async Task<ActionResult<List<OrderDto>>> GetUserOrders(int id)
{
var orders = await _wrapper.OrderRepo.GetByUserIdAsync(id);
return Ok(orders.Select(MapToDto).ToList());
}
}
Scenario 2: Background Service
public class OrderProcessingService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public OrderProcessingService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = _serviceProvider.CreateScope();
var wrapper = scope.ServiceProvider.GetRequiredService<IRepoWrapper>();
while (!stoppingToken.IsCancellationRequested)
{
var pendingOrders = await wrapper.OrderRepo.GetByStatusAsync("Pending");
foreach (var order in pendingOrders)
{
await ProcessOrderAsync(order, wrapper);
}
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
private async Task ProcessOrderAsync(Order order, IRepoWrapper wrapper)
{
// Process payment
var payment = new Payment { OrderId = order.Id, Amount = order.Amount };
await wrapper.PaymentRepo.AddAsync(payment);
// Update order status
order.Status = "Completed";
await wrapper.OrderRepo.UpdateAsync(order);
}
}
🏗️ Layered Architecture Support
Source generators only see the source files of the project they run in. To support classic layered solutions, each project installs the package and the generator emits only the pieces that project can contribute, skipping anything already available from a referenced project:
| Project | Contains | Generated |
|---|---|---|
| Application | I{Name}Repository interfaces |
IRepoWrapper (properties typed by the interfaces) |
| Infrastructure | {Name}Repository classes + AppDbContext |
RepoWrapper + RepoWrapperExtensions (AddRepoWrapper()) |
| Api | Controllers + Program.cs |
nothing — resolves the generated types via project references |
The interfaces can live in either Application or Domain — the package just goes in whichever project holds them:
Option A — interfaces in Application Option B — interfaces in Domain (Domain refs nothing)
Api/ // refs App, Infra Api/ // refs App, Infra
Controllers/ Controllers/
Program.cs // AddRepoWrapper() Program.cs // AddRepoWrapper()
Application/ // refs Domain → IRepoWrapper Application/ // refs Domain (no package)
Handlers/ Handlers/ // injects IRepoWrapper
IUserRepository ← package here Domain/ // refs nothing → IRepoWrapper
Infrastructure/ // refs Domain, App → RepoWrapper Infrastructure/ // refs Domain → RepoWrapper
AppDbContext.cs AppDbContext.cs
UserRepository UserRepository
Domain/ // entities only Domain/ // IUserRepository ← package here
Note: The package must be installed in every project that needs to generate a piece (the project holding the interfaces, and the project holding the repository classes + DbContext). Consumers such as Api do not need the package, only a project reference. Repository interfaces referenced across projects must be
public.
A single project containing repositories, interfaces and the DbContext still generates everything in one place, exactly as before.
⚙️ Requirements
- .NET 8.0 or higher
- Entity Framework Core 8.0 or higher
- C# 11 or higher
🐛 Troubleshooting
Issue: IRepoWrapper not found
Cause: The project does not contain (or reference a project containing) any repository classes, repository interfaces or a DbContext, so nothing was generated.
Solution:
- Ensure repositories end with
Repository(e.g.,UserRepository) - Ensure interfaces follow
I{Name}Repositorypattern (e.g.,IUserRepository) - In a layered solution, install the package in the project(s) that contain the interfaces and the repository classes — see Layered Architecture Support
- Rebuild the project to trigger source generator
- Check
obj/Debug/net8.0/generated/for generated files
Issue: Warning PRW004 — "No repositories found"
Cause: The package is installed but the project has no {Name}Repository classes, no
I{Name}Repository interfaces, no DbContext, and the generated types are not available from any
referenced project. Nothing was generated.
Solution:
- Add repositories/interfaces to this project, or
- Add a project reference to a project that generates them (Application/Infrastructure)
Issue: Warning PRW005 — "Repository interface is not public"
Cause: A repository class implements an interface that lives in another assembly and is not
public. The generated RepoWrapper cannot reference it, so the repository was skipped.
Solution:
- Make the interface
public(e.g.,public interface IUserRepository)
Issue: AppDbContext not found (warning PRW002)
Cause: DbContext not registered in DI container or not deriving from DbContext.
Solution:
// Make sure to register DbContext BEFORE calling AddRepoWrapper()
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlServer(connectionString));
builder.Services.AddRepoWrapper();
Issue: Repository not appearing in IRepoWrapper (warning PRW001)
Cause: Naming convention not followed or class is abstract.
Solution:
- Ensure class ends with
Repository - Ensure class is not abstract
- Ensure corresponding interface exists:
I{ClassName}(e.g.,IUserRepository) - Rebuild project
Issue: Multiple DbContexts found (error PRW003)
Cause: Several classes derive from DbContext and none is named AppDbContext.
Solution:
- Rename your primary context to
AppDbContext, or - Reduce to a single DbContext in the project
📖 API Reference
The following members are generated into your project by the source generator:
AddRepoWrapper()
public static IServiceCollection AddRepoWrapper(
this IServiceCollection services)
Registers the auto-generated RepoWrapper as a Scoped service.
Prerequisites:
- A
DbContext(e.g.,AppDbContext) must be registered viaAddDbContext<TDbContext>() - Repositories must follow naming convention
Example:
builder.Services.AddDbContext<AppDbContext>(opt => opt.UseSqlServer(connectionString));
builder.Services.AddRepoWrapper();
AddRepoWrapper<TDbContext>()
public static IServiceCollection AddRepoWrapper<TDbContext>(
this IServiceCollection services)
where TDbContext : class
Provided for API symmetry. The wrapper's constructor type is fixed at generation time from the DbContext discovered in the compilation, so this overload registers the same generated wrapper.
Example:
builder.Services.AddDbContext<CustomDbContext>(opt => opt.UseSqlServer(connectionString));
builder.Services.AddRepoWrapper<CustomDbContext>();
IRepoWrapper
Generated interface exposing one lazy-initialized property per discovered repository, named {Name}Repo.
RepoWrapper
Generated sealed class implementing IRepoWrapper. Its constructor takes the discovered DbContext type and is resolved by the DI container.
⚠️ Known Limitation: Namespace Collision
The generated RepoWrapper class lives in the global namespace (so no using is needed).
C# does not allow a type and a namespace of the same name in the same scope, so your
project must not declare namespaces starting with RepoWrapper. (e.g.
namespace RepoWrapper.Data;). Use a different root, e.g. App.Data or MyApp.Data.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
👤 Author
Ramanan
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 106 | 8/17/2026 |