IronAlpine.Data 3.0.0

dotnet add package IronAlpine.Data --version 3.0.0
                    
NuGet\Install-Package IronAlpine.Data -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IronAlpine.Data" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IronAlpine.Data" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="IronAlpine.Data" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IronAlpine.Data --version 3.0.0
                    
#r "nuget: IronAlpine.Data, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IronAlpine.Data@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IronAlpine.Data&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=IronAlpine.Data&version=3.0.0
                    
Install as a Cake Tool

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.