SQL Server Provider
The SQL Server provider offers full relational database support with Dapper-based query execution, connection pooling, health checks, and built-in resilience.
Before You Start
- .NET 8.0+ (or .NET 9/10 for latest features)
- A SQL Server instance (local, Azure SQL, or AWS RDS)
- Familiarity with data access and IDb interface
Installation
dotnet add package Excalibur.Data.SqlServer
Dependencies: Excalibur.Data.Abstractions, Microsoft.Data.SqlClient, Dapper
Quick Start
using Microsoft.Extensions.DependencyInjection;
services.AddSqlServerPersistence(opts =>
{
opts.ConnectionString = "Server=localhost;Database=MyApp;Trusted_Connection=true;";
});
Registration Methods
| Method | What It Registers | Key Options |
|---|---|---|
AddSqlServerPersistence(opts) | Core data executors + persistence | ConnectionString, CommandTimeout |
AddSqlServerPersistenceWithRetry(opts) | Persistence + automatic retry | Resiliency.MaxRetryAttempts |
AddSqlServerPersistenceWithEncryption(opts) | Persistence + Always Encrypted | Security.ColumnEncryptionSetting |
AddSqlServerPersistenceReadOnly(opts) | Read-only replica connection | ConnectionString |
AddSqlServerPersistenceHighAvailability(opts) | Failover support | ConnectionString |
AddSqlServerDeadLetterStore(opts) | IDeadLetterStore | TableName |
AddSqlServerProjectionStore<T>(opts) | IProjectionStore<T> | ConnectionString, TableName |
AddSqlServerTransactionScope(...) | Transaction scope | IsolationLevel, Timeout |
All methods also accept IConfiguration binding: AddSqlServerPersistence(configuration, sectionName: "SqlServerPersistence").
Batch Projection Registration
Register multiple projections sharing the same connection:
services.AddSqlServerProjections(connectionString, projections =>
{
projections.Add<OrderSummary>();
projections.Add<CustomerProfile>(o => o.TableName = "CustomerViews");
projections.Add<InventoryView>(o => o.TableName = "InventoryViews");
});
Health Checks
services.AddHealthChecks()
.AddSqlServerPersistenceHealthCheck();
Data Request Pattern
Define queries as reusable data request objects:
public class GetOrderByIdRequest : DataRequest<Order?>
{
public GetOrderByIdRequest(Guid orderId)
{
Command = new CommandDefinition(
"SELECT * FROM Orders WHERE Id = @Id",
new { Id = orderId });
}
}
// Execute via persistence provider
var order = await provider.ExecuteAsync(new GetOrderByIdRequest(orderId), cancellationToken);
Batch Operations
var requests = new[]
{
new InsertOrderRequest(order1),
new InsertOrderRequest(order2),
new InsertOrderRequest(order3)
};
await sqlProvider.ExecuteBatchInTransactionAsync(requests, scope, cancellationToken);
Configuration
{
"SqlServerPersistence": {
"ConnectionString": "Server=localhost;Database=MyApp;Trusted_Connection=true;",
"CommandTimeout": 30,
"EnableConnectionResiliency": true,
"MaxRetryAttempts": 3,
"RetryDelayMilliseconds": 1000,
"EnableDetailedLogging": false,
"EnableMetrics": true
}
}
Dead Letter Store
For messages that fail processing:
services.AddSqlServerDeadLetterStore(options =>
{
options.ConnectionString = connectionString;
options.TableName = "DeadLetters";
});
See Also
- Data Providers Overview — Architecture and core abstractions
- PostgreSQL Provider — Open-source SQL alternative
- Event Sourcing — SQL Server event store integration