Skip to content
Open
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
28 changes: 28 additions & 0 deletions .github/workflows/ef-cli-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: EF CLI Smoke

on:
push:
branches: [main]
paths-ignore:
- "**/*.md"
pull_request:
branches: [main]

jobs:
ef-cli-smoke:
name: dotnet-ef integration tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x

- name: Install dotnet-ef
run: dotnet tool install --global dotnet-ef

- name: Run dotnet-ef CLI tests
run: dotnet test test/EFCore.ClickHouse.Tests/EFCore.ClickHouse.Tests.csproj --filter "FullyQualifiedName~DotnetEfCliTests"
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using ClickHouse.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;

namespace ClickHouse.EntityFrameworkCore.Design.Internal;

public class ClickHouseAnnotationCodeGenerator : AnnotationCodeGenerator
{
public ClickHouseAnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies)
: base(dependencies)
{
}

protected override bool IsHandledByConvention(IModel model, IAnnotation annotation)
{
if (annotation.Name.StartsWith(ClickHouseAnnotationNames.Prefix, StringComparison.Ordinal))
return false;

return base.IsHandledByConvention(model, annotation);
}

protected override bool IsHandledByConvention(IEntityType entityType, IAnnotation annotation)
{
if (annotation.Name.StartsWith(ClickHouseAnnotationNames.Prefix, StringComparison.Ordinal))
return false;

return base.IsHandledByConvention(entityType, annotation);
}

protected override bool IsHandledByConvention(IProperty property, IAnnotation annotation)
{
if (annotation.Name.StartsWith(ClickHouseAnnotationNames.Prefix, StringComparison.Ordinal))
return false;

return base.IsHandledByConvention(property, annotation);
}

protected override bool IsHandledByConvention(IIndex index, IAnnotation annotation)
{
if (annotation.Name.StartsWith(ClickHouseAnnotationNames.Prefix, StringComparison.Ordinal))
return false;

return base.IsHandledByConvention(index, annotation);
}
}
30 changes: 30 additions & 0 deletions src/EFCore.ClickHouse/Design/Internal/ClickHouseCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using ClickHouse.EntityFrameworkCore.Extensions;
using ClickHouse.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Scaffolding;
using System.Reflection;

namespace ClickHouse.EntityFrameworkCore.Design.Internal;

public class ClickHouseCodeGenerator : ProviderCodeGenerator
{
private static readonly MethodInfo UseClickHouseMethodInfo
= typeof(ClickHouseDbContextOptionsBuilderExtensions).GetRuntimeMethod(
nameof(ClickHouseDbContextOptionsBuilderExtensions.UseClickHouse),
[typeof(DbContextOptionsBuilder), typeof(string), typeof(Action<ClickHouseDbContextOptionsBuilder>)])!;

public ClickHouseCodeGenerator(ProviderCodeGeneratorDependencies dependencies)
: base(dependencies)
{
}

public override MethodCallCodeFragment GenerateUseProvider(
string connectionString,
MethodCallCodeFragment? providerOptions)
=> new(
UseClickHouseMethodInfo,
providerOptions is null
? [connectionString]
: [connectionString, new NestedClosureCodeFragment("x", providerOptions)]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using ClickHouse.EntityFrameworkCore.Extensions;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.Extensions.DependencyInjection;

[assembly: DesignTimeProviderServices(
"ClickHouse.EntityFrameworkCore.Design.Internal.ClickHouseDesignTimeServices")]

namespace ClickHouse.EntityFrameworkCore.Design.Internal;

public class ClickHouseDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
{
serviceCollection.AddEntityFrameworkClickHouse();

new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection)
.TryAdd<IAnnotationCodeGenerator, ClickHouseAnnotationCodeGenerator>()
.TryAdd<IProviderConfigurationCodeGenerator, ClickHouseCodeGenerator>()
.TryAddCoreServices();
}
}
1 change: 1 addition & 0 deletions src/EFCore.ClickHouse/EFCore.ClickHouse.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2" PrivateAssets="none" />
<PackageReference Include="ClickHouse.Driver" Version="1.1.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using ClickHouse.EntityFrameworkCore.Metadata.Builders;
using ClickHouse.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ClickHouse.EntityFrameworkCore.Extensions;

public static class ClickHouseEntityTypeBuilderExtensions
{
public static ClickHouseMergeTreeEngineBuilder HasMergeTreeEngine(this TableBuilder tableBuilder)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder));
}

public static ClickHouseReplacingMergeTreeEngineBuilder HasReplacingMergeTreeEngine(
this TableBuilder tableBuilder, string? version = null, string? isDeleted = null)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder), version, isDeleted);
}

public static ClickHouseSummingMergeTreeEngineBuilder HasSummingMergeTreeEngine(
this TableBuilder tableBuilder, params string[] columns)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder), columns);
}

public static ClickHouseAggregatingMergeTreeEngineBuilder HasAggregatingMergeTreeEngine(
this TableBuilder tableBuilder)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder));
}

public static ClickHouseCollapsingMergeTreeEngineBuilder HasCollapsingMergeTreeEngine(
this TableBuilder tableBuilder, string sign)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
ArgumentException.ThrowIfNullOrWhiteSpace(sign);
return new(GetEntityType(tableBuilder), sign);
}

public static ClickHouseVersionedCollapsingMergeTreeEngineBuilder HasVersionedCollapsingMergeTreeEngine(
this TableBuilder tableBuilder, string sign, string version)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
ArgumentException.ThrowIfNullOrWhiteSpace(sign);
ArgumentException.ThrowIfNullOrWhiteSpace(version);
return new(GetEntityType(tableBuilder), sign, version);
}

public static ClickHouseGraphiteMergeTreeEngineBuilder HasGraphiteMergeTreeEngine(
this TableBuilder tableBuilder, string configSection)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
ArgumentException.ThrowIfNullOrWhiteSpace(configSection);
return new(GetEntityType(tableBuilder), configSection);
}

public static ClickHouseSimpleEngineBuilder HasTinyLogEngine(this TableBuilder tableBuilder)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder), ClickHouseAnnotationNames.TinyLog);
}

public static ClickHouseSimpleEngineBuilder HasStripeLogEngine(this TableBuilder tableBuilder)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder), ClickHouseAnnotationNames.StripeLog);
}

public static ClickHouseSimpleEngineBuilder HasLogEngine(this TableBuilder tableBuilder)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder), ClickHouseAnnotationNames.Log);
}

public static ClickHouseSimpleEngineBuilder HasMemoryEngine(this TableBuilder tableBuilder)
{
ArgumentNullException.ThrowIfNull(tableBuilder);
return new(GetEntityType(tableBuilder), ClickHouseAnnotationNames.Memory);
}

private static IMutableEntityType GetEntityType(TableBuilder tableBuilder)
=> (IMutableEntityType)tableBuilder.Metadata;
}
132 changes: 132 additions & 0 deletions src/EFCore.ClickHouse/Extensions/ClickHouseEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using ClickHouse.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata;

namespace ClickHouse.EntityFrameworkCore.Extensions;

public static class ClickHouseEntityTypeExtensions
{
// Engine

public static string? GetEngine(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.Engine];

public static void SetEngine(this IMutableEntityType entityType, string? engine)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.Engine, engine);

// ORDER BY

public static string[]? GetOrderBy(this IReadOnlyEntityType entityType)
=> (string[]?)entityType[ClickHouseAnnotationNames.OrderBy];

public static void SetOrderBy(this IMutableEntityType entityType, string[]? columns)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.OrderBy,
columns is { Length: > 0 } ? columns : null);

// PARTITION BY

public static string[]? GetPartitionBy(this IReadOnlyEntityType entityType)
=> (string[]?)entityType[ClickHouseAnnotationNames.PartitionBy];

public static void SetPartitionBy(this IMutableEntityType entityType, string[]? columns)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.PartitionBy,
columns is { Length: > 0 } ? columns : null);

// PRIMARY KEY (ClickHouse structural key, distinct from EF's HasKey)

public static string[]? GetClickHousePrimaryKey(this IReadOnlyEntityType entityType)
=> (string[]?)entityType[ClickHouseAnnotationNames.PrimaryKey];

public static void SetClickHousePrimaryKey(this IMutableEntityType entityType, string[]? columns)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.PrimaryKey,
columns is { Length: > 0 } ? columns : null);

// SAMPLE BY

public static string[]? GetSampleBy(this IReadOnlyEntityType entityType)
=> (string[]?)entityType[ClickHouseAnnotationNames.SampleBy];

public static void SetSampleBy(this IMutableEntityType entityType, string[]? columns)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.SampleBy,
columns is { Length: > 0 } ? columns : null);

// TTL (table-level)

public static string? GetTtl(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.Ttl];

public static void SetTtl(this IMutableEntityType entityType, string? ttlExpression)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.Ttl, ttlExpression);

// ReplacingMergeTree

public static string? GetReplacingMergeTreeVersion(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.ReplacingMergeTreeVersion];

public static void SetReplacingMergeTreeVersion(this IMutableEntityType entityType, string? version)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.ReplacingMergeTreeVersion, version);

public static string? GetReplacingMergeTreeIsDeleted(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.ReplacingMergeTreeIsDeleted];

public static void SetReplacingMergeTreeIsDeleted(this IMutableEntityType entityType, string? isDeleted)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.ReplacingMergeTreeIsDeleted, isDeleted);

// SummingMergeTree

public static string[]? GetSummingMergeTreeColumns(this IReadOnlyEntityType entityType)
=> (string[]?)entityType[ClickHouseAnnotationNames.SummingMergeTreeColumns];

public static void SetSummingMergeTreeColumns(this IMutableEntityType entityType, string[]? columns)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.SummingMergeTreeColumns,
columns is { Length: > 0 } ? columns : null);

// CollapsingMergeTree

public static string? GetCollapsingMergeTreeSign(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.CollapsingMergeTreeSign];

public static void SetCollapsingMergeTreeSign(this IMutableEntityType entityType, string? sign)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.CollapsingMergeTreeSign, sign);

// VersionedCollapsingMergeTree

public static string? GetVersionedCollapsingMergeTreeSign(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.VersionedCollapsingMergeTreeSign];

public static void SetVersionedCollapsingMergeTreeSign(this IMutableEntityType entityType, string? sign)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.VersionedCollapsingMergeTreeSign, sign);

public static string? GetVersionedCollapsingMergeTreeVersion(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.VersionedCollapsingMergeTreeVersion];

public static void SetVersionedCollapsingMergeTreeVersion(this IMutableEntityType entityType, string? version)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.VersionedCollapsingMergeTreeVersion, version);

// GraphiteMergeTree

public static string? GetGraphiteMergeTreeConfigSection(this IReadOnlyEntityType entityType)
=> (string?)entityType[ClickHouseAnnotationNames.GraphiteMergeTreeConfigSection];

public static void SetGraphiteMergeTreeConfigSection(this IMutableEntityType entityType, string? configSection)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.GraphiteMergeTreeConfigSection, configSection);

// Settings (prefix-based key-value)

public static Dictionary<string, string> GetSettings(this IReadOnlyEntityType entityType)
{
var settings = new Dictionary<string, string>();
foreach (var annotation in entityType.GetAnnotations())
{
if (annotation.Name.StartsWith(ClickHouseAnnotationNames.SettingPrefix, StringComparison.Ordinal)
&& annotation.Value is string value)
{
var key = annotation.Name[ClickHouseAnnotationNames.SettingPrefix.Length..];
settings[key] = value;
}
}
return settings;
}

public static void SetSetting(this IMutableEntityType entityType, string settingName, string? value)
=> entityType.SetOrRemoveAnnotation(ClickHouseAnnotationNames.SettingPrefix + settingName, value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using ClickHouse.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace ClickHouse.EntityFrameworkCore.Extensions;

public static class ClickHouseIndexBuilderExtensions
{
public static IndexBuilder HasSkippingIndexType(this IndexBuilder indexBuilder, string type)
{
indexBuilder.Metadata.SetSkippingIndexType(type);
return indexBuilder;
}

public static IndexBuilder HasGranularity(this IndexBuilder indexBuilder, int granularity)
{
indexBuilder.Metadata.SetGranularity(granularity);
return indexBuilder;
}

public static IndexBuilder HasSkippingIndexParams(this IndexBuilder indexBuilder, string parameters)
{
indexBuilder.Metadata.SetSkippingIndexParams(parameters);
return indexBuilder;
}
}
Loading
Loading