### Complete Continuous Aggregate Configuration Example Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md A comprehensive example demonstrating the configuration of a continuous aggregate, including hypertable setup, multiple aggregate functions, group by columns, filtering, index creation, and materialized-only settings. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class TradeConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => new { x.Ticker, x.Timestamp }); // Configure the source hypertable builder.IsHypertable(x => x.Timestamp) .WithChunkTimeInterval("7 days"); } } public class TradeAggregateConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); // Configure comprehensive continuous aggregate builder.IsContinuousAggregate( "trade_hourly_stats", "1 hour", x => x.Timestamp, timeBucketGroupBy: true, chunkInterval: "7 days") .AddAggregateFunction(x => x.AveragePrice, x => x.Price, EAggregateFunction.Avg) .AddAggregateFunction(x => x.MaxPrice, x => x.Price, EAggregateFunction.Max) .AddAggregateFunction(x => x.MinPrice, x => x.Price, EAggregateFunction.Min) .AddAggregateFunction(x => x.TotalVolume, x => x.Size, EAggregateFunction.Sum) .AddAggregateFunction(x => x.TradeCount, x => x.Timestamp, EAggregateFunction.Count) .AddGroupByColumn(x => x.Ticker) .AddGroupByColumn(x => x.Exchange) .Where("\"price\" > 0 AND \"size\" > 0") .CreateGroupIndexes(true) .MaterializedOnly(false); } } public class Trade { public DateTime Timestamp { get; set; } public string Ticker { get; set; } = string.Empty; public string Exchange { get; set; } = string.Empty; public decimal Price { get; set; } public int Size { get; set; } } public class TradeAggregate { public decimal AveragePrice { get; set; } public decimal MaxPrice { get; set; } public decimal MinPrice { get; set; } public decimal TotalVolume { get; set; } public int TradeCount { get; set; } } ``` -------------------------------- ### Complete Example: Hourly Averages per Sensor Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/query-functions/time-bucket.md Demonstrates a full setup including entity model, DbContext configuration, and a GROUP BY aggregation query using EF.Functions.TimeBucket for hourly averages per sensor over the last 24 hours. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Query; using Microsoft.EntityFrameworkCore; // Entity model public class SensorReading { public Guid Id { get; set; } public DateTime Timestamp { get; set; } public string SensorId { get; set; } = string.Empty; public double Temperature { get; set; } } // DbContext public class AppDbContext(DbContextOptions options) : DbContext(options) { public DbSet SensorReadings => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(entity => { entity.HasKey(x => new { x.Id, x.Timestamp }); entity.IsHypertable(x => x.Timestamp) .WithChunkTimeInterval("1 day"); }); } } // Registration builder.Services.AddDbContext(options => options.UseNpgsql(connectionString).UseTimescaleDb()); // Query: hourly averages per sensor over the last 24 hours DateTime since = DateTime.UtcNow.AddHours(-24); var hourlyAverages = await context.SensorReadings .Where(r => r.Timestamp >= since) .GroupBy(r => new { Bucket = EF.Functions.TimeBucket(TimeSpan.FromHours(1), r.Timestamp), r.SensorId }) .Select(g => new { g.Key.Bucket, g.Key.SensorId, AvgTemperature = g.Average(r => r.Temperature), ReadingCount = g.Count() }) .OrderBy(r => r.Bucket) .ThenBy(r => r.SensorId) .ToListAsync(); ``` -------------------------------- ### Complete Continuous Aggregate Configuration Example Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md A comprehensive example demonstrating the configuration of a continuous aggregate with various attributes, including chunk interval, data population, index creation, materialization, and filtering. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using Microsoft.EntityFrameworkCore; // Source hypertable [Hypertable(nameof(Timestamp), ChunkTimeInterval = "7 days")] [PrimaryKey(nameof(Ticker), nameof(Timestamp))] public class Trade { public DateTime Timestamp { get; set; } public string Ticker { get; set; } = string.Empty; public string Exchange { get; set; } = string.Empty; public decimal Price { get; set; } public int Size { get; set; } } // Continuous aggregate with comprehensive configuration [Keyless] [ContinuousAggregate( MaterializedViewName = "trade_hourly_stats", ParentName = nameof(Trade), ChunkInterval = "30 days", WithNoData = false, CreateGroupIndexes = true, MaterializedOnly = false, Where = "\"price\" > 0 AND \"size\" > 0")] [TimeBucket("1 hour", nameof(Trade.Timestamp), GroupBy = true)] public class TradeHourlyAggregate { [Aggregate(EAggregateFunction.Avg, nameof(Trade.Price))] public decimal AveragePrice { get; set; } [Aggregate(EAggregateFunction.Max, nameof(Trade.Price))] public decimal MaxPrice { get; set; } [Aggregate(EAggregateFunction.Min, nameof(Trade.Price))] public decimal MinPrice { get; set; } [Aggregate(EAggregateFunction.Sum, nameof(Trade.Size))] public decimal TotalVolume { get; set; } [Aggregate(EAggregateFunction.Count, "*")] public int TradeCount { get; set; } [Aggregate(EAggregateFunction.First, nameof(Trade.Price))] public decimal OpeningPrice { get; set; } [Aggregate(EAggregateFunction.Last, nameof(Trade.Price))] public decimal ClosingPrice { get; set; } } ``` -------------------------------- ### Add TimescaleDB Design Package Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/01-dotnet-tools.md Install the design-time package into your startup project using the .NET CLI. ```bash dotnet add package CmdScale.EntityFrameworkCore.TimescaleDB.Design --version 0.2.0 ``` -------------------------------- ### Project Structure Overview Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/samples/Eftdb.Samples.CodeFirst/README.md Illustrates the directory layout for the EF Core Code-First TimescaleDB example. ```text samples/ ├── Eftdb.Samples.CodeFirst/ # Startup project (contains Program.cs and design-time services) ├── Eftdb.Samples.Shared/ # Contains DbContext, entities, and migrations └── Eftdb.Samples.DatabaseFirst/ # Database-first scaffolding example ``` -------------------------------- ### Install Stryker.NET CLI Tool Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/tests/Eftdb.Tests/STRYKER_README.md Installs the Stryker.NET command-line interface globally. This is a prerequisite for running mutation tests. ```bash dotnet tool install -g dotnet-stryker ``` -------------------------------- ### Install ReportGenerator Tool Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Installs the ReportGenerator global tool, used for generating code coverage reports. Install this only once. ```bash dotnet tool install -g dotnet-reportgenerator-globaltool ``` -------------------------------- ### Install CmdScale Entity Framework Core TimescaleDB Packages Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/00-getting-started.md Add the core runtime and design-time support packages to your project using the .NET CLI. ```bash dotnet add package CmdScale.EntityFrameworkCore.TimescaleDB dotnet add package CmdScale.EntityFrameworkCore.TimescaleDB.Design ``` -------------------------------- ### Stryker.NET Example Results Summary Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/tests/Eftdb.Tests/STRYKER_README.md An example of how mutation testing results might be summarized, showing scores for different code modules. ```text All files | 87.5% | 350/400 | 350 | 40 | 10 | 0 ├── Differs | 92.3% | 120/130 | 120 | 8 | 2 | 0 ├── Generators | 85.7% | 180/210 | 180 | 25 | 5 | 0 └── Extractors | 83.3% | 50/60 | 50 | 7 | 3 | 0 ``` -------------------------------- ### Configure Complete Retention Policy with Custom Scheduling Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/retention-policies.md Configure a retention policy with advanced scheduling parameters like `InitialStart`, `ScheduleInterval`, `MaxRuntime`, `MaxRetries`, and `RetryPeriod`. This example sets a policy to drop chunks older than 30 days, starting on a specific date and with custom job scheduling. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy; using Microsoft.EntityFrameworkCore; [Hypertable(nameof(Time), ChunkTimeInterval = "1 day")] [PrimaryKey(nameof(Id), nameof(Time))] [RetentionPolicy("30 days", InitialStart = "2025-10-01T03:00:00Z", ScheduleInterval = "1 day", MaxRuntime = "30 minutes", MaxRetries = 3, RetryPeriod = "5 minutes")] public class ApplicationLog { public Guid Id { get; set; } public DateTime Time { get; set; } public string ServiceName { get; set; } = string.Empty; public string Level { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; } ``` -------------------------------- ### Stryker.NET Configuration: Key Settings Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/tests/Eftdb.Tests/STRYKER_README.md Example JSON snippet showing key configuration settings for Stryker.NET, including project targets, thresholds, and coverage analysis. ```json "baseline": { "enabled": true } ``` -------------------------------- ### Start TimescaleDB Docker Container Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/samples/Eftdb.Samples.DatabaseFirst/README.md This command starts a TimescaleDB container using Docker Compose. Ensure your connection string settings in the scaffold command match the configuration in your docker-compose.yml file. ```bash docker-compose up -d ``` -------------------------------- ### Install EF Core Specification Tests and Testcontainers Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/wiki/Tutorial:-Implementing-EF-Core's-MigrationsInfrastructureTestBase-for-a-Custom-Provider Install necessary NuGet packages for EF Core relational specification tests and Testcontainers. Reference your custom provider's design and main projects. ```xml ``` -------------------------------- ### Set Initial Start Time for Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md Configure the first scheduled run time for the refresh policy using `.WithInitialStart()`. ```csharp builder.IsContinuousAggregate( "trade_hourly_stats", "1 hour", x => x.Timestamp) .AddAggregateFunction(x => x.AveragePrice, x => x.Price, EAggregateFunction.Avg) .WithRefreshPolicy(startOffset: "7 days", endOffset: "1 hour", scheduleInterval: "1 hour") .WithInitialStart(new DateTime(2026, 2, 1, 0, 0, 0, DateTimeKind.Utc)); ``` -------------------------------- ### Configure Reorder Policy - C# Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/reorder-policies.md Configure a reorder policy for a hypertable using the `WithReorderPolicy` extension method. This example sets a policy to run every two days, starting at a specific time, with custom retry parameters. ```csharp public void Configure(EntityTypeBuilder builder) { builder.ToTable("Trades"); builder.HasNoKey().IsHypertable(x => x.Timestamp) // Configure the reorder policy to use the index builder.WithReorderPolicy( "Trades_Timestamp_idx", DateTime.Parse("2025-09-23T09:15:19.3905112Z"), "2 days", "10 minutes", 3, "1 minute" ); } ``` -------------------------------- ### Hypertable Compression OrderBy Syntax Examples Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/hypertable.md Illustrates various ways to specify column ordering for compression using the `CompressionOrderBy` property, including default ascending, descending with nulls last, and mixed directions with explicit null handling. ```csharp // Default ordering (ascending) CompressionOrderBy = new[] { "Time" } ``` ```csharp // Descending with nulls last CompressionOrderBy = new[] { "Time DESC NULLS LAST" } ``` ```csharp // Multiple columns with mixed directions CompressionOrderBy = new[] { "Time DESC", "Voltage ASC NULLS FIRST", "Power DESC NULLS LAST" } ``` -------------------------------- ### Complete Continuous Aggregate with Refresh Policy Example Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md Configures a continuous aggregate for trade data with hourly statistics, including a refresh policy to keep it up-to-date. Ensure the TimescaleDB background worker is enabled for the refresh policy to function. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class TradeAggregateConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); builder.IsContinuousAggregate( "trade_hourly_stats", "1 hour", x => x.Timestamp, timeBucketGroupBy: true, chunkInterval: "7 days") .AddAggregateFunction(x => x.AveragePrice, x => x.Price, EAggregateFunction.Avg) .AddAggregateFunction(x => x.MaxPrice, x => x.Price, EAggregateFunction.Max) .AddAggregateFunction(x => x.MinPrice, x => x.Price, EAggregateFunction.Min) .AddAggregateFunction(x => x.TotalVolume, x => x.Size, EAggregateFunction.Sum) .AddGroupByColumn(x => x.Ticker) .WithRefreshPolicy( startOffset: "30 days", endOffset: "1 hour", scheduleInterval: "1 hour") .WithInitialStart(new DateTime(2026, 2, 1, 0, 0, 0, DateTimeKind.Utc)) .WithIfNotExists(true) .WithBucketsPerBatch(5) .WithMaxBatchesPerExecution(10) .WithRefreshNewestFirst(true); } } public class Trade { public DateTime Timestamp { get; set; } public string Ticker { get; set; } = string.Empty; public decimal Price { get; set; } public int Size { get; set; } } public class TradeAggregate { public decimal AveragePrice { get; set; } public decimal MaxPrice { get; set; } public decimal MinPrice { get; set; } public decimal TotalVolume { get; set; } } ``` -------------------------------- ### Configure Basic Retention Policy with DropAfter Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/retention-policies.md Use the `[RetentionPolicy]` attribute with a duration string to configure a basic retention policy. This example sets the policy to drop chunks older than 30 days. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy; using Microsoft.EntityFrameworkCore; [Hypertable(nameof(Time), ChunkTimeInterval = "1 day")] [PrimaryKey(nameof(Id), nameof(Time))] [RetentionPolicy("30 days")] public class ApplicationLog { public Guid Id { get; set; } public DateTime Time { get; set; } public string ServiceName { get; set; } = string.Empty; public string Level { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; } ``` -------------------------------- ### Configure Basic Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md Use the `.WithRefreshPolicy()` method to add an automatic refresh policy to a continuous aggregate. This example sets the refresh window and schedule interval. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class TradeAggregateConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); builder.IsContinuousAggregate( "trade_hourly_stats", "1 hour", x => x.Timestamp, timeBucketGroupBy: true, chunkInterval: "7 days") .AddAggregateFunction(x => x.AveragePrice, x => x.Price, EAggregateFunction.Avg) .WithRefreshPolicy( startOffset: "7 days", // Refresh data from the last 7 days endOffset: "1 hour", // Exclude the most recent hour (still incoming) scheduleInterval: "1 hour"); // Run refresh every hour } } ``` -------------------------------- ### Complete Hypertable Compression Configuration Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/hypertable.md A comprehensive example of hypertable configuration including chunk time interval, compression, segment-by columns, and order-by clauses for optimized data storage and retrieval. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using Microsoft.EntityFrameworkCore; [Hypertable(nameof(Time), ChunkTimeInterval = "1 day", EnableCompression = true, CompressionSegmentBy = new[] { "DeviceId", "TenantId" }, CompressionOrderBy = new[] { "Time DESC", "Voltage ASC" })] [PrimaryKey(nameof(Id), nameof(Time))] public class DeviceReading { public Guid Id { get; set; } public DateTime Time { get; set; } public string DeviceId { get; set; } = string.Empty; public string TenantId { get; set; } = string.Empty; public double Voltage { get; set; } public double Power { get; set; } } ``` -------------------------------- ### Implement TimescaleMigrationsFixture for Testcontainer Management Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/wiki/Tutorial:-Implementing-EF-Core's-MigrationsInfrastructureTestBase-for-a-Custom-Provider This xUnit Class Fixture manages the test database lifecycle using Testcontainers. It configures and starts a PostgreSQL container with the TimescaleDB image, sets the connection string, and ensures the container is stopped after tests. ```csharp using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.TestUtilities; using Testcontainers.PostgreSql; using Xunit; namespace CmdScale.EntityFrameworkCore.TimescaleDB.FunctionalTests.Utils; public class TimescaleMigrationsFixture : MigrationsInfrastructureFixtureBase, IAsyncLifetime { // Configure the Testcontainer for your specific database private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder() .WithImage("timescale/timescaledb:latest-pg17") .WithDatabase("migration_tests_db") .WithUsername("test_user") .WithPassword("test_password") .Build(); public string ConnectionString => _dbContainer.GetConnectionString(); // Start the container before tests run public override async Task InitializeAsync() { await _dbContainer.StartAsync(); TimescaleTestStoreFactory.ConnectionString = ConnectionString; await base.InitializeAsync(); } // Stop the container after tests finish public override async Task DisposeAsync() { await base.DisposeAsync(); await _dbContainer.StopAsync(); } protected override ITestStoreFactory TestStoreFactory => TimescaleTestStoreFactory.Instance; } ``` -------------------------------- ### Configure Complete Retention Policy with Advanced Options Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/retention-policies.md Configures a retention policy using `dropAfter` with advanced options including initial start time, schedule interval, maximum runtime, and retry parameters. Note that customizing job scheduling for `dropCreatedBefore` requires TimescaleDB 2.26.3 or later. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class ApplicationLogConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => new { x.Id, x.Time }); builder.IsHypertable(x => x.Time) .WithChunkTimeInterval("1 day"); builder.WithRetentionPolicy( dropAfter: "30 days", initialStart: new DateTime(2025, 10, 1, 3, 0, 0, DateTimeKind.Utc), scheduleInterval: "1 day", maxRuntime: "30 minutes", maxRetries: 3, retryPeriod: "5 minutes"); } } public class ApplicationLog { public Guid Id { get; set; } public DateTime Time { get; set; } public string ServiceName { get; set; } = string.Empty; public string Level { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; } ``` -------------------------------- ### Configure Basic Continuous Aggregate Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Apply the [ContinuousAggregatePolicy] attribute to a class to define automatic refresh settings for a continuous aggregate. This example sets the refresh window and schedule interval. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy; using Microsoft.EntityFrameworkCore; [Keyless] [ContinuousAggregate( MaterializedViewName = "trade_hourly_stats", ParentName = nameof(Trade))] [TimeBucket("1 hour", nameof(Trade.Timestamp))] [ContinuousAggregatePolicy( StartOffset = "7 days", EndOffset = "1 hour", ScheduleInterval = "1 hour")] public class TradeAggregate { [Aggregate(EAggregateFunction.Avg, nameof(Trade.Price))] public decimal AveragePrice { get; set; } } public class Trade { public DateTime Timestamp { get; set; } public decimal Price { get; set; } public string Ticker { get; set; } = string.Empty; } ``` -------------------------------- ### Configure Retention Policy with DropCreatedBefore Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/retention-policies.md Use the `[RetentionPolicy]` attribute with the `dropCreatedBefore` named argument to configure a policy that drops chunks based on their creation time. This example drops chunks created more than 30 days ago. ```csharp [Hypertable(nameof(Time), ChunkTimeInterval = "1 day")] [PrimaryKey(nameof(Id), nameof(Time))] [RetentionPolicy(dropCreatedBefore: "30 days")] public class ApiRequestLog { public Guid Id { get; set; } public DateTime Time { get; set; } public string Path { get; set; } = string.Empty; public int StatusCode { get; set; } } ``` -------------------------------- ### Grouping Data by Expression Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md Demonstrates grouping data in a continuous aggregate using a raw SQL expression. This is useful for complex grouping scenarios where standard column mapping is insufficient. The example groups by the first two columns in the SELECT list. ```csharp public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); builder.IsContinuousAggregate( "trade_hourly_stats", "1 hour", x => x.Timestamp) .AddAggregateFunction(x => x.AveragePrice, x => x.Price, EAggregateFunction.Avg) // Group by ordinal positions in SELECT list .AddGroupByColumn("1, 2"); } ``` -------------------------------- ### TimeBucket with Timezone for DateTimeOffset Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/query-functions/time-bucket.md For DateTimeOffset columns, specify a timezone name to align bucket boundaries to local time zone rules, including daylight saving time transitions. This example groups events by hourly buckets in the 'Europe/Berlin' timezone. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Query; using Microsoft.EntityFrameworkCore; var results = await context.Events .GroupBy(e => EF.Functions.TimeBucket( TimeSpan.FromHours(1), e.Timestamp, "Europe/Berlin")) .Select(g => new { Bucket = g.Key, Count = g.Count() }) .ToListAsync(); ``` -------------------------------- ### Run Benchmark via Command Line Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/benchmarks/Eftdb.Benchmarks/README.md Execute the benchmark project from the command line. Ensure the build configuration is set to 'Release' for accurate performance measurements. ```bash dotnet run --project CmdScale.EntityFrameworkCore.TimescaleDB.Benchmarks -c Release ``` -------------------------------- ### Configure Time Bucketing for Daily Sensor Aggregates Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Sets up daily time bucketing for sensor readings, grouping data by day and calculating the average temperature. The `GroupBy` property defaults to `true`. ```csharp [Keyless] [ContinuousAggregate( MaterializedViewName = "sensor_daily_stats", ParentName = nameof(SensorReading))] [TimeBucket("1 day", nameof(SensorReading.Timestamp), GroupBy = true)] public class SensorDailyAggregate { [Aggregate(EAggregateFunction.Avg, nameof(SensorReading.Temperature))] public double AverageTemperature { get; set; } } ``` -------------------------------- ### Specify Materialized View Name Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Example of setting the `MaterializedViewName` property for a continuous aggregate. ```csharp [ContinuousAggregate(MaterializedViewName = "hourly_metrics", ParentName = nameof(Metric))] ``` -------------------------------- ### Enable Baseline Mode Configuration Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/tests/Eftdb.Tests/STRYKER_README.md Enable baseline mode by setting 'enabled' to true and specifying a 'provider'. This configuration speeds up subsequent runs by comparing against a baseline. ```json "baseline": { "enabled": true, "provider": "disk" } ``` -------------------------------- ### Configure InitialStart for Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Set the `InitialStart` property to specify the exact UTC date-time for the first scheduled run of the policy job. If omitted, the first run is based on the `ScheduleInterval`. ```csharp [ContinuousAggregatePolicy( StartOffset = "7 days", EndOffset = "1 hour", ScheduleInterval = "1 hour", InitialStart = "2026-02-01T00:00:00Z")] ``` -------------------------------- ### Specify Parent Hypertable Name Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Example of setting the `ParentName` property to link a continuous aggregate to its source hypertable. ```csharp [ContinuousAggregate(MaterializedViewName = "trade_stats", ParentName = nameof(Trade))] ``` -------------------------------- ### Stryker.NET Configuration: Test Case Filter Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/tests/Eftdb.Tests/STRYKER_README.md Filters which tests are run during mutation testing. This example excludes integration tests. ```json "test-case-filter": "Category!=Integration" ``` -------------------------------- ### WHERE Filtering with TimeBucket Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/query-functions/time-bucket.md Filter rows based on their computed bucket boundary. This example filters metrics where the time bucket is after a specific threshold. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Query; using Microsoft.EntityFrameworkCore; TimeSpan bucket = TimeSpan.FromHours(1); DateTime threshold = new(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc); List recent = await context.Metrics .Where(m => EF.Functions.TimeBucket(bucket, m.Timestamp) >= threshold) .ToListAsync(); ``` -------------------------------- ### Switch to NuGet Package References with PowerShell Script Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Uses a PowerShell script to switch .csproj files to reference local NuGet packages. Use '-Mode Package' to simulate consumer usage. ```powershell .\scripts\Switch-References.ps1 -Mode Package ``` -------------------------------- ### Run All Tests with Dotnet CLI Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Execute all tests within the project using the dotnet test command. ```bash dotnet test ``` -------------------------------- ### Configure Hypertable with Data Migration Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/hypertable.md Set up a hypertable to migrate existing data when converting a table. This ensures all historical rows are properly partitioned into chunks according to the hypertable configuration. Use with caution on large datasets. ```csharp public class WeatherDataConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => new { x.Id, x.Time }); // Enable migration of existing data when converting to a hypertable builder.IsHypertable(x => x.Time) .WithChunkTimeInterval("1 day") .WithMigrateData(true); } } ``` -------------------------------- ### Scaffold DbContext and Models Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/samples/Eftdb.Samples.DatabaseFirst/README.md Use this command to generate EF Core models and DbContext from a TimescaleDB database. Ensure the connection string and project path are correctly configured. The `--schema` flag is important to avoid including internal TimescaleDB tables. ```bash dotnet ef dbcontext scaffold \ "Host=localhost;Database=cmdscale-ef-timescaledb;Username=timescale_admin;Password=R#!kro#GP43ra8Ae" \ CmdScale.EntityFrameworkCore.TimescaleDB.Design \ --output-dir Models \ --schema public \ --context-dir . \ --context MyTimescaleDbContext \ --project samples/Eftdb.Samples.DatabaseFirst ``` -------------------------------- ### Generate EF Core Migrations Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/01-dotnet-tools.md Run the standard migrations add command from your project's root directory. The tooling will automatically generate TimescaleDB-specific SQL for hypertable configurations, compression, and policies. ```bash dotnet ef migrations add --project --startup-project ``` -------------------------------- ### Generated Migration Up and Down Methods for Chunk Time Interval Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/01-dotnet-tools.md Demonstrates how the tool generates both 'Up' and 'Down' methods for modifying chunk time intervals, ensuring proper rollback capabilities. ```csharp protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.Sql(@" SELECT set_chunk_time_interval('""TradesWithId""', INTERVAL '7 days'); "); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.Sql(@" SELECT set_chunk_time_interval('""TradesWithId""', INTERVAL '2 day'); "); } ``` -------------------------------- ### Configure Reorder Policy with Entity Framework Core Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/reorder-policies.md Use the `ReorderPolicy` attribute to define a reorder policy for a hypertable. Specify the index name and configure parameters like initial start time, schedule interval, maximum runtime, and retry settings. This policy is applied to the `DeviceReading` hypertable, reordering data based on the `DeviceReadings_Time_idx` index. ```csharp [Hypertable(nameof(Time))] [ReorderPolicy("DeviceReadings_Time_idx", InitialStart = "2025-09-23T09:15:19.3905112Z", ScheduleInterval = "1 day", MaxRuntime = "00:00:00", RetryPeriod = "00:05:00", MaxRetries = 3)] [PrimaryKey(nameof(Id), nameof(Time))] public class DeviceReading { public Guid Id { get; set; } public DateTime Time { get; set; } public string DeviceId { get; set; } = string.Empty; public double Voltage { get; set; } public double Power { get; set; } } ``` -------------------------------- ### Add Local NuGet Source via Dotnet CLI Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Adds a local folder as a NuGet source using the .NET CLI, naming it 'LocalCmdScale'. ```bash dotnet nuget add source "path\NuGet-Packages" --name LocalCmdScale ``` -------------------------------- ### Add New EF Core Migration Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/samples/Eftdb.Samples.CodeFirst/README.md Use this command to create a new migration file for your EF Core model. Ensure you specify the correct project paths. ```bash dotnet ef migrations add --project samples/Eftdb.Samples.Shared --startup-project samples/Eftdb.Samples.CodeFirst ``` -------------------------------- ### Generated Migration Up Method with TimescaleDB Configuration Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/01-dotnet-tools.md The generated 'Up' method includes standard table creation followed by SQL statements to configure the hypertable and its features in TimescaleDB. ```csharp protected override void Up(MigrationBuilder migrationBuilder) { // Standard EF Core table creation migrationBuilder.CreateTable( name: "DeviceReadings", columns: table => new { Id = table.Column(type: "uuid", nullable: false), Time = table.Column(type: "timestamp with time zone", nullable: false), DeviceId = table.Column(type: "text", nullable: false), Voltage = table.Column(type: "double precision", nullable: false), Power = table.Column(type: "double precision", nullable: false) }, constraints: table => { table.PrimaryKey("PK_DeviceReadings", x => new { x.Id, x.Time }); }); // SQL generated by CmdScale.EntityFrameworkCore.TimescaleDB.Design migrationBuilder.Sql(@" SELECT create_hypertable('""DeviceReadings""', 'Time'); SELECT set_chunk_time_interval('""DeviceReadings""', INTERVAL '1 day'); ALTER TABLE ""DeviceReadings"" SET (timescaledb.compress = true); SET timescaledb.enable_chunk_skipping = 'ON'; SELECT enable_chunk_skipping('""DeviceReadings""', 'Time'); "); } ``` -------------------------------- ### Configure StartOffset for Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Set the `StartOffset` property to define the beginning of the data window for refresh, relative to the execution time. An empty string or null uses the earliest available data. ```csharp [ContinuousAggregatePolicy( StartOffset = "1 month", EndOffset = "1 hour", ScheduleInterval = "1 hour")] ``` -------------------------------- ### TimescaleDB Test Store Implementation Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/wiki/Tutorial:-Implementing-EF-Core's-MigrationsInfrastructureTestBase-for-a-Custom-Provider Defines the `TimescaleTestStore` class, inheriting from `RelationalTestStore`. It handles connection string management, test store initialization, and custom DbContext options for TimescaleDB. ```csharp using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.TestUtilities; using Npgsql; using System.Collections.Concurrent; using System.Data.Common; namespace CmdScale.EntityFrameworkCore.TimescaleDB.FunctionalTests.Utils; public class TimescaleTestStore : RelationalTestStore { private static readonly ConcurrentDictionary _sharedStores = new(); protected override DbConnection Connection { get => base.Connection; set => base.Connection = value; } public override string ConnectionString { get => base.ConnectionString; protected set => base.ConnectionString = value; } private TimescaleTestStore(string name, bool shared, string connectionString) : base(name, shared) { ConnectionString = connectionString; Connection = new NpgsqlConnection(ConnectionString); } public static TimescaleTestStore Create(string name, string connectionString) => new(name, shared: false, connectionString); public static TimescaleTestStore GetOrCreateShared(string name, string connectionString) => _sharedStores.GetOrAdd(name, _ => { TimescaleTestStore store = new(name, shared: true, connectionString); DbContextOptions options = new DbContextOptionsBuilder() .UseNpgsql(connectionString).UseTimescaleDb().Options; store.Initialize(null, () => new MigrationsInfrastructureFixtureBase.MigrationsContext(options), null); return store; }); public override DbContextOptionsBuilder AddProviderOptions(DbContextOptionsBuilder builder) => builder.AddInterceptors(new TimescaleMigrationsTestInterceptor()).UseNpgsql(ConnectionString, options => { }).UseTimescaleDb().EnableSensitiveDataLogging(); public override void Clean(DbContext context) { context.Database.EnsureClean(); } } ``` -------------------------------- ### Run Stryker.NET Mutation Tests Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Executes mutation tests from the test directory. Use the --since flag for a quicker run on changed files. ```bash # Run from the test directory cd tests/Eftdb.Tests dotnet stryker # Quick run (test only changed files) dotnet stryker --since ``` -------------------------------- ### Run Stryker.NET Mutation Testing Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/tests/Eftdb.Tests/STRYKER_README.md Commands to execute Stryker.NET mutation tests. Use '--since' for faster runs on changed files, or '--mutate' to target specific files. ```bash # Full mutation run (can take 30-60 minutes) dotnet stryker # Quick run for development (tests only changed files since last commit) dotnet stryker --since # Run on specific files only dotnet stryker --mutate "**/HypertableDiffer.cs" ``` -------------------------------- ### Generate HTML Coverage Report Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Runs tests with coverage collection and then generates an HTML report from the collected coverage data. ```bash # Run tests with coverage collection dotnet test tests/Eftdb.Tests --settings tests/Eftdb.Tests/coverlet.runsettings --collect:"XPlat Code Coverage" # Generate HTML report from coverage files reportgenerator -reports:"tests/Eftdb.Tests/TestResults/**/coverage.cobertura.xml" -targetdir:"tests/Eftdb.Tests/TestResults/CoverageReport" -reporttypes:Html ``` -------------------------------- ### Switch Project References with PowerShell Script Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Uses a PowerShell script to switch project references in .csproj files. Use '-Mode Project' for direct project references. ```powershell .\scripts\Switch-References.ps1 -Mode Project ``` -------------------------------- ### Create Basic Continuous Aggregate Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Defines a continuous aggregate for hourly trade statistics. Continuous aggregate entities must be marked with `[Keyless]`. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using Microsoft.EntityFrameworkCore; [Keyless] [ContinuousAggregate( MaterializedViewName = "trade_hourly_stats", ParentName = nameof(Trade))] [TimeBucket("1 hour", nameof(Trade.Timestamp))] public class TradeAggregate { [Aggregate(EAggregateFunction.Avg, nameof(Trade.Price))] public decimal AveragePrice { get; set; } } public class Trade { public DateTime Timestamp { get; set; } public decimal Price { get; set; } public string Ticker { get; set; } = string.Empty; } ``` -------------------------------- ### Configure Hypertable with Compression Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/hypertable.md Use this configuration to set up a hypertable with advanced compression settings. It specifies the time interval for chunks, columns for segmenting compressed data, and ordering for compressed data. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class DeviceReadingConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => new { x.Id, x.Time }); builder.IsHypertable(x => x.Time) .WithChunkTimeInterval("1 day") .WithCompressionSegmentBy(x => x.DeviceId, x => x.TenantId) .WithCompressionOrderBy( OrderByBuilder.For(x => x.Time).Descending(), OrderByBuilder.For(x => x.Voltage).Ascending()); } } public class DeviceReading { public Guid Id { get; set; } public DateTime Time { get; set; } public string DeviceId { get; set; } = string.Empty; public string TenantId { get; set; } = string.Empty; public double Voltage { get; set; } public double Power { get; set; } } ``` -------------------------------- ### Basic Continuous Aggregate Configuration Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md Defines a continuous aggregate named 'trade_hourly_stats' that groups trades into 1-hour buckets. It calculates the average price from the 'Price' column and includes the time bucket in the GROUP BY clause. The 'chunkInterval' is set to '7 days'. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class TradeAggregateConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); // Create a continuous aggregate that groups trades into 1-hour buckets builder.IsContinuousAggregate( "trade_hourly_stats", // Materialized view name "1 hour", // Time bucket width x => x.Timestamp, // Source time column timeBucketGroupBy: true, // Include time bucket in GROUP BY chunkInterval: "7 days") // Chunk interval for aggregate data .AddAggregateFunction( x => x.AveragePrice, // Aggregate entity property x => x.Price, // Source entity column EAggregateFunction.Avg); } } public class TradeAggregate { public decimal AveragePrice { get; set; } } public class Trade { public DateTime Timestamp { get; set; } public decimal Price { get; set; } public string Ticker { get; set; } = string.Empty; } ``` -------------------------------- ### Adding Multiple Aggregate Functions Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/continuous-aggregates.md Configures a continuous aggregate to include several aggregation functions: average price, maximum price, minimum price, total volume (sum of size), and trade count. This allows for comprehensive analysis of trade data within time buckets. ```csharp public void Configure(EntityTypeBuilder builder) { builder.HasNoKey(); builder.IsContinuousAggregate( "trade_hourly_stats", "1 hour", x => x.Timestamp) .AddAggregateFunction(x => x.AveragePrice, x => x.Price, EAggregateFunction.Avg) .AddAggregateFunction(x => x.MaxPrice, x => x.Price, EAggregateFunction.Max) .AddAggregateFunction(x => x.MinPrice, x => x.Price, EAggregateFunction.Min) .AddAggregateFunction(x => x.TotalVolume, x => x.Size, EAggregateFunction.Sum) .AddAggregateFunction(x => x.TradeCount, x => x.Timestamp, EAggregateFunction.Count); } ``` -------------------------------- ### Configure Hypertable using Data Annotations Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/README.md Use the `[Hypertable]` attribute on your model class for simpler hypertable configurations. Specify partitioning columns, chunk skipping, chunk interval, and data migration settings. ```csharp [Hypertable(nameof(Time), ChunkSkipColumns = new[] { "Time" }, ChunkTimeInterval = "86400000", MigrateData = true)] [PrimaryKey(nameof(Id), nameof(Time))] public class DeviceReading { public Guid Id { get; set; } public DateTime Time { get; set; } public string DeviceId { get; set; } = string.Empty; public double Voltage { get; set; } public double Power { get; set; } } ``` -------------------------------- ### Configure Basic Retention Policy (dropAfter) Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/retention-policies.md Sets a retention policy to drop chunks older than 30 days, with the job scheduled to run daily. This is the standard mode for managing time-series data lifecycle. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; public class ApplicationLogConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => new { x.Id, x.Time }); builder.IsHypertable(x => x.Time) .WithChunkTimeInterval("1 day"); // Drop chunks older than 30 days, running the job daily builder.WithRetentionPolicy( dropAfter: "30 days", scheduleInterval: "1 day"); } } ``` -------------------------------- ### Configure RefreshNewestFirst for Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Set `RefreshNewestFirst` to `true` to refresh the newest data first, or `false` to refresh the oldest data first. Defaults to `true`. ```csharp [ContinuousAggregatePolicy( StartOffset = "7 days", EndOffset = "1 hour", ScheduleInterval = "1 hour", RefreshNewestFirst = true)] ``` -------------------------------- ### Create a Basic Hypertable Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/hypertable.md Use the `Hypertable` attribute to convert an entity into a hypertable. Specify the time column and optionally customize the chunk time interval. ```csharp [Hypertable(nameof(Time), ChunkTimeInterval = "1 day")] [PrimaryKey(nameof(Id), nameof(Time))] public class DeviceReading { public Guid Id { get; set; } public DateTime Time { get; set; } public string DeviceId { get; set; } = string.Empty; public double Voltage { get; set; } public double Power { get; set; } } ``` -------------------------------- ### Define Multiple Aggregations for Continuous Aggregate Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Configures a continuous aggregate to calculate average price, max price, min price, total volume, and trade count per hour. For `COUNT(*)`, use the wildcard `"*"` as the source column. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions; using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate; using Microsoft.EntityFrameworkCore; [Keyless] [ContinuousAggregate( MaterializedViewName = "trade_hourly_stats", ParentName = nameof(Trade))] [TimeBucket("1 hour", nameof(Trade.Timestamp))] public class TradeAggregate { [Aggregate(EAggregateFunction.Avg, nameof(Trade.Price))] public decimal AveragePrice { get; set; } [Aggregate(EAggregateFunction.Max, nameof(Trade.Price))] public decimal MaxPrice { get; set; } [Aggregate(EAggregateFunction.Min, nameof(Trade.Price))] public decimal MinPrice { get; set; } [Aggregate(EAggregateFunction.Sum, nameof(Trade.Size))] public decimal TotalVolume { get; set; } [Aggregate(EAggregateFunction.Count, "*")] public int TradeCount { get; set; } } ``` -------------------------------- ### Configure IfNotExists for Refresh Policy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/continuous-aggregates.md Set `IfNotExists` to `true` to prevent errors if the policy job already exists, issuing a notice instead. Defaults to `false`. ```csharp [ContinuousAggregatePolicy( StartOffset = "7 days", EndOffset = "1 hour", ScheduleInterval = "1 hour", IfNotExists = true)] ``` -------------------------------- ### Configure Hypertable Compression OrderBy using String Extensions Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/fluent-api/hypertable.md Specify columns and their order for data within compressed segments using string extension methods for column names. This optimizes compression ratios and range scan query performance. Using this method automatically enables compression. ```csharp builder.IsHypertable(x => x.Time) .WithCompressionSegmentBy(x => x.DeviceId) .WithCompressionOrderBy( "Time".Descending(), "Voltage".Ascending(nullsFirst: true)); ``` -------------------------------- ### Configure DbContext with TimescaleDB Support Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/00-getting-started.md Enable TimescaleDB support in your DbContext by chaining the .UseTimescaleDb() method after configuring the Npgsql provider. ```csharp string? connectionString = builder.Configuration.GetConnectionString("Timescale"); builder.Services.AddDbContext(options => options.UseNpgsql(connectionString).UseTimescaleDb()); ``` -------------------------------- ### Optimize Compression with SegmentBy Source: https://github.com/cmdscale/cmdscale.entityframeworkcore.timescaledb/blob/main/docs/data-annotations/hypertable.md Use `CompressionSegmentBy` to specify columns for optimizing compression. This maps to TimescaleDB's `timescaledb.compress_segmentby` setting and improves query performance by decompressing only relevant segments. ```csharp using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.Hypertable; using Microsoft.EntityFrameworkCore; [Hypertable(nameof(Time), EnableCompression = true, CompressionSegmentBy = new[] { "DeviceId" })] [PrimaryKey(nameof(Id), nameof(Time))] public class DeviceReading { public Guid Id { get; set; } public DateTime Time { get; set; } public string DeviceId { get; set; } = string.Empty; public double Voltage { get; set; } public double Power { get; set; } } ```