Skip to content

Commit 18521c4

Browse files
Merge pull request #24 from cmdscale/feature/#16_migrate_data_support
Feature/#16 migrate data support
2 parents f72b4fc + e5c7d30 commit 18521c4

19 files changed

+1418
-40
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,10 @@ FodyWeavers.xsd
428428
*.msp
429429

430430
# Ignore all generated migration files
431-
CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess/Migrations/
431+
samples/Eftdb.Samples.Shared/Migrations/
432432

433433
# Ignore all scaffolded models and the DbContext from the DbFirst project
434-
CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess.DbFirst/
434+
samples/Eftdb.Samples.DatabaseFirst/**/*.cs
435435

436436
# AI
437437
CLAUDE.md

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Seamlessly define and manage **TimescaleDB hypertables** using standard EF Core
2424
- **Time Partitioning**: Easily specify the primary time column and set the `chunk_time_interval`.
2525
- **Space Partitioning**: Add additional dimensions for hash or range partitioning to further optimize queries.
2626
- **Chunk Time Interval**: Configure chunk intervals to balance performance and storage efficiency.
27+
- **Data Migration**: Control whether existing data should be migrated when converting a regular table to a hypertable using `migrate_data`.
2728
- **Compression & Chunk Skipping**: Enable TimescaleDB's native compression and configure chunk skipping to improve query performance.
2829

2930
### Reorder Policies
@@ -97,7 +98,9 @@ public class WeatherDataConfiguration : IEntityTypeConfiguration<WeatherData>
9798
// Optional: Enable chunk skipping for faster queries on this column.
9899
.WithChunkSkipping(x => x.Time)
99100
// Optional: Set the chunk interval. Can be a string ("7 days") or long (microseconds).
100-
.WithChunkTimeInterval("86400000");
101+
.WithChunkTimeInterval("86400000")
102+
// Optional: Migrate existing data when converting to hypertable (defaults to false).
103+
.WithMigrateData(true);
101104
}
102105
}
103106
```
@@ -108,7 +111,10 @@ public class WeatherDataConfiguration : IEntityTypeConfiguration<WeatherData>
108111
For simpler configurations, you can use the [Hypertable] attribute directly on your model class.
109112

110113
```csharp
111-
[Hypertable(nameof(Time), ChunkSkipColumns = new[] { "Time" }, ChunkTimeInterval = "86400000")]
114+
[Hypertable(nameof(Time),
115+
ChunkSkipColumns = new[] { "Time" },
116+
ChunkTimeInterval = "86400000",
117+
MigrateData = true)]
112118
[PrimaryKey(nameof(Id), nameof(Time))]
113119
public class DeviceReading
114120
{

samples/Eftdb.Samples.CodeFirst/README.md

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,78 @@
1-
# EF Core Code-First Example with TimescaleDB
1+
# EF Core Code-First Example with TimescaleDB
22

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

55
---
66

7-
## 🚀 Migrations and Database Management
7+
## Migrations and Database Management
88

99
Use the following commands to manage your EF Core migrations and database updates.
1010

11-
### 📌 Add a new migration
11+
> **Note:** Run all commands from the repository root directory.
12+
13+
### Add a new migration
1214

1315
```bash
14-
dotnet ef migrations add --project CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess --startup-project CmdScale.EntityFrameworkCore.TimescaleDB.Example <MigrationName>
16+
dotnet ef migrations add <MigrationName> --project samples/Eftdb.Samples.Shared --startup-project samples/Eftdb.Samples.CodeFirst
1517
```
1618

17-
### Apply migrations to the database
19+
### Apply migrations to the database
1820

1921
```bash
20-
dotnet ef database update --project CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess --startup-project CmdScale.EntityFrameworkCore.TimescaleDB.Example
22+
dotnet ef database update --project samples/Eftdb.Samples.Shared --startup-project samples/Eftdb.Samples.CodeFirst
2123
```
2224

23-
### ❌ Remove last migration (if not applied to the database, yet)
24-
```
25-
dotnet ef migrations remove --project CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess --startup-project CmdScale.EntityFrameworkCore.TimescaleDB.Example
25+
### Remove last migration (if not applied to the database yet)
26+
27+
```bash
28+
dotnet ef migrations remove --project samples/Eftdb.Samples.Shared --startup-project samples/Eftdb.Samples.CodeFirst
2629
```
2730

28-
### 🧹 Reset all migrations (rollback to initial state)
31+
### Reset all migrations (rollback to initial state)
2932

3033
```bash
31-
dotnet ef database update 0 --project CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess --startup-project CmdScale.EntityFrameworkCore.TimescaleDB.Example
34+
dotnet ef database update 0 --project samples/Eftdb.Samples.Shared --startup-project samples/Eftdb.Samples.CodeFirst
3235
```
3336

3437
---
3538

36-
## 📁 Project Structure
39+
## Project Structure
3740

3841
```text
39-
CmdScale.EntityFrameworkCore.TimescaleDB.Example/
40-
41-
├── CmdScale.EntityFrameworkCore.TimescaleDB.Example/ # Startup project
42-
├── CmdScale.EntityFrameworkCore.TimescaleDB.Example.DataAccess/ # Contains DbContext and migrations
43-
└── docker-compose.yml # Sets up TimescaleDB container (in Solution Items)
42+
samples/
43+
├── Eftdb.Samples.CodeFirst/ # Startup project (contains Program.cs and design-time services)
44+
├── Eftdb.Samples.Shared/ # Contains DbContext, entities, and migrations
45+
└── Eftdb.Samples.DatabaseFirst/ # Database-first scaffolding example
4446
```
4547

4648
---
4749

48-
## 🐳 Docker
49-
- This project assumes you have an existing TimescaleDB-compatible PostgreSQL database.
50-
- A `docker-compose.yml` file is included in the **Solution Items** folder to spin up a TimescaleDB container for local development and testing.
50+
## Docker
51+
52+
This project assumes you have an existing TimescaleDB-compatible PostgreSQL database. A `docker-compose.yml` file is included in the repository root to spin up a TimescaleDB container for local development and testing.
5153

52-
To start the container, run:
54+
To start the container, run from the repository root:
5355

54-
```bash
55-
docker-compose up -d
56-
```
56+
```bash
57+
docker-compose up -d
58+
```
59+
60+
To stop and reset the database:
61+
62+
```bash
63+
docker-compose down -v
64+
```
5765

58-
- Connection string settings should match the configuration in your `docker-compose.yml`.
66+
Connection string settings should match the configuration in your `docker-compose.yml`.
5967

6068
---
6169

62-
## 🧠 Notes
63-
- Depending on if you're using project- or package-references you might to (un)comment adding the services in `TimescaleDBDesignTimeService.cs`
70+
## Notes
6471

72+
- Depending on if you're using project- or package-references you might need to (un)comment adding the services in `TimescaleDBDesignTimeService.cs`
73+
- The `Eftdb.Samples.Shared` project contains the `TimescaleContext` and entity models shared between samples
6574

66-
## 📚 Resources
75+
## Resources
6776

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

src/Eftdb.Design/TimescaleCSharpMigrationOperationGenerator.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protected override void Generate(MigrationOperation operation, IndentedStringBui
1717
ReorderPolicyOperationGenerator? reorderPolicyOperationGenerator = null;
1818
ContinuousAggregateOperationGenerator? continuousAggregateOperationGenerator = null;
1919

20-
List<string> statements = [];
20+
List<string> statements;
2121
bool suppressTransaction = false;
2222

2323
switch (operation)
@@ -60,7 +60,14 @@ protected override void Generate(MigrationOperation operation, IndentedStringBui
6060

6161
default:
6262
base.Generate(operation, builder);
63-
break;
63+
return;
64+
}
65+
66+
// Guard: if no statements were generated, output a no-op SQL comment to maintain valid C# syntax.
67+
if (statements.Count == 0)
68+
{
69+
builder.Append(".Sql(@\"-- No SQL generated for this operation\")");
70+
return;
6471
}
6572

6673
SqlBuilderHelper.BuildQueryString(statements, builder, suppressTransaction);

src/Eftdb/Configuration/Hypertable/HypertableAnnotations.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public static class HypertableAnnotations
88
public const string IsHypertable = "TimescaleDB:IsHypertable";
99
public const string HypertableTimeColumn = "TimescaleDB:TimeColumnName";
1010
public const string EnableCompression = "TimescaleDB:EnableCompression";
11+
public const string MigrateData = "TimescaleDB:MigrateData";
1112
public const string ChunkTimeInterval = "TimescaleDB:ChunkTimeInterval";
1213
public const string ChunkSkipColumns = "TimescaleDB:ChunkSkipColumns";
1314
public const string AdditionalDimensions = "TimescaleDB:AdditionalDimensions";

src/Eftdb/Configuration/Hypertable/HypertableAttribute.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ public sealed class HypertableAttribute : Attribute
1010
public string TimeColumnName { get; } = string.Empty;
1111

1212
/// <summary>
13-
///
13+
/// Specifies whether compression is enabled on the hypertable.
1414
/// </summary>
1515
public bool EnableCompression { get; set; } = false;
1616

17+
/// <summary>
18+
/// Specifies whether existing data should be migrated when converting a table to a hypertable.
19+
/// </summary>
20+
public bool MigrateData { get; set; } = false;
21+
1722
/// <summary>
1823
/// Defines the duration of time covered by each chunk in a hypertable.
1924
/// </summary>

src/Eftdb/Configuration/Hypertable/HypertableConvention.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ public void ProcessEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilde
3737
entityTypeBuilder.HasAnnotation(HypertableAnnotations.EnableCompression, true);
3838
}
3939

40+
if (attribute.MigrateData == true)
41+
{
42+
entityTypeBuilder.HasAnnotation(HypertableAnnotations.MigrateData, true);
43+
}
44+
4045
if (attribute.ChunkSkipColumns != null && attribute.ChunkSkipColumns.Length > 0)
4146
{
4247
/// Chunk skipping requires compression to be enabled

src/Eftdb/Configuration/Hypertable/HypertableTypeBuilder.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,25 @@ public static EntityTypeBuilder<TEntity> EnableCompression<TEntity>(
128128
return entityTypeBuilder;
129129
}
130130

131+
/// <summary>
132+
/// Specifies whether existing data should be migrated when converting a table to a hypertable.
133+
/// </summary>
134+
/// <remarks>
135+
/// When converting an existing table to a hypertable, this parameter controls whether existing data
136+
/// is migrated into chunks. If set to false, only new data will be stored in chunks.
137+
/// Defaults to <c>false</c> to match TimescaleDB's default behavior.
138+
/// </remarks>
139+
/// <typeparam name="TEntity">The entity type being configured.</typeparam>
140+
/// <param name="entityTypeBuilder">The builder for the entity type.</param>
141+
/// <param name="migrateData">A boolean indicating whether to migrate existing data. Defaults to <c>true</c>.</param>
142+
public static EntityTypeBuilder<TEntity> WithMigrateData<TEntity>(
143+
this EntityTypeBuilder<TEntity> entityTypeBuilder,
144+
bool migrateData = true) where TEntity : class
145+
{
146+
entityTypeBuilder.HasAnnotation(HypertableAnnotations.MigrateData, migrateData);
147+
return entityTypeBuilder;
148+
}
149+
131150
/// <summary>
132151
/// Extracts the property name from a member access lambda expression.
133152
/// </summary>

src/Eftdb/Generators/HypertableOperationGenerator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ public List<string> Generate(CreateHypertableOperation operation)
2424
string qualifiedTableName = sqlHelper.Regclass(operation.TableName, operation.Schema);
2525
string qualifiedIdentifier = sqlHelper.QualifiedIdentifier(operation.TableName, operation.Schema);
2626

27+
string migrateDataParam = operation.MigrateData ? ", migrate_data => true" : "";
28+
2729
List<string> statements =
2830
[
29-
$"SELECT create_hypertable({qualifiedTableName}, '{operation.TimeColumnName}');"
31+
$"SELECT create_hypertable({qualifiedTableName}, '{operation.TimeColumnName}'{migrateDataParam});"
3032
];
3133

3234
// ChunkTimeInterval

src/Eftdb/Internals/Features/Hypertables/HypertableModelExtractor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public static IEnumerable<CreateHypertableOperation> GetHypertables(IRelationalM
7676

7777
string chunkTimeInterval = entityType.FindAnnotation(HypertableAnnotations.ChunkTimeInterval)?.Value as string ?? DefaultValues.ChunkTimeInterval;
7878
bool enableCompression = entityType.FindAnnotation(HypertableAnnotations.EnableCompression)?.Value as bool? ?? false;
79+
bool migrateData = entityType.FindAnnotation(HypertableAnnotations.MigrateData)?.Value as bool? ?? false;
7980

8081
yield return new CreateHypertableOperation
8182
{
@@ -84,6 +85,7 @@ public static IEnumerable<CreateHypertableOperation> GetHypertables(IRelationalM
8485
TimeColumnName = timeColumnName,
8586
ChunkTimeInterval = chunkTimeInterval ?? DefaultValues.ChunkTimeInterval,
8687
EnableCompression = enableCompression,
88+
MigrateData = migrateData,
8789
ChunkSkipColumns = chunkSkipColumns,
8890
AdditionalDimensions = additionalDimensions
8991
};

0 commit comments

Comments
 (0)