EasyEF is a lightweight, generic Entity Framework Core library that implements the Repository and Unit of Work patterns, making data access simpler and more maintainable in .NET applications.
- Generic Repository Pattern: CRUD operations with a clean, consistent API
- Unit of Work Pattern: Manage transactions across multiple repositories
- Eager Loading Support: Include related entities with ease
- Pagination: Built-in support for paginated queries
- No-Tracking Queries: Optimize read-only operations for better performance
- Flexible Querying: LINQ support with filtering, sorting, and custom predicates
- Dependency Injection Ready: Easy integration with ASP.NET Core DI container
- Type-Safe: Strongly-typed interfaces with generic constraints
Clone the repository and add the EasyEF.Core project to your solution:
git clone https://github.com/CYB3RPHO3NIX/Project-EasyEF.gitThen add a project reference in your .csproj file:
<ItemGroup>
<ProjectReference Include="..\EasyEF.Core\EasyEF.Core.csproj" />
</ItemGroup>dotnet add package EasyEF.CoreImplement the IEntity<TKey> interface on your entity classes:
using EasyEF.Core.Abstractions;
public class Employee : IEntity<int>
{
public int Id { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public int Age { get; set; }
public Department EmployeeDepartment { get; set; }
}
public class Department : IEntity<int>
{
public int Id { get; set; }
public string DepartmentName { get; set; }
public string Description { get; set; }
}Inherit from BaseDbContext:
using EasyEF.Core.Context;
using Microsoft.EntityFrameworkCore;
public class EmployeeDbContext : BaseDbContext
{
public EmployeeDbContext(DbContextOptions<EmployeeDbContext> options)
: base(options)
{
}
public DbSet<Employee> Employees => Set<Employee>();
public DbSet<Department> Departments => Set<Department>();
}In your Program.cs:
using EasyEF.Core.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register DbContext and Unit of Work
builder.Services.AddGenericDataAccess<EmployeeDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();using EasyEF.Core.Abstractions;
public class EmployeeService
{
private readonly IUnitOfWork _unitOfWork;
public EmployeeService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<List<Employee>> GetAllEmployeesAsync()
{
var repository = _unitOfWork.Repository<Employee, int>();
return await repository.GetAsync();
}
public async Task CreateEmployeeAsync(Employee employee)
{
var repository = _unitOfWork.Repository<Employee, int>();
await repository.AddAsync(employee);
await _unitOfWork.SaveChangesAsync();
}
}var repository = _unitOfWork.Repository<Employee, int>();
var newEmployee = new Employee
{
Name = "John Doe",
Username = "johndoe",
Age = 30
};
await repository.AddAsync(newEmployee);
await _unitOfWork.SaveChangesAsync();var repository = _unitOfWork.Repository<Employee, int>();
// Get by ID
var employee = await repository.GetByIdAsync(1);
// Get all
var allEmployees = await repository.GetAsync();
// Get with filter
var youngEmployees = await repository.GetAsync(e => e.Age < 30);
// Get single with condition
var specificEmployee = await repository.GetSingleAsync(e => e.Username == "johndoe");var repository = _unitOfWork.Repository<Employee, int>();
var employee = await repository.GetByIdAsync(1);
if (employee != null)
{
employee.Age = 31;
repository.Update(employee);
await _unitOfWork.SaveChangesAsync();
}var repository = _unitOfWork.Repository<Employee, int>();
var employee = await repository.GetByIdAsync(1);
if (employee != null)
{
repository.Remove(employee);
await _unitOfWork.SaveChangesAsync();
}var repository = _unitOfWork.Repository<Employee, int>();
// Include Department when getting employee
var employee = await repository.GetByIdAsync(1, e => e.EmployeeDepartment);
// Include in filtered query
var employees = await repository.GetAsync(
predicate: e => e.Age > 25,
includes: e => e.EmployeeDepartment
);var repository = _unitOfWork.Repository<Employee, int>();
var sortedEmployees = await repository.GetAsync(
predicate: e => e.Age > 25,
orderBy: q => q.OrderBy(e => e.Name).ThenByDescending(e => e.Age)
);var repository = _unitOfWork.Repository<Employee, int>();
// Note: pageIndex is zero-based (0 = first page, 1 = second page, 2 = third page, etc.)
int pageIndex = 0; // First page
int pageSize = 10; // Number of items per page
var pagedEmployees = await repository.GetPagedAsync(
predicate: e => e.Age > 25,
pageIndex: pageIndex,
pageSize: pageSize,
orderBy: q => q.OrderBy(e => e.Name)
);var repository = _unitOfWork.Repository<Employee, int>();
// Faster queries for read-only scenarios (default behavior)
var employees = await repository.GetAsync(asNoTracking: true);
// Or use Query method directly
var query = repository.Query(asNoTracking: true)
.Where(e => e.Age > 25)
.OrderBy(e => e.Name);
var result = await query.ToListAsync();var repository = _unitOfWork.Repository<Employee, int>();
// Add multiple entities
var newEmployees = new List<Employee>
{
new Employee { Name = "Alice", Username = "alice", Age = 28 },
new Employee { Name = "Bob", Username = "bob", Age = 32 }
};
await repository.AddRangeAsync(newEmployees);
await _unitOfWork.SaveChangesAsync();
// Update multiple entities
var employeesToUpdate = await repository.GetAsync(e => e.Age < 30);
foreach (var emp in employeesToUpdate)
{
emp.Age += 1;
}
repository.UpdateRange(employeesToUpdate);
await _unitOfWork.SaveChangesAsync();
// Delete multiple entities
var employeesToDelete = await repository.GetAsync(e => e.Age > 60);
repository.RemoveRange(employeesToDelete);
await _unitOfWork.SaveChangesAsync();public async Task TransferEmployeeAsync(int employeeId, int newDepartmentId)
{
var employeeRepo = _unitOfWork.Repository<Employee, int>();
var departmentRepo = _unitOfWork.Repository<Department, int>();
var employee = await employeeRepo.GetByIdAsync(employeeId);
var department = await departmentRepo.GetByIdAsync(newDepartmentId);
if (employee != null && department != null)
{
employee.EmployeeDepartment = department;
employeeRepo.Update(employee);
// Both changes are saved in one transaction
await _unitOfWork.SaveChangesAsync();
}
}The repository pattern provides an abstraction layer between the data access logic and the business logic. Each repository handles CRUD operations for a specific entity type.
The Unit of Work pattern maintains a list of objects affected by a business transaction and coordinates the writing out of changes. It ensures that all repository operations are committed in a single transaction.
- IEntity: Base interface for all entities
- IRepository<TEntity, TKey>: Generic repository interface
- IUnitOfWork: Unit of Work interface for transaction management
- BaseDbContext: Base DbContext with automatic configuration scanning
- GenericRepository<TEntity, TKey>: Generic repository implementation
- UnitOfWork: Unit of Work implementation with repository caching
For console applications or scenarios where you're not using dependency injection:
using EasyEF.Core.UnitOfWork;
using Microsoft.EntityFrameworkCore;
string connectionString = "Server=localhost;Database=MyDb;Integrated Security=True;";
var options = new DbContextOptionsBuilder<EmployeeDbContext>()
.UseSqlServer(connectionString)
.Options;
using var dbContext = new EmployeeDbContext(options);
var unitOfWork = new UnitOfWork<EmployeeDbContext>(dbContext);
// Run migrations if needed
if ((await dbContext.Database.GetPendingMigrationsAsync()).Any())
{
await dbContext.Database.MigrateAsync();
}
// Use the unit of work
var repository = unitOfWork.Repository<Employee, int>();
var employees = await repository.GetAsync(e => e.Age > 25);
foreach (var emp in employees)
{
Console.WriteLine($"{emp.Name} - Age: {emp.Age}");
}- .NET 8.0 or later
- Entity Framework Core 8.0 or later
Contributions are welcome! Please feel free to submit a Pull Request. Here are some ways you can contribute:
- Report bugs and issues
- Suggest new features or enhancements
- Improve documentation
- Submit pull requests with bug fixes or new features
- Fork the repository
- Clone your fork
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License.
CYB3RPHO3NIX
- GitHub: @CYB3RPHO3NIX
- Inspired by best practices in Entity Framework Core and the Repository/Unit of Work patterns
- Built to simplify data access in .NET applications
If you have any questions or need help, please:
- Open an issue on GitHub
- Check the documentation above
- Review the example project in
EasyEF.Runner
Happy Coding! π