Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,77 @@ public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAgg
string? chunkInterval = null)
where TEntity : class
where TSourceEntity : class
=> IsContinuousAggregateCore<TEntity, TSourceEntity>(entityTypeBuilder, materializedViewName, timeBucketWidth, Box(propertyExpression), timeBucketGroupBy, chunkInterval);

/// <inheritdoc cref="IsContinuousAggregate{TEntity, TSourceEntity}(EntityTypeBuilder{TEntity}, string, string, Expression{Func{TSourceEntity, DateTime}}, bool, string)"/>
public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAggregate<TEntity, TSourceEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string materializedViewName,
string timeBucketWidth,
Expression<Func<TSourceEntity, DateTimeOffset>> propertyExpression,
bool timeBucketGroupBy = true,
string? chunkInterval = null)
where TEntity : class
where TSourceEntity : class
=> IsContinuousAggregateCore<TEntity, TSourceEntity>(entityTypeBuilder, materializedViewName, timeBucketWidth, Box(propertyExpression), timeBucketGroupBy, chunkInterval);

/// <inheritdoc cref="IsContinuousAggregate{TEntity, TSourceEntity}(EntityTypeBuilder{TEntity}, string, string, Expression{Func{TSourceEntity, DateTime}}, bool, string)"/>
public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAggregate<TEntity, TSourceEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string materializedViewName,
string timeBucketWidth,
Expression<Func<TSourceEntity, DateOnly>> propertyExpression,
bool timeBucketGroupBy = true,
string? chunkInterval = null)
where TEntity : class
where TSourceEntity : class
=> IsContinuousAggregateCore<TEntity, TSourceEntity>(entityTypeBuilder, materializedViewName, timeBucketWidth, Box(propertyExpression), timeBucketGroupBy, chunkInterval);

/// <inheritdoc cref="IsContinuousAggregate{TEntity, TSourceEntity}(EntityTypeBuilder{TEntity}, string, string, Expression{Func{TSourceEntity, DateTime}}, bool, string)"/>
public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAggregate<TEntity, TSourceEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string materializedViewName,
string timeBucketWidth,
Expression<Func<TSourceEntity, long>> propertyExpression,
bool timeBucketGroupBy = true,
string? chunkInterval = null)
where TEntity : class
where TSourceEntity : class
=> IsContinuousAggregateCore<TEntity, TSourceEntity>(entityTypeBuilder, materializedViewName, timeBucketWidth, Box(propertyExpression), timeBucketGroupBy, chunkInterval);

/// <inheritdoc cref="IsContinuousAggregate{TEntity, TSourceEntity}(EntityTypeBuilder{TEntity}, string, string, Expression{Func{TSourceEntity, DateTime}}, bool, string)"/>
public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAggregate<TEntity, TSourceEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string materializedViewName,
string timeBucketWidth,
Expression<Func<TSourceEntity, int>> propertyExpression,
bool timeBucketGroupBy = true,
string? chunkInterval = null)
where TEntity : class
where TSourceEntity : class
=> IsContinuousAggregateCore<TEntity, TSourceEntity>(entityTypeBuilder, materializedViewName, timeBucketWidth, Box(propertyExpression), timeBucketGroupBy, chunkInterval);

/// <inheritdoc cref="IsContinuousAggregate{TEntity, TSourceEntity}(EntityTypeBuilder{TEntity}, string, string, Expression{Func{TSourceEntity, DateTime}}, bool, string)"/>
public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAggregate<TEntity, TSourceEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
string materializedViewName,
string timeBucketWidth,
Expression<Func<TSourceEntity, short>> propertyExpression,
bool timeBucketGroupBy = true,
string? chunkInterval = null)
where TEntity : class
where TSourceEntity : class
=> IsContinuousAggregateCore<TEntity, TSourceEntity>(entityTypeBuilder, materializedViewName, timeBucketWidth, Box(propertyExpression), timeBucketGroupBy, chunkInterval);

private static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAggregateCore<TEntity, TSourceEntity>(
EntityTypeBuilder<TEntity> entityTypeBuilder,
string materializedViewName,
string timeBucketWidth,
Expression<Func<TSourceEntity, object>> propertyExpression,
bool timeBucketGroupBy,
string? chunkInterval)
where TEntity : class
where TSourceEntity : class
{
// Configure the entity to map to a view instead of a table
// This prevents EF Core from trying to create a table for the continuous aggregate
Expand All @@ -52,5 +123,14 @@ public static ContinuousAggregateBuilder<TEntity, TSourceEntity> IsContinuousAgg

return new ContinuousAggregateBuilder<TEntity, TSourceEntity>(entityTypeBuilder);
}

// Lifts a typed time-column expression to Expression<Func<TSourceEntity, object>> by inserting
// a Convert node, so the shared core method can extract the property name uniformly.
private static Expression<Func<TSourceEntity, object>> Box<TSourceEntity, TProperty>(
Expression<Func<TSourceEntity, TProperty>> expression)
where TProperty : struct
=> Expression.Lambda<Func<TSourceEntity, object>>(
Expression.Convert(expression.Body, typeof(object)),
expression.Parameters);
}
}
48 changes: 46 additions & 2 deletions src/Eftdb/Configuration/Hypertable/HypertableTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,48 @@ public static class HypertableTypeBuilder
/// </summary>
/// <remarks>
/// This is the essential first step to enable TimescaleDB features for an entity.
/// It corresponds to the `create_hypertable` function in PostgreSQL.
/// It corresponds to the <c>create_hypertable</c> function in PostgreSQL.
/// </remarks>
/// <typeparam name="TEntity">The entity type being configured.</typeparam>
/// <param name="entityTypeBuilder">The builder for the entity type.</param>
/// <param name="timePropertyExpression">A lambda expression representing the time column (e.g., `x => x.Timestamp`).</param>
/// <param name="timePropertyExpression">A lambda expression representing the time column (e.g., <c>x =&gt; x.Timestamp</c>).</param>
public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, DateTime>> timePropertyExpression) where TEntity : class
=> IsHypertableCore(entityTypeBuilder, Box(timePropertyExpression));

/// <inheritdoc cref="IsHypertable{TEntity}(EntityTypeBuilder{TEntity}, Expression{Func{TEntity, DateTime}})"/>
public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, DateTimeOffset>> timePropertyExpression) where TEntity : class
=> IsHypertableCore(entityTypeBuilder, Box(timePropertyExpression));

/// <inheritdoc cref="IsHypertable{TEntity}(EntityTypeBuilder{TEntity}, Expression{Func{TEntity, DateTime}})"/>
public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, DateOnly>> timePropertyExpression) where TEntity : class
=> IsHypertableCore(entityTypeBuilder, Box(timePropertyExpression));

/// <inheritdoc cref="IsHypertable{TEntity}(EntityTypeBuilder{TEntity}, Expression{Func{TEntity, DateTime}})"/>
public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, long>> timePropertyExpression) where TEntity : class
=> IsHypertableCore(entityTypeBuilder, Box(timePropertyExpression));

/// <inheritdoc cref="IsHypertable{TEntity}(EntityTypeBuilder{TEntity}, Expression{Func{TEntity, DateTime}})"/>
public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, int>> timePropertyExpression) where TEntity : class
=> IsHypertableCore(entityTypeBuilder, Box(timePropertyExpression));

/// <inheritdoc cref="IsHypertable{TEntity}(EntityTypeBuilder{TEntity}, Expression{Func{TEntity, DateTime}})"/>
public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
this EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, short>> timePropertyExpression) where TEntity : class
=> IsHypertableCore(entityTypeBuilder, Box(timePropertyExpression));

private static EntityTypeBuilder<TEntity> IsHypertableCore<TEntity>(
EntityTypeBuilder<TEntity> entityTypeBuilder,
Expression<Func<TEntity, object>> timePropertyExpression) where TEntity : class
{
string propertyName = GetPropertyName(timePropertyExpression);
Expand All @@ -33,6 +68,15 @@ public static EntityTypeBuilder<TEntity> IsHypertable<TEntity>(
return entityTypeBuilder;
}

// Lifts a typed time-column expression to Expression<Func<TEntity, object>> by inserting a Convert
// node, so the shared core method can extract the property name uniformly via GetPropertyName.
private static Expression<Func<TEntity, object>> Box<TEntity, TProperty>(
Expression<Func<TEntity, TProperty>> expression)
where TProperty : struct
=> Expression.Lambda<Func<TEntity, object>>(
Expression.Convert(expression.Body, typeof(object)),
expression.Parameters);

/// <summary>
/// Adds an additional partitioning dimension to the hypertable.
/// </summary>
Expand Down
Loading
Loading