Plex.DataAccess.Base
A .NET 8 library that provides base classes for the Unit of Work and Repository patterns with EF Core. Supports dynamic multi-tenant connection strings, SQL Server and PostgreSQL, concurrency conflict auto-resolution, raw SQL connection factories for Dapper, and SQL Server JSON function mappings.
Features
- Unit of Work + Repository pattern —
UnitOfWorkBase, RepositoryBase, and DbContextBase with full CRUD operations
- Multi-provider — SQL Server and PostgreSQL, switchable per database via configuration
- Dynamic connection strings — per-request database targeting via
cx-db and cx-server HTTP headers (multi-tenant)
- Concurrency handling — automatic retry on
DbUpdateConcurrencyException (up to 5 retries with entity reload)
- Raw SQL connection factory —
SqlConnectionFactory for Dapper queries outside of EF Core
- JSON function mappings —
JSON_VALUE and JSON_QUERY SQL Server functions usable in LINQ queries
- Strongly-typed IDs —
TypedIdValueBase for DDD-style typed ID value objects
- EF Core options from config — command timeout, retry-on-failure, lazy loading, change tracking, query tracking, migrations, sensitive data logging all driven by
appsettings.json
Installation
dotnet add package Plex.DataAccess.Base
Configuration
Add these settings to your appsettings.json (all are optional — defaults shown):
{
"ConnectionStringKey": "DefaultConnection",
"ConnectionStrings": {
"DefaultConnection": "Server=%server%;Database=%db%;Trusted_Connection=True;"
},
"EfSqlCommandTimeOutInSecond": 300,
"EfSqlMaxRetryOnFailureCount": 0,
"EfEnableMigration": false,
"EfUseLazyLoading": false,
"EfUseChangeTrackingProxies": false,
"EfUseQueryTrackingBehavior": false,
"EfIsHandleDbUpdateConcurrency": true,
"EfEnableSensitiveDataLogging": false,
"AppSettings": {
"DbProviderMappings": {
"my-database-name": "mssql",
"my-pg-database": "postgresql"
}
}
}
| Setting |
Default |
Description |
EfSqlCommandTimeOutInSecond |
300 |
SQL command timeout in seconds |
EfSqlMaxRetryOnFailureCount |
0 |
Max automatic retries on transient failures (0 = disabled) |
EfEnableMigration |
false |
Run Database.Migrate() on context creation |
EfUseLazyLoading |
false |
Enable EF Core lazy loading proxies |
EfUseChangeTrackingProxies |
false |
Enable EF Core change tracking proxies |
EfUseQueryTrackingBehavior |
false |
If false, sets QueryTrackingBehavior.NoTracking |
EfIsHandleDbUpdateConcurrency |
true |
Auto-retry on concurrency conflicts |
EfEnableSensitiveDataLogging |
false |
Include parameter values in EF Core logs |
Usage
Define your DbContext
public class MyDbContext : DbContextBase
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
public DbSet<Order> Orders => Set<Order>();
}
Define your Unit of Work
public class MyUnitOfWork : UnitOfWorkBase<MyDbContext>
{
public MyUnitOfWork(Func<MyDbContext> factory) : base(factory) { }
}
Define your Repository
public class OrderRepository : RepositoryBase<Order, MyUnitOfWork>, IOrderRepository
{
public OrderRepository(Func<MyUnitOfWork> unitOfWorkFactory) : base(unitOfWorkFactory) { }
}
Register in DI
// Pooled DbContext registration
builder.Services.RegisterDbContextPool<MyDbContext>();
// Standard DbContext registration
builder.Services.RegisterDbContext<MyDbContext>();
// With cache interceptor
builder.Services.RegisterDbContextPool<MyDbContext, MyCacheInterceptor>();
// Raw SQL connection factory for Dapper (scoped)
builder.Services.RegisterSqlConnectionFactory<ISqlConnectionFactory, SqlConnectionFactory>();
// Raw SQL connection factory for Dapper (singleton/pooled)
builder.Services.RegisterSqlConnectionFactoryPool<ISqlConnectionFactory, SqlConnectionFactory>();
Repository operations
// Query
var orders = repository.QueryableAsNoTracking(o => o.Status == "Active");
var order = await repository.FirstOrDefaultAsync(o => o.Id == orderId);
// Add with immediate save
await repository.SaveAddedAsync(newOrder);
// Update with immediate save
await repository.SaveUpdatedAsync(existingOrder);
// Remove with immediate save
await repository.SaveRemovedAsync(order);
// Save via Unit of Work
await unitOfWork.CommitAsync(cancellationToken);
JSON functions in LINQ queries
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
EfJsonExtensions.AddJsonValue(modelBuilder);
EfJsonQueryExtensions.AddJsonQuery(modelBuilder);
}
// Then use in queries:
var results = context.Orders
.Where(o => EfJsonExtensions.JsonValue(o.JsonData, "$.status") == "active");
API Reference
Interfaces
| Interface |
Description |
IUnitOfWork |
Commit, change tracker, raw SQL connection |
IRepositoryBase<TEntity> |
Full CRUD with sync/async, tracking/no-tracking variants |
ISqlConnectionFactory |
Open sync/async raw SqlConnection for Dapper |
Base Classes
| Class |
Description |
DbContextBase |
EF Core DbContext with auto-configuration from PlexDbContextOption |
UnitOfWorkBase<TContext> |
Abstract Unit of Work with concurrency retry |
RepositoryBase<TEntity, TUnitOfWork> |
Generic repository with 26 CRUD methods |
SqlConnectionFactory |
Raw SqlConnection manager for Dapper |
TypedIdValueBase |
DDD-style strongly-typed ID base class (wraps long?) |
Extension Methods
| Class |
Methods |
ServiceCollectionExtensions |
RegisterDbContextPool, RegisterDbContext, RegisterSqlConnectionFactory, RegisterSqlConnectionFactoryPool |
DbContextOptionsExtensions |
AppendOptions<TContext> |
EfJsonExtensions |
JsonValue, AddJsonValue |
EfJsonQueryExtensions |
JsonQuery, AddJsonQuery |
Dependencies
| Package |
Version |
| Microsoft.EntityFrameworkCore |
8.0.x |
| Microsoft.EntityFrameworkCore.SqlServer |
8.0.x |
| Microsoft.EntityFrameworkCore.Proxies |
8.0.x |
| Microsoft.Data.SqlClient |
5.2.x |
| Plex.Extensions.Configuration |
8.0.x |
| System.Text.Json |
8.0.x |
License
Plex-Solution Community Source-Available License — free for non-commercial use only.