Skip to content

Commit ffb5c65

Browse files
implements example for the continuous aggregate policies
1 parent 2aac8d6 commit ffb5c65

6 files changed

Lines changed: 57 additions & 31 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ samples/Eftdb.Samples.DatabaseFirst/**/*.cs
436436
# AI
437437
CLAUDE.md
438438
.claude
439+
tmpclaude-*
439440

440441
# Code coverage report
441442
coverage
Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
# EF Core Database-First Example with TimescaleDB
1+
# EF Core Database-First Example with TimescaleDB
22

33
This project demonstrates how to use the **Database-First** approach with [TimescaleDB](https://www.timescale.com/) using the `CmdScale.EntityFrameworkCore.TimescaleDB` package.
44

55
---
66

7-
## 📦 Required NuGet Packages
7+
## Required NuGet Packages
88

99
Ensure the following package is installed in your project:
1010

1111
- `CmdScale.EntityFrameworkCore.TimescaleDB.Design`
1212

1313
---
1414

15-
## 🛠️ Scaffold DbContext and Models
15+
## Scaffold DbContext and Models
1616

1717
Use the following command to scaffold the `DbContext` and entity classes from an existing TimescaleDB database:
1818

1919
```bash
20-
dotnet ef dbcontext scaffold
21-
"Host=localhost;Database=cmdscale-ef-timescaledb;Username=timescale_admin;Password=R#!kro#GP43ra8Ae"
22-
CmdScale.EntityFrameworkCore.TimescaleDB.Design
23-
--output-dir Models
24-
--schema public
25-
--context-dir .
26-
--context MyTimescaleDbContext
27-
--project CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.DbFirst
20+
dotnet ef dbcontext scaffold \
21+
"Host=localhost;Database=cmdscale-ef-timescaledb;Username=timescale_admin;Password=R#!kro#GP43ra8Ae" \
22+
CmdScale.EntityFrameworkCore.TimescaleDB.Design \
23+
--output-dir Models \
24+
--schema public \
25+
--context-dir . \
26+
--context MyTimescaleDbContext \
27+
--project samples/Eftdb.Samples.DatabaseFirst
2828
```
2929

3030
This command will:
@@ -37,20 +37,20 @@ This command will:
3737
3838
---
3939

40-
## 📁 Project Structure
40+
## Project Structure
4141

4242
```text
43-
CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.DbFirst/
44-
45-
├── Models/ # Auto-generated entity models
46-
└── MyTimescaleDbContext.cs # Auto-generated DbContext
43+
samples/Eftdb.Samples.DatabaseFirst/
44+
|
45+
+-- Models/ # Auto-generated entity models
46+
+-- MyTimescaleDbContext.cs # Auto-generated DbContext
4747
```
4848

4949
---
5050

51-
## 🐳 Docker
51+
## Docker
5252

53-
- A `docker-compose.yml` file is available in the **Solution Items** to spin up a TimescaleDB container for local development:
53+
- A `docker-compose.yml` file is available at the repository root to spin up a TimescaleDB container for local development:
5454

5555
```bash
5656
docker-compose up -d
@@ -60,7 +60,7 @@ CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.DbFirst/
6060

6161
---
6262

63-
## 📚 Resources
63+
## Resources
6464

6565
- [Entity Framework Core Documentation](https://learn.microsoft.com/en-us/ef/core/)
6666
- [TimescaleDB Documentation](https://docs.timescale.com/)

samples/Eftdb.Samples.Shared/Configurations/TradeAggregateConfiguration.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions;
22
using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate;
3+
using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy;
34
using CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.Models;
45
using Microsoft.EntityFrameworkCore;
56
using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -18,7 +19,10 @@ public void Configure(EntityTypeBuilder<TradeAggregate> builder)
1819
.AddGroupByColumn(x => x.Exchange)
1920
.AddGroupByColumn("1, 2")
2021
.Where("\"ticker\" = 'MCRS'")
21-
.MaterializedOnly();
22+
.MaterializedOnly()
23+
.WithRefreshPolicy(startOffset: "7 days", endOffset: "1 hour", scheduleInterval: "1 hour")
24+
.WithTimezone("UTC")
25+
.WithRefreshNewestFirst(true);
2226
}
2327
}
2428
}

samples/Eftdb.Samples.Shared/Models/WeatherAggregate.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CmdScale.EntityFrameworkCore.TimescaleDB.Abstractions;
22
using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregate;
3+
using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.ContinuousAggregatePolicy;
34
using Microsoft.EntityFrameworkCore;
45

56
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.Models
@@ -18,6 +19,12 @@ namespace CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.Models
1819
MaterializedOnly = false,
1920
Where = "\"temperature\" > -50 AND \"humidity\" >= 0")]
2021
[TimeBucket("1 day", nameof(WeatherData.Time), GroupBy = true)]
22+
[ContinuousAggregatePolicy(
23+
StartOffset = "30 days",
24+
EndOffset = "1 day",
25+
ScheduleInterval = "1 hour",
26+
Timezone = "UTC",
27+
RefreshNewestFirst = true)]
2128
public class WeatherAggregate
2229
{
2330
// Avg aggregate function

src/Eftdb.Design/Scaffolding/ContinuousAggregatePolicyScaffoldingExtractor.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,17 @@ public sealed record ContinuousAggregatePolicyInfo(
3636
using (DbCommand command = connection.CreateCommand())
3737
{
3838
// Query continuous aggregate policies from TimescaleDB jobs table
39-
// The config column contains JSONB with start_offset, end_offset, and other policy parameters
39+
// The config column contains JSONB with start_offset, end_offset, timezone, mat_hypertable_id and other policy parameters
4040
command.CommandText = @"
4141
SELECT
42-
ca.view_schema,
43-
ca.view_name,
42+
ca.user_view_schema,
43+
ca.user_view_name,
4444
j.config,
4545
j.schedule_interval::text,
46-
j.initial_start,
47-
j.timezone
46+
j.initial_start
4847
FROM timescaledb_information.jobs j
49-
INNER JOIN timescaledb_information.continuous_aggregates ca
50-
ON j.hypertable_schema = ca.materialization_hypertable_schema
51-
AND j.hypertable_name = ca.materialization_hypertable_name
48+
INNER JOIN _timescaledb_catalog.continuous_agg ca
49+
ON (j.config->>'mat_hypertable_id')::integer = ca.mat_hypertable_id
5250
WHERE j.proc_name = 'policy_refresh_continuous_aggregate';";
5351

5452
using DbDataReader reader = command.ExecuteReader();
@@ -59,11 +57,11 @@ INNER JOIN timescaledb_information.continuous_aggregates ca
5957
string? configJson = reader.IsDBNull(2) ? null : reader.GetString(2);
6058
string? scheduleInterval = reader.IsDBNull(3) ? null : reader.GetString(3);
6159
DateTime? initialStart = reader.IsDBNull(4) ? null : reader.GetDateTime(4);
62-
string? timezone = reader.IsDBNull(5) ? null : reader.GetString(5);
6360

6461
// Parse the JSONB config to extract policy parameters
6562
string? startOffset = null;
6663
string? endOffset = null;
64+
string? timezone = null;
6765
bool? includeTieredData = null;
6866
int? bucketsPerBatch = null;
6967
int? maxBatchesPerExecution = null;
@@ -86,6 +84,13 @@ INNER JOIN timescaledb_information.continuous_aggregates ca
8684
endOffset = ParseIntervalOrInteger(endOffsetElement);
8785
}
8886

87+
// Extract timezone
88+
if (root.TryGetProperty("timezone", out JsonElement timezoneElement)
89+
&& timezoneElement.ValueKind == JsonValueKind.String)
90+
{
91+
timezone = timezoneElement.GetString();
92+
}
93+
8994
// Extract include_tiered_data (optional)
9095
if (root.TryGetProperty("include_tiered_data", out JsonElement includeTieredDataElement)
9196
&& (includeTieredDataElement.ValueKind == JsonValueKind.True || includeTieredDataElement.ValueKind == JsonValueKind.False))

src/Eftdb/Internals/Features/ContinuousAggregatePolicies/ContinuousAggregatePolicyModelExtractor.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,17 @@ public static IEnumerable<AddContinuousAggregatePolicyOperation> GetContinuousAg
3939
continue;
4040
}
4141

42-
// Get the schema (use the entity's schema or default)
43-
string schema = entityType.GetSchema() ?? DefaultValues.DefaultSchema;
42+
// Get the parent (source) entity to determine the schema
43+
string? parentModelName = entityType.FindAnnotation(ContinuousAggregateAnnotations.ParentName)?.Value as string;
44+
IEntityType? parentEntityType = null;
45+
if (!string.IsNullOrWhiteSpace(parentModelName))
46+
{
47+
parentEntityType = relationalModel.Model.GetEntityTypes()
48+
.FirstOrDefault(e => e.ClrType?.Name == parentModelName || e.ShortName() == parentModelName);
49+
}
50+
51+
// Use parent table's schema for the continuous aggregate (matching ContinuousAggregateModelExtractor behavior)
52+
string schema = parentEntityType?.GetSchema() ?? entityType.GetSchema() ?? DefaultValues.DefaultSchema;
4453

4554
// Extract policy configuration from annotations
4655
string? startOffset = entityType.FindAnnotation(ContinuousAggregatePolicyAnnotations.StartOffset)?.Value as string;

0 commit comments

Comments
 (0)