A generic, reusable Entity Framework Core data access layer providing base entity infrastructure and repository abstractions for .NET projects.
- .NET 10.0
- Microsoft.EntityFrameworkCore 10.x
- Microsoft.EntityFrameworkCore.SqlServer 10.x
- Ulid 1.4.x
Infrastructure
BaseEntity- Base class all entities must inherit from. ProvidesId(Ulid),CreatedAt,UpdatedAt,CreatedBy,UpdatedBy, andIsDeletedaudit fields.BaseEntityConfiguration<T>- EF CoreIEntityTypeConfiguration<T>base that wires up primary key, index, soft-delete query filter, and required audit field constraints.BaseEntityExtensionMethods- Extension methodsInitializeAuditFieldsandUpdateAuditFieldsfor setting audit fields on insert and update.ValueConverters- ProvidesGetUlidConverter()(Ulid to string) andGetEnumConverter<TEnum>()(enum to string) for use in entity configurations.
Interfaces
IReadRepository<TContext, T>- Read-only repository interface withGetAllAsync,GetAllAttachedAsync,GetAsync, andGetAttachedAsync.ICudRepository<TContext, T>- Create/Update/Delete repository interface withInsertAsync,InsertRangeAsync,UpdateAsync,UpdateRangeAsync,DeleteAsync, andDeleteRangeAsync.
Repositories
ReadRepository<TContext, T>- Concrete read repository. All queries useAsNoTracking()by default. Supports optionalwhereexpressions,orderBy, and strongly typedincludes.CudRepository<TContext, T>- Concrete CUD repository. Soft-deletes by settingIsDeleted = truerather than removing rows.
1. Install the NuGet package
Using the .NET CLI:
dotnet add package CoreDesign.DataUsing the NuGet Package Manager Console:
Install-Package CoreDesign.DataOr add directly to your .csproj:
<ItemGroup>
<PackageReference Include="CoreDesign.Data" Version="*" />
</ItemGroup>2. Define your entities
All entities must inherit from BaseEntity:
public class Widget : BaseEntity
{
public string Name { get; set; } = string.Empty;
}3. Configure your entities
Inherit from BaseEntityConfiguration<T> and call base.Configure(builder) to apply the standard audit field and query filter setup:
public class WidgetConfiguration : BaseEntityConfiguration<Widget>
{
public override void Configure(EntityTypeBuilder<Widget> builder)
{
base.Configure(builder);
builder.Property(e => e.Name).IsRequired().HasMaxLength(100);
}
}4. Register your DbContext
Your DbContext must inherit from EF Core's DbContext. Apply entity configurations in OnModelCreating:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
}5. Register repositories
Register the repositories in your DI container, once per entity type that needs them:
services.AddTransient<IReadRepository<AppDbContext, Widget>, ReadRepository<AppDbContext, Widget>>();
services.AddTransient<ICudRepository<AppDbContext, Widget>, CudRepository<AppDbContext, Widget>>();Querying
// All widgets, no tracking
var all = await readRepository.GetAllAsync();
// Filtered and ordered, with related entities included
var active = await readRepository.GetAllAsync(
whereExpression: w => w.Name.StartsWith("A"),
orderBy: q => q.OrderBy(w => w.Name),
includes: q => q.Include(w => w.Parts).ThenInclude(p => p.Supplier));
// Single entity
var widget = await readRepository.GetAsync(w => w.Id == id);Writing
// Insert
var widget = new Widget { Name = "Sprocket" };
await cudRepository.InsertAsync(widget, userId, cancellationToken);
// Update
widget.Name = "Updated Sprocket";
await cudRepository.UpdateAsync(widget, userId, cancellationToken);
// Soft delete (sets IsDeleted = true, row is not removed)
await cudRepository.DeleteAsync(id, userId, cancellationToken);BaseEntityConfigurationapplies a global query filter (e => !e.IsDeleted) to every entity. Soft-deleted rows are automatically excluded from all queries.GetAllAttachedAsyncandGetAttachedAsyncreturn tracked entities for use when you need EF Core to detect changes without an explicitAttachcall.InitializeAuditFieldsmust be called on new entities before insert;CudRepositoryhandles this automatically when usingInsertAsyncorInsertRangeAsync.