Excalibur.Data.ElasticSearch
10.0.0-alpha.13
dotnet add package Excalibur.Data.ElasticSearch --version 10.0.0-alpha.13
NuGet\Install-Package Excalibur.Data.ElasticSearch -Version 10.0.0-alpha.13
<PackageReference Include="Excalibur.Data.ElasticSearch" Version="10.0.0-alpha.13" />
<PackageVersion Include="Excalibur.Data.ElasticSearch" Version="10.0.0-alpha.13" />
<PackageReference Include="Excalibur.Data.ElasticSearch" />
paket add Excalibur.Data.ElasticSearch --version 10.0.0-alpha.13
#r "nuget: Excalibur.Data.ElasticSearch, 10.0.0-alpha.13"
#:package Excalibur.Data.ElasticSearch@10.0.0-alpha.13
#addin nuget:?package=Excalibur.Data.ElasticSearch&version=10.0.0-alpha.13&prerelease
#tool nuget:?package=Excalibur.Data.ElasticSearch&version=10.0.0-alpha.13&prerelease
Excalibur.Data.ElasticSearch
Elasticsearch data provider for the Excalibur framework, providing enterprise-grade document storage, full-text search, and projection management with comprehensive resilience, performance, and monitoring capabilities.
Overview
This package provides Elasticsearch integration for Excalibur applications, enabling:
- Document Storage: Type-safe repository pattern with CRUD operations
- Full-Text Search: Rich query DSL support with aggregations
- Index Management: Lifecycle management, templates, and schema evolution
- Projections: Event sourcing projection support with rebuild capabilities
- Resilience: Circuit breaker, retry policies, and dead letter handling
- Performance: Connection pooling with configurable pool types and node sniffing
- Monitoring: OpenTelemetry integration, metrics, and health checks
- Security: API key, certificate, and basic authentication
Installation
dotnet add package Excalibur.Data.ElasticSearch
Dependencies:
Elastic.Clients.Elasticsearch(8.x)Microsoft.Extensions.DependencyInjectionMicrosoft.Extensions.Options
Configuration
Basic Connection
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
// Single node
options.Url = new Uri("https://localhost:9200");
// Or multiple nodes
options.Urls = new[]
{
new Uri("https://node1:9200"),
new Uri("https://node2:9200"),
new Uri("https://node3:9200")
};
});
Elastic Cloud
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
options.CloudId = "my-deployment:dXMtY2VudHJhbDE...";
options.Connection.ApiKey = "your-api-key";
});
Authentication Options
API Key (Recommended)
options.Connection.ApiKey = "your-api-key";
// Or Base64-encoded
options.Connection.Base64ApiKey = "base64-encoded-api-key";
Basic Authentication
options.Connection.Username = "elastic";
options.Connection.Password = "your-password";
Certificate Fingerprint
options.Connection.CertificateFingerprint = "A1:B2:C3:...";
options.Connection.DisableCertificateValidation = false; // Keep false in production
Environment Variables
ELASTICSEARCH__URL=https://localhost:9200
ELASTICSEARCH__CONNECTION__APIKEY=your-api-key
ELASTICSEARCH__CONNECTION__USERNAME=elastic
ELASTICSEARCH__CONNECTION__PASSWORD=your-password
services.Configure<ElasticsearchConfigurationOptions>(
configuration.GetSection("Elasticsearch"));
Connection Settings
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
// Timeouts
options.Connection.RequestTimeout = TimeSpan.FromSeconds(30);
options.Connection.PingTimeout = TimeSpan.FromSeconds(5);
// Connection pooling
options.ConnectionPoolType = ConnectionPoolType.Sniffing;
options.Connection.MaximumConnectionsPerNode = 80;
// Node discovery
options.EnableSniffing = true;
options.Connection.SniffingInterval = TimeSpan.FromHours(1);
});
Security
Two distinct secret-handling capabilities live in this package, and they are deliberately separate abstractions:
Connection-credential storage
IElasticsearchKeyStorage stores the opaque credentials this package uses to authenticate to Elasticsearch
itself -- OAuth tokens, service-account secrets, passwords, API keys. An in-memory development store is wired
by default. To back it with Azure Key Vault instead, install the optional
Excalibur.Data.ElasticSearch.Azure package and call AddAzureKeyVaultCredentialStorage(configuration)
before registering authentication. That provider lives in its own package so this one does not put the Azure
SDK on consumers who never use Key Vault.
Field-level encryption key management
Field-level encryption (AddFieldEncryption()) delegates key resolution and cryptographic operations entirely
to Excalibur.Compliance's IKeyManagementProvider and IEncryptionProviderRegistry -- this package never
sees a raw encryption key. Call AddKeyManagement(configuration) to select a provider:
Local-- the in-process development provider (Excalibur.Compliance'sAddDevEncryption()). Keys are lost on restart; never use in production.AzureKeyVault/AwsKms/GoogleCloudKms/HashiCorpVault-- register the correspondingExcalibur.Compliance.Azure/.Aws/.Vaultpackage's key-management extension (e.g.services.AddEncryption(e => e.UseKeyManagement<AzureKeyVaultProvider>())) before callingAddKeyManagement; it is refused otherwise, so a cloud selection can never silently fall back to the development provider.
Index Management
Index Lifecycle Management
// Inject IIndexLifecycleManager
public class MyService
{
private readonly IIndexLifecycleManager _lifecycleManager;
public async Task ConfigureIndexAsync()
{
var policy = new IndexLifecyclePolicy
{
Hot = new HotPhaseConfiguration
{
RolloverConditions = new RolloverConditions
{
MaxAge = TimeSpan.FromDays(7),
MaxSize = "50gb"
}
},
Warm = new WarmPhaseConfiguration
{
MinAge = TimeSpan.FromDays(30)
},
Delete = new DeletePhaseConfiguration
{
MinAge = TimeSpan.FromDays(90)
}
};
await _lifecycleManager.CreatePolicyAsync("my-policy", policy);
}
}
Index Templates
// Inject IIndexTemplateManager
var template = new IndexTemplateConfiguration
{
Name = "my-template",
IndexPatterns = new[] { "logs-*" },
Priority = 100
};
await _templateManager.CreateTemplateAsync(template);
Projections
Projection Store
public class OrderProjection
{
public string OrderId { get; set; }
public string CustomerId { get; set; }
public decimal Total { get; set; }
public DateTime LastModified { get; set; }
}
// Configure projection store
services.AddElasticSearchProjectionStore<OrderProjection>(options =>
{
options.IndexPrefix = "orders";
});
Projection Rebuild
// Inject IProjectionRebuildManager
var request = new ProjectionRebuildRequest
{
ProjectionType = nameof(OrderProjection),
SourceIndexName = "orders-v1",
TargetIndexName = "orders-v2",
CreateNewIndex = true,
UseAliasing = true,
BatchSize = 1000
};
var result = await _rebuildManager.StartRebuildAsync(request);
// Check status
var status = await _rebuildManager.GetRebuildStatusAsync(result.OperationId);
Eventual Consistency Tracking
// Inject IEventualConsistencyTracker
var eventId = Guid.NewGuid().ToString();
await _tracker.TrackWriteModelEventAsync(eventId, "order-1", "OrderCreated", DateTime.UtcNow);
await _tracker.TrackReadModelProjectionAsync(eventId, nameof(OrderProjection), DateTime.UtcNow);
// Check lag
var lag = await _tracker.GetConsistencyLagAsync(nameof(OrderProjection));
if (!lag.IsWithinSLA)
{
_logger.LogWarning("Projection lagging by {Events} events", lag.PendingEvents);
}
Schema Evolution
// Inject ISchemaEvolutionHandler
var comparison = await _schemaHandler.CompareSchemaAsync("orders-v1", "orders-v2");
if (!comparison.IsBackwardsCompatible)
{
var migrationRequest = new SchemaMigrationRequest
{
ProjectionType = nameof(OrderProjection),
SourceIndex = "orders-v1",
TargetIndex = "orders-v2",
Strategy = MigrationStrategy.AliasSwitch,
NewSchema = new Elastic.Clients.Elasticsearch.Mapping.Properties
{
{ "orderId", new Elastic.Clients.Elasticsearch.Mapping.KeywordProperty() },
{ "status", new Elastic.Clients.Elasticsearch.Mapping.KeywordProperty() },
{ "total", new Elastic.Clients.Elasticsearch.Mapping.DoubleNumberProperty() }
}
};
var plan = await _schemaHandler.PlanMigrationAsync(migrationRequest);
await _schemaHandler.ExecuteMigrationAsync(plan);
}
Resilience
Circuit Breaker
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
options.Resilience = new ElasticsearchResilienceOptions
{
CircuitBreaker = new CircuitBreakerOptions
{
Enabled = true,
MinimumThroughput = 10, // Minimum requests before evaluation
BreakDuration = TimeSpan.FromSeconds(30),
SamplingDuration = TimeSpan.FromSeconds(60),
FailureRatio = 0.5 // opens at a 50% failure ratio
}
};
});
Retry Policy
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
options.Resilience = new ElasticsearchResilienceOptions
{
Retry = new ElasticSearchRetryPolicyOptions
{
Enabled = true,
MaxAttempts = 3,
BaseDelay = TimeSpan.FromMilliseconds(100),
MaxDelay = TimeSpan.FromSeconds(30),
UseExponentialBackoff = true,
JitterFactor = 0.1
}
};
});
Dead Letter Handling
services.Configure<ElasticsearchDeadLetterOptions>(options =>
{
options.DeadLetterIndexPrefix = "dead-letters";
options.MaxRetryCount = 3;
options.RetentionPeriod = TimeSpan.FromDays(30);
});
Monitoring
OpenTelemetry Metrics
services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddMeter("Excalibur.Data.ElasticSearch");
})
.WithTracing(tracing =>
{
tracing.AddSource("Excalibur.Data.ElasticSearch");
});
Monitoring Settings
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
options.Monitoring = new ElasticsearchMonitoringOptions
{
Metrics = new MetricsOptions { Enabled = true },
RequestLogging = new RequestLoggingOptions
{
Enabled = true,
LogFailuresOnly = true,
MaxBodySizeBytes = 1024
},
Performance = new PerformanceDiagnosticsOptions
{
Enabled = true,
SlowOperationThreshold = TimeSpan.FromSeconds(1),
SamplingRate = 0.1 // 10% sampling
}
};
});
Health Checks
Registration
services.AddHealthChecks()
.AddElasticSearchHealthCheck(tags: new[] { "ready", "elasticsearch" });
Custom Configuration
services.AddHealthChecks()
.AddCheck<ElasticClientHealthCheck>(
"elasticsearch",
failureStatus: HealthStatus.Degraded,
tags: new[] { "ready" },
timeout: TimeSpan.FromSeconds(10));
Repository Pattern
Define Repository
public class OrderRepository : ElasticRepositoryBase<Order, string>
{
public OrderRepository(IElasticClient client) : base(client, "orders") { }
public async Task<IEnumerable<Order>> FindByCustomerAsync(string customerId)
{
return await SearchAsync(q => q
.Match(m => m.Field(f => f.CustomerId).Query(customerId)));
}
}
Register and Use
services.AddScoped<IOrderRepository, OrderRepository>();
// In your service
public class OrderService
{
private readonly IOrderRepository _orders;
public async Task<Order> GetAsync(string id)
{
return await _orders.GetByIdAsync(id);
}
}
Troubleshooting
Common Issues
Connection Refused
Elasticsearch.Net.ElasticsearchClientException: Connection refused
Solutions:
- Verify Elasticsearch is running
- Check URL and port configuration
- Verify firewall allows connections
- Check certificate fingerprint for HTTPS
Authentication Failed
Elasticsearch.Net.ElasticsearchClientException: 401 Unauthorized
Solutions:
- Verify API key or credentials
- Check user has required permissions
- Ensure authentication method matches server configuration
Index Not Found
Elasticsearch.Net.ElasticsearchClientException: index_not_found_exception
Solutions:
- Verify index exists:
GET /_cat/indices - Check index name spelling (case-sensitive)
- Create index if using auto-create
Logging Configuration
{
"Logging": {
"LogLevel": {
"Excalibur.Data.ElasticSearch": "Debug",
"Elastic.Transport": "Warning"
}
}
}
Complete Configuration Reference
services.Configure<ElasticsearchConfigurationOptions>(options =>
{
// Endpoint
options.Url = new Uri("https://localhost:9200");
options.CloudId = null;
options.ConnectionPoolType = ConnectionPoolType.Static;
options.EnableSniffing = false;
// Connection (authentication, timeouts, SSL/TLS)
options.Connection.Username = null;
options.Connection.Password = null;
options.Connection.ApiKey = "your-api-key";
options.Connection.CertificateFingerprint = null;
options.Connection.DisableCertificateValidation = false;
options.Connection.RequestTimeout = TimeSpan.FromSeconds(30);
options.Connection.PingTimeout = TimeSpan.FromSeconds(5);
options.Connection.MaximumConnectionsPerNode = 80;
options.Connection.SniffingInterval = TimeSpan.FromHours(1);
// Resilience
options.Resilience = new ElasticsearchResilienceOptions();
// Monitoring
options.Monitoring = new ElasticsearchMonitoringOptions();
// Projections
options.Projections = new ProjectionOptions();
});
See Also
License
This project is multi-licensed under:
See LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Ben.Demystifier (>= 0.4.1)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Cronos (>= 0.12.0)
- Dapper (>= 2.1.72)
- Elastic.Clients.Elasticsearch (>= 9.4.1)
- Elastic.Transport (>= 0.17.1)
- Excalibur.AuditLogging.Abstractions (>= 10.0.0-alpha.13)
- Excalibur.Compliance (>= 10.0.0-alpha.13)
- Excalibur.Data (>= 10.0.0-alpha.13)
- Excalibur.Dispatch.Abstractions (>= 10.0.0-alpha.13)
- Excalibur.EventSourcing (>= 10.0.0-alpha.13)
- FluentValidation (>= 12.1.1)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- IdentityModel (>= 7.0.0)
- JsonNet.ContractResolvers (>= 2.0.0)
- Medo.Uuid7 (>= 3.2.0)
- MemoryPack (>= 1.21.4)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Caching.Memory (>= 10.0.10)
- Microsoft.Extensions.Configuration (>= 10.0.10)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.10)
- Microsoft.Extensions.Configuration.CommandLine (>= 10.0.10)
- Microsoft.Extensions.Configuration.EnvironmentVariables (>= 10.0.10)
- Microsoft.Extensions.Configuration.Json (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.10)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Hosting (>= 10.0.10)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Logging (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Logging.Console (>= 10.0.10)
- Microsoft.Extensions.ObjectPool (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.10)
- Microsoft.IdentityModel.Tokens (>= 8.17.0)
- NCrontab (>= 3.4.0)
- OpenTelemetry (>= 1.15.3)
- OpenTelemetry.Api (>= 1.15.3)
- OpenTelemetry.Extensions.Hosting (>= 1.15.3)
- Polly (>= 8.6.6)
- System.IdentityModel.Tokens.Jwt (>= 8.17.0)
- System.IO.Hashing (>= 10.0.7)
- System.Threading.RateLimiting (>= 10.0.7)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Excalibur.Data.ElasticSearch:
| Package | Downloads |
|---|---|
|
Excalibur.Inbox.ElasticSearch
Elasticsearch implementation of the inbox pattern for Excalibur, providing idempotent message processing. PRE-RELEASE: the public API is not frozen and may change between pre-release builds. Not recommended for production use. Known issues: https://docs.excalibur-dispatch.dev/docs/known-issues |
|
|
Excalibur.Outbox.ElasticSearch
Elasticsearch implementation of the outbox pattern for Excalibur. PRE-RELEASE: the public API is not frozen and may change between pre-release builds. Not recommended for production use. Known issues: https://docs.excalibur-dispatch.dev/docs/known-issues |
|
|
Excalibur.Data.ElasticSearch.Azure
Azure Key Vault connection-credential storage for Excalibur.Data.ElasticSearch. Optional: install this only if you back Elasticsearch authentication secrets with Key Vault. PRE-RELEASE: the public API is not frozen and may change between pre-release builds. Not recommended for production use. Known issues: https://docs.excalibur-dispatch.dev/docs/known-issues |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0-alpha.13 | 0 | 9/29/2026 |
| 10.0.0-alpha.12 | 64 | 9/25/2026 |
| 10.0.0-alpha.11 | 77 | 9/15/2026 |
| 10.0.0-alpha.10 | 75 | 9/6/2026 |
| 10.0.0-alpha.9 | 79 | 8/31/2026 |
| 10.0.0-alpha.8 | 97 | 8/14/2026 |
| 10.0.0-alpha.7 | 89 | 8/13/2026 |
| 10.0.0-alpha.6 | 88 | 8/11/2026 |
| 10.0.0-alpha.5 | 80 | 8/10/2026 |
| 10.0.0-alpha.4 | 79 | 8/10/2026 |
| 3.0.0-alpha.216 | 102 | 6/30/2026 |
| 3.0.0-alpha.215 | 88 | 6/23/2026 |
| 3.0.0-alpha.214 | 92 | 6/23/2026 |
| 3.0.0-alpha.208 | 86 | 6/11/2026 |
| 3.0.0-alpha.207 | 84 | 6/11/2026 |
| 3.0.0-alpha.205 | 83 | 6/10/2026 |
| 3.0.0-alpha.204 | 93 | 6/8/2026 |
| 3.0.0-alpha.203 | 91 | 6/8/2026 |
| 3.0.0-alpha.202 | 79 | 6/8/2026 |
| 3.0.0-alpha.201 | 85 | 6/8/2026 |
Release notes and versioning policy: https://docs.excalibur-dispatch.dev/docs/migration/version-upgrades