RecurringThings.PostgreSQL
0.1.0-alpha.4
See the version list below for details.
dotnet add package RecurringThings.PostgreSQL --version 0.1.0-alpha.4
NuGet\Install-Package RecurringThings.PostgreSQL -Version 0.1.0-alpha.4
<PackageReference Include="RecurringThings.PostgreSQL" Version="0.1.0-alpha.4" />
<PackageVersion Include="RecurringThings.PostgreSQL" Version="0.1.0-alpha.4" />
<PackageReference Include="RecurringThings.PostgreSQL" />
paket add RecurringThings.PostgreSQL --version 0.1.0-alpha.4
#r "nuget: RecurringThings.PostgreSQL, 0.1.0-alpha.4"
#:package RecurringThings.PostgreSQL@0.1.0-alpha.4
#addin nuget:?package=RecurringThings.PostgreSQL&version=0.1.0-alpha.4&prerelease
#tool nuget:?package=RecurringThings.PostgreSQL&version=0.1.0-alpha.4&prerelease
RecurringThings.PostgreSQL
PostgreSQL persistence provider for RecurringThings.
Installation
dotnet add package RecurringThings
dotnet add package RecurringThings.PostgreSQL
Configuration
using RecurringThings.Configuration;
using RecurringThings.PostgreSQL.Configuration;
services.AddRecurringThings(builder =>
builder.UsePostgreSql(options =>
{
options.ConnectionString = "Host=localhost;Database=myapp;Username=user;Password=pass";
options.RunMigrationsOnStartup = true; // Optional, default is true
}));
Migrations
Migrations run automatically on startup when RunMigrationsOnStartup = true (default). The provider uses Entity Framework Core migrations with PostgreSQL advisory locks to ensure safe concurrent migration across multiple application replicas.
To disable automatic migrations:
builder.UsePostgreSql(options =>
{
options.ConnectionString = connectionString;
options.RunMigrationsOnStartup = false;
});
Indexes
The provider creates indexes for efficient querying:
(organization, resource_path, start_time, recurrence_end_time)on recurrences(organization, resource_path, start_time, end_time)on occurrences(recurrence_id)on exceptions and overrides
Transactions
Use IPostgresTransactionManager from the Transactional library:
using Transactional.PostgreSQL;
public class CalendarService(IRecurrenceEngine engine, IPostgresTransactionManager transactionManager)
{
public async Task CreateMultipleEntriesAsync()
{
await using var context = await transactionManager.BeginTransactionAsync();
try
{
await engine.CreateRecurrenceAsync(request1, context);
await engine.CreateOccurrenceAsync(request2, context);
await context.CommitAsync();
}
catch
{
await context.RollbackAsync();
throw;
}
}
}
Usage Examples
Basic Setup
public class CalendarService(IRecurrenceEngine engine)
{
public async Task CreateWeeklyMeetingAsync()
{
var recurrence = await engine.CreateRecurrenceAsync(new RecurrenceCreate
{
Organization = "tenant1",
ResourcePath = "user123/calendar",
Type = "meeting",
StartTimeUtc = new DateTime(2025, 1, 6, 14, 0, 0, DateTimeKind.Utc),
Duration = TimeSpan.FromHours(1),
RecurrenceEndTimeUtc = new DateTime(2025, 12, 31, 23, 59, 59, DateTimeKind.Utc),
RRule = "FREQ=WEEKLY;BYDAY=MO;UNTIL=20251231T235959Z",
TimeZone = "America/New_York",
Extensions = new Dictionary<string, string>
{
["title"] = "Weekly Team Standup",
["location"] = "Conference Room A"
}
});
}
public async Task GetJanuaryEntriesAsync()
{
var start = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var end = new DateTime(2025, 1, 31, 23, 59, 59, DateTimeKind.Utc);
await foreach (var entry in engine.GetAsync("tenant1", "user123/calendar", start, end, null))
{
Console.WriteLine($"{entry.Type}: {entry.StartTime} - {entry.EndTime}");
}
}
}
Querying with Type Filter
// Get only appointments and meetings
await foreach (var entry in engine.GetAsync(
"tenant1", "user123/calendar", start, end,
types: ["appointment", "meeting"]))
{
// Process filtered entries
}
Updating Entries
// Update a standalone occurrence
var entries = await engine.GetAsync(org, path, start, end, null).ToListAsync();
var entry = entries.First(e => e.OccurrenceId.HasValue);
entry.StartTime = entry.StartTime.AddHours(1);
entry.Duration = TimeSpan.FromMinutes(45);
var updated = await engine.UpdateAsync(entry);
// EndTime is automatically recomputed
// Update a virtualized occurrence (creates an override)
var virtualizedEntry = entries.First(e => e.RecurrenceOccurrenceDetails != null);
virtualizedEntry.Duration = TimeSpan.FromMinutes(45);
var overridden = await engine.UpdateAsync(virtualizedEntry);
// Original values preserved in RecurrenceOccurrenceDetails.Original
Deleting Entries
// Delete entire recurrence series (cascade deletes exceptions/overrides)
await engine.DeleteAsync(recurrenceEntry);
// Delete a virtualized occurrence (creates an exception)
await engine.DeleteAsync(virtualizedEntry);
// Restore an overridden occurrence to original state
if (entry.OverrideId.HasValue)
{
await engine.RestoreAsync(entry);
}
Integration Tests
Set the environment variable before running integration tests:
export POSTGRES_CONNECTION_STRING="Host=localhost;Database=test;Username=user;Password=pass"
dotnet test --filter 'Category=Integration'
Limitations
- Database must exist before running the application (schema is auto-created)
- All DateTime values must be UTC
- RRule must use UNTIL (COUNT is not supported)
- UNTIL must have UTC suffix (Z)
| 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
- Microsoft.EntityFrameworkCore (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.0)
- RecurringThings (>= 0.1.0-alpha.4)
- Transactional.PostgreSQL (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0 | 35 | 2/2/2026 |
| 0.1.0-alpha.10 | 36 | 2/2/2026 |
| 0.1.0-alpha.9 | 41 | 2/2/2026 |
| 0.1.0-alpha.8 | 43 | 1/28/2026 |
| 0.1.0-alpha.7 | 44 | 1/28/2026 |
| 0.1.0-alpha.6 | 37 | 1/28/2026 |
| 0.1.0-alpha.5 | 41 | 1/27/2026 |
| 0.1.0-alpha.4 | 37 | 1/27/2026 |
| 0.1.0-alpha.3 | 35 | 1/27/2026 |
| 0.1.0-alpha.2 | 38 | 1/27/2026 |
| 0.1.0-alpha.1 | 37 | 1/27/2026 |