IronAlpine.Data
3.0.0
dotnet add package IronAlpine.Data --version 3.0.0
NuGet\Install-Package IronAlpine.Data -Version 3.0.0
<PackageReference Include="IronAlpine.Data" Version="3.0.0" />
<PackageVersion Include="IronAlpine.Data" Version="3.0.0" />
<PackageReference Include="IronAlpine.Data" />
paket add IronAlpine.Data --version 3.0.0
#r "nuget: IronAlpine.Data, 3.0.0"
#:package IronAlpine.Data@3.0.0
#addin nuget:?package=IronAlpine.Data&version=3.0.0
#tool nuget:?package=IronAlpine.Data&version=3.0.0
IronAlpine.Data
EF Core repositories, unit of work, domain event dispatch, auditing, and transactions.
- Target Frameworks: net9.0, net10.0
- Dependencies: EntityFrameworkCore, StackExchange.Redis
- Package Size: ~200 KB
- Replaces: 5 v2 packages (Data.Abstractions, EFCore, EFCore.Mediator, EFCore.Modeling, Caching.Redis)
What It Is
IronAlpine.Data provides the infrastructure layer:
- Repository Pattern — generic CRUD + specifications
- Unit of Work — transaction management, SaveChangesAsync
- Domain Event Dispatch — automatic event publishing on SaveChanges
- Auditing — who, what, when tracking (Slot 4)
- Transactions — ACID guarantees (Slot 5)
- Redis Caching — distributed caching for queries
Installation
dotnet add package IronAlpine.Data
Quick Setup
services.AddIronAlpineData<TimeOffContext>(configuration, cfg =>
{
cfg.ConfigureDbContext(opt => opt.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(TimeOffContext).Assembly.FullName)));
cfg.UseAuditingBehavior(); // Slot 4
cfg.UseTransactionBehavior(); // Slot 5
cfg.UseUnitOfWork();
cfg.UseRedisCache(); // Optional
});
DbContext Setup
Define Context
public class TimeOffContext : DbContext
{
public TimeOffContext(DbContextOptions<TimeOffContext> options)
: base(options) { }
public DbSet<TimeOff> TimeOffs { get; set; }
public DbSet<AuditLog> AuditLogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Apply Fluent API configurations
modelBuilder.Entity<TimeOff>()
.HasKey(t => t.Id);
modelBuilder.Entity<TimeOff>()
.Property(t => t.Status)
.HasConversion<string>();
}
}
Configure in DI
services.AddIronAlpineData<TimeOffContext>(configuration, cfg =>
{
cfg.ConfigureDbContext(opt => opt.UseSqlServer(
configuration.GetConnectionString("TimeOffDb"),
b => b.MigrationsAssembly(typeof(TimeOffContext).Assembly.FullName)));
});
Repository Pattern
Define Custom Repository
// Interface (Application layer)
public interface ITimeOffRepository : IRepository<TimeOff>
{
Task<IEnumerable<TimeOff>> ListPendingAsync(Guid approverUserId, CancellationToken ct);
}
// Implementation (Infrastructure layer)
public class TimeOffRepository : Repository<TimeOff>, ITimeOffRepository
{
private readonly TimeOffContext _context;
public TimeOffRepository(TimeOffContext context) : base(context)
{
_context = context;
}
public async Task<IEnumerable<TimeOff>> ListPendingAsync(
Guid approverUserId,
CancellationToken ct)
{
var spec = new TimeOffPendingApprovalSpec(approverUserId);
return await ListAsync(spec, ct);
}
}
// Register it
services.AddScoped<ITimeOffRepository, TimeOffRepository>();
Use Repository
public class ApproveTimeOffCommandHandler : IRequestHandler<ApproveTimeOffCommand, Unit>
{
private readonly IUnitOfWork _unitOfWork;
public async Task<Unit> Handle(ApproveTimeOffCommand request, CancellationToken ct)
{
// Get generic repository
var repo = _unitOfWork.Repository<TimeOff>();
// Or use custom repository
var customRepo = _unitOfWork.Repository<ITimeOffRepository>();
var timeOff = await repo.GetByIdAsync(request.TimeOffId, ct);
timeOff.Approve(...);
repo.Update(timeOff);
// SaveChangesAsync:
// 1. Validates (Slot 3 from Mediator)
// 2. Begins transaction (Slot 5)
// 3. Executes SaveChangesAsync
// 4. Dispatches domain events to Kafka
// 5. Records audit entry (Slot 4)
// 6. Commits transaction
await _unitOfWork.SaveChangesAsync(ct);
return Unit.Value;
}
}
Specification Pattern
// Define reusable query specifications
public class TimeOffPendingApprovalSpec : Specification<TimeOff>
{
public TimeOffPendingApprovalSpec(Guid approverUserId)
{
Query
.Where(t => t.Status == TimeOffStatus.Pending)
.Where(t => t.ApproverId == approverUserId)
.OrderByDescending(t => t.CreatedAt)
.Include(t => t.Requester);
}
}
// Use in repository
var spec = new TimeOffPendingApprovalSpec(approverUserId);
var pending = await repository.ListAsync(spec);
Auditing (Slot 4)
Automatic tracking of who changed what when.
Configuration
{
"IronAlpine": {
"Data": {
"EFCore": {
"Auditing": {
"Mode": "ActorPreferred",
"MissingActorBehavior": "UseDefault",
"DefaultActorValue": "system"
}
}
}
}
}
| Setting | Values | Purpose |
|---|---|---|
Mode |
ActorPreferred, CreateDate | Which fields to track |
MissingActorBehavior |
UseDefault, ThrowException | If actor not available |
DefaultActorValue |
string | Default user ID when actor missing |
Access Audit Logs
public class GetAuditLogsQuery : IRequest<List<AuditLogResponse>>
{
public Guid TimeOffId { get; set; }
}
public class GetAuditLogsQueryHandler : IRequestHandler<GetAuditLogsQuery, List<AuditLogResponse>>
{
private readonly IRepository<AuditLog> _auditRepository;
public async Task<List<AuditLogResponse>> Handle(
GetAuditLogsQuery request,
CancellationToken ct)
{
var logs = await _auditRepository.ListAsync(
new AuditLogsByEntitySpec(request.TimeOffId),
ct);
return logs.Select(Map).ToList();
}
}
Transactions (Slot 5)
Automatic ACID transaction wrapping for commands.
Configuration
{
"IronAlpine": {
"Data": {
"EFCore": {
"Transaction": {
"Mode": "ResultAware",
"IsolationLevel": "ReadCommitted"
}
}
}
}
}
How It Works
// Your handler
public async Task<Unit> Handle(ApproveTimeOffCommand request, CancellationToken ct)
{
// Executed inside a transaction automatically
var repo = _unitOfWork.Repository<TimeOff>();
var timeOff = await repo.GetByIdAsync(request.TimeOffId, ct);
timeOff.Approve(...);
repo.Update(timeOff);
// If exception thrown here → transaction rolled back automatically
// If successful → transaction committed automatically
await _unitOfWork.SaveChangesAsync(ct);
return Unit.Value;
}
Domain Event Dispatch
Automatic publishing of domain events to Kafka on SaveChangesAsync.
// 1. Define domain event
public class TimeOffApprovedEvent : IDomainEvent
{
public Guid TimeOffId { get; init; }
public Guid ApprovedBy { get; init; }
}
// 2. Publish from aggregate
public void Approve(UserId approver, string comment)
{
Status = TimeOffStatus.Approved;
AddDomainEvent(new TimeOffApprovedEvent
{
TimeOffId = Id.Value,
ApprovedBy = approver.Value
});
}
// 3. Automatically dispatched on SaveChangesAsync
await _unitOfWork.SaveChangesAsync(ct);
// Events now in Kafka topic (configured in EventBus setup)
Redis Caching
Distributed cache for frequently-accessed queries.
Configuration
{
"IronAlpine": {
"Data": {
"Redis": {
"Enabled": true,
"InstanceName": "TimeOffService",
"ConnectionString": "redis-server:6379"
}
}
}
}
Setup
services.AddIronAlpineData<TimeOffContext>(configuration, cfg =>
{
cfg.UseRedisCache(redisOptions =>
{
// Override defaults if needed
redisOptions.InstanceName = "TimeOff";
redisOptions.Expiration = TimeSpan.FromMinutes(5);
});
});
Behavior Pipeline Integration
Data owns Slots 4-5:
| Slot | Behavior | Purpose |
|---|---|---|
| 4 | AuditingBehavior | Audits every command |
| 5 | TransactionBehavior | Wraps in ACID transaction |
Complete flow:
Request
→ Slot 1: LoggingBehavior (Observability)
→ Slot 2: CachingBehavior (Mediator)
→ Slot 3: ValidationBehavior (Mediator)
→ Slot 4: AuditingBehavior (Data) ← records who did what
→ Slot 5: TransactionBehavior (Data) ← begins ACID transaction
→ Handler (your code)
→ SaveChangesAsync
→ Dispatches domain events
→ Commits transaction
Response
Best Practices
✅ DO
// 1. Use IUnitOfWork for persistence
private readonly IUnitOfWork _unitOfWork;
// 2. Inject specific repository interfaces
private readonly ITimeOffRepository _repository;
// 3. Use specifications for complex queries
var spec = new TimeOffPendingApprovalSpec(userId);
var pending = await repository.ListAsync(spec);
// 4. Let framework handle transactions
// Don't manually call BeginTransaction
// 5. Design aggregates for event dispatch
timeOff.Approve(...); // Publishes event internally
await _unitOfWork.SaveChangesAsync(ct); // Dispatches to Kafka
❌ DON'T
// 1. Inject DbContext directly
private readonly TimeOffContext _context; // Use IUnitOfWork
// 2. Use DbSet directly
await _context.TimeOffs.ToListAsync(); // Use repository
// 3. Manage transactions manually
_context.Database.BeginTransactionAsync(); // Behavior handles it
// 4. Miss domain events
timeOff.Status = TimeOffStatus.Approved; // No event published
// Use method: timeOff.Approve(...)
// 5. Ignore SaveChangesAsync
_context.SaveChanges(); // Sync, no event dispatch, no transaction handling
Configuration Reference
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=TimeOff;Integrated Security=true;"
},
"IronAlpine": {
"Data": {
"EFCore": {
"Auditing": {
"Mode": "ActorPreferred",
"MissingActorBehavior": "UseDefault",
"DefaultActorValue": "system"
}
},
"Redis": {
"Enabled": true,
"InstanceName": "TimeOffService",
"ConnectionString": "localhost:6379"
}
}
}
}
Examples
See IRONALPINE_V3_DOCUMENTATION.md for detailed examples.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- IronAlpine.Kernel (>= 3.0.0)
- IronAlpine.Mediator (>= 3.0.0)
- Microsoft.EntityFrameworkCore (>= 9.0.7)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.7)
- Microsoft.Extensions.Caching.Memory (>= 9.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.7)
- StackExchange.Redis (>= 2.8.37)
-
net9.0
- IronAlpine.Kernel (>= 3.0.0)
- IronAlpine.Mediator (>= 3.0.0)
- Microsoft.EntityFrameworkCore (>= 9.0.7)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.7)
- Microsoft.Extensions.Caching.Memory (>= 9.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.7)
- StackExchange.Redis (>= 2.8.37)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on IronAlpine.Data:
| Package | Downloads |
|---|---|
|
IronAlpine.Security
JWT authentication, claim-based authorization, and EFCore permission store for IronAlpine microservices. Provides ICurrentUser, permission policy provider, and security builder. All implementations internal. |
|
|
IronAlpine.EventBus
Kafka-backed event bus with outbox/inbox pattern, dead letter support, and replay functionality. Unified namespace: IronAlpine.EventBus. Kafka implementation is internal; only contracts and topology builder are public. SSL cert parameters fully configurable (SslCaLocation, SslCertLocation, SslKeyLocation, SslKeyPassword). |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 160 | 7/1/2026 |
Stable mediator release with request/response, notification publish strategies, streaming, and dependency injection integration.