Skip to content

Commit 70bd39a

Browse files
authored
Merge pull request #59 from cnblogs/support-clickhouse
feat: add clickhouse support
2 parents 27ab527 + a0b5ab7 commit 70bd39a

19 files changed

+467
-24
lines changed

Cnblogs.Architecture.sln

+14
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.In
6262
EndProject
6363
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.TestShared", "test\Cnblogs.Architecture.TestShared\Cnblogs.Architecture.TestShared.csproj", "{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}"
6464
EndProject
65+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse", "src\Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse\Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse.csproj", "{73665E32-3D10-4F71-B893-4C65F36332D0}"
66+
EndProject
67+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse", "src\Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse\Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj", "{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}"
68+
EndProject
6569
Global
6670
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6771
Debug|Any CPU = Debug|Any CPU
@@ -94,6 +98,8 @@ Global
9498
{E7ABC399-AF71-46A6-A4D4-A38972BC7D50} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
9599
{A6A8FDC5-20E7-4776-9CB6-A2E43DCCBE7B} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
96100
{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F} = {772497F8-2CB1-4EA6-AEB8-482C3ECD0A9D}
101+
{73665E32-3D10-4F71-B893-4C65F36332D0} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
102+
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB} = {D3A6DF01-017E-4088-936C-B3791F41DF53}
97103
EndGlobalSection
98104
GlobalSection(ProjectConfigurationPlatforms) = postSolution
99105
{54D9D850-1CFC-485E-97FE-87F41C220523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -200,5 +206,13 @@ Global
200206
{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
201207
{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
202208
{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}.Release|Any CPU.Build.0 = Release|Any CPU
209+
{73665E32-3D10-4F71-B893-4C65F36332D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
210+
{73665E32-3D10-4F71-B893-4C65F36332D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
211+
{73665E32-3D10-4F71-B893-4C65F36332D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
212+
{73665E32-3D10-4F71-B893-4C65F36332D0}.Release|Any CPU.Build.0 = Release|Any CPU
213+
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
214+
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
215+
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
216+
{4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.Build.0 = Release|Any CPU
203217
EndGlobalSection
204218
EndGlobal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse;
2+
3+
/// <summary>
4+
/// The collection for clickhouse contexts.
5+
/// </summary>
6+
public class ClickhouseContextCollection
7+
{
8+
internal List<Type> ContextTypes { get; } = new();
9+
10+
internal void Add<TContext>()
11+
{
12+
ContextTypes.Add(typeof(TContext));
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Data;
2+
using ClickHouse.Client.ADO;
3+
using Cnblogs.Architecture.Ddd.Infrastructure.Dapper;
4+
5+
namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse;
6+
7+
/// <summary>
8+
/// Clickhouse connection factory.
9+
/// </summary>
10+
public class ClickhouseDbConnectionFactory : IDbConnectionFactory
11+
{
12+
private readonly string _connectionString;
13+
14+
/// <summary>
15+
/// Create a clickhouse connection factory.
16+
/// </summary>
17+
/// <param name="connectionString">The connection string.</param>
18+
public ClickhouseDbConnectionFactory(string connectionString)
19+
{
20+
_connectionString = connectionString;
21+
}
22+
23+
/// <inheritdoc />
24+
public IDbConnection CreateDbConnection()
25+
{
26+
return new ClickHouseConnection(_connectionString);
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Hosting;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse;
7+
8+
/// <summary>
9+
/// Hosed service to initialize clickhouse contexts.
10+
/// </summary>
11+
public class ClickhouseInitializeHostedService : IHostedService
12+
{
13+
private readonly ClickhouseContextCollection _collection;
14+
private readonly IServiceProvider _serviceProvider;
15+
16+
/// <summary>
17+
/// Create a <see cref="ClickhouseInitializeHostedService"/>.
18+
/// </summary>
19+
/// <param name="collections">The contexts been registered.</param>
20+
/// <param name="serviceProvider">The provider for contexts.</param>
21+
public ClickhouseInitializeHostedService(
22+
IOptions<ClickhouseContextCollection> collections,
23+
IServiceProvider serviceProvider)
24+
{
25+
_serviceProvider = serviceProvider;
26+
_collection = collections.Value;
27+
}
28+
29+
/// <inheritdoc />
30+
public Task StartAsync(CancellationToken cancellationToken)
31+
{
32+
using var scope = _serviceProvider.CreateScope();
33+
foreach (var collectionContextType in _collection.ContextTypes)
34+
{
35+
var context = scope.ServiceProvider.GetRequiredService(collectionContextType) as ClickhouseDapperContext;
36+
context?.Init();
37+
}
38+
39+
return Task.CompletedTask;
40+
}
41+
42+
/// <inheritdoc />
43+
public Task StopAsync(CancellationToken cancellationToken)
44+
{
45+
return Task.CompletedTask;
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<Description>
5+
Provides clickhouse dapper integration.
6+
</Description>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Cqrs.Dapper\Cnblogs.Architecture.Ddd.Cqrs.Dapper.csproj" />
11+
<ProjectReference Include="..\Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse\Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj" />
12+
</ItemGroup>
13+
14+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse;
2+
using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
// ReSharper disable once CheckNamespace
6+
namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper;
7+
8+
/// <summary>
9+
/// Extension methods for inject clickhouse to dapper context.
10+
/// </summary>
11+
public static class DapperConfigurationBuilderExtension
12+
{
13+
/// <summary>
14+
/// Use clickhouse as the underlying database.
15+
/// </summary>
16+
/// <param name="builder"><see cref="DapperConfigurationBuilder{TContext}"/>.</param>
17+
/// <param name="connectionString">The connection string for clickhouse.</param>
18+
/// <typeparam name="TContext">The context type been used.</typeparam>
19+
public static void UseClickhouse<TContext>(
20+
this DapperConfigurationBuilder<TContext> builder,
21+
string connectionString)
22+
where TContext : ClickhouseDapperContext
23+
{
24+
builder.UseDbConnectionFactory(new ClickhouseDbConnectionFactory(connectionString));
25+
builder.Services.AddSingleton(new ClickhouseContextOptions<TContext>(connectionString));
26+
builder.Services.Configure<ClickhouseContextCollection>(x => x.Add<TContext>());
27+
builder.Services.AddHostedService<ClickhouseInitializeHostedService>();
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
using Cnblogs.Architecture.Ddd.Infrastructure.Dapper;
1+
using Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer;
2+
using Cnblogs.Architecture.Ddd.Infrastructure.Dapper;
23

3-
namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer;
4+
// ReSharper disable once CheckNamespace
5+
namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper;
46

57
/// <summary>
6-
/// 用于配置 Dapper Configuration 的扩展方法。
8+
/// Extension methods to configure dapper context.
79
/// </summary>
810
public static class DapperConfigurationBuilderExtension
911
{
1012
/// <summary>
11-
/// 使用 SqlServer 配置 <see cref="DapperContext"/>
13+
/// Configure <see cref="DapperContext"/> to use sql server as underlying database.
1214
/// </summary>
13-
/// <param name="builder"><see cref="DapperConfigurationBuilder"/></param>
14-
/// <param name="connectionString">连接字符串。</param>
15-
public static void UseSqlServer(this DapperConfigurationBuilder builder, string connectionString)
15+
/// <param name="builder"><see cref="DapperConfigurationBuilder{TContext}"/></param>
16+
/// <param name="connectionString">The connection string for sql server.</param>
17+
/// <typeparam name="TContext">The type of context been configured.</typeparam>
18+
public static void UseSqlServer<TContext>(this DapperConfigurationBuilder<TContext> builder, string connectionString)
19+
where TContext : DapperContext
1620
{
1721
builder.UseDbConnectionFactory(new SqlServerDbConnectionFactory(connectionString));
1822
}
19-
}
23+
}

src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs

+13-8
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper;
77
/// <summary>
88
/// Dapper 配置类。
99
/// </summary>
10-
public class DapperConfigurationBuilder
10+
/// <typeparam name="TContext">The context type been configured.</typeparam>
11+
public class DapperConfigurationBuilder<TContext>
12+
where TContext : DapperContext
1113
{
12-
private readonly IServiceCollection _services;
1314
private readonly string _dapperContextTypeName;
1415

1516
/// <summary>
1617
/// 创建一个 DapperConfigurationBuilder。
1718
/// </summary>
18-
/// <param name="dapperContextTypeName">正在配置的 DapperContext 名称。</param>
1919
/// <param name="services"><see cref="ServiceCollection"/></param>
20-
public DapperConfigurationBuilder(string dapperContextTypeName, IServiceCollection services)
20+
public DapperConfigurationBuilder(IServiceCollection services)
2121
{
22-
_dapperContextTypeName = dapperContextTypeName;
23-
_services = services;
22+
_dapperContextTypeName = typeof(TContext).Name;
23+
Services = services;
2424
}
2525

2626
/// <summary>
@@ -31,7 +31,12 @@ public DapperConfigurationBuilder(string dapperContextTypeName, IServiceCollecti
3131
public void UseDbConnectionFactory<TFactory>(TFactory factory)
3232
where TFactory : IDbConnectionFactory
3333
{
34-
_services.Configure<DbConnectionFactoryCollection>(
34+
Services.Configure<DbConnectionFactoryCollection>(
3535
c => c.AddDbConnectionFactory(_dapperContextTypeName, factory));
3636
}
37-
}
37+
38+
/// <summary>
39+
/// The underlying <see cref="IServiceCollection"/>.
40+
/// </summary>
41+
public IServiceCollection Services { get; }
42+
}

src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ public static class ServiceCollectionInjector
1515
/// <param name="services"><see cref="ServiceCollection"/>。</param>
1616
/// <typeparam name="TContext"><see cref="DapperContext"/> 的实现类型。</typeparam>
1717
/// <returns></returns>
18-
public static DapperConfigurationBuilder AddDapperContext<TContext>(this IServiceCollection services)
18+
public static DapperConfigurationBuilder<TContext> AddDapperContext<TContext>(this IServiceCollection services)
1919
where TContext : DapperContext
2020
{
2121
services.AddScoped<TContext>();
22-
return new DapperConfigurationBuilder(typeof(TContext).Name, services);
22+
return new DapperConfigurationBuilder<TContext>(services);
2323
}
24-
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse;
2+
3+
/// <summary>
4+
/// The options for clickhouse context.
5+
/// </summary>
6+
/// <typeparam name="TContext">The type of <see cref="ClickhouseDapperContext"/> been configured.</typeparam>
7+
public class ClickhouseContextOptions<TContext> : ClickhouseContextOptions
8+
where TContext : ClickhouseDapperContext
9+
{
10+
/// <summary>
11+
/// Create a <see cref="ClickhouseContextOptions{TContext}"/> with given connection string.
12+
/// </summary>
13+
/// <param name="connectionString">The connection string for clickhouse.</param>
14+
public ClickhouseContextOptions(string connectionString)
15+
: base(connectionString)
16+
{
17+
}
18+
}
19+
20+
/// <summary>
21+
/// The options for <see cref="ClickhouseDapperContext"/>.
22+
/// </summary>
23+
public class ClickhouseContextOptions
24+
{
25+
private readonly Dictionary<Type, ClickhouseEntityConfiguration> _entityConfigurations = new();
26+
27+
internal ClickhouseContextOptions(string connectionString)
28+
{
29+
ConnectionString = connectionString;
30+
}
31+
32+
internal string ConnectionString { get; }
33+
34+
internal void Add(Type type, ClickhouseEntityConfiguration configuration)
35+
{
36+
_entityConfigurations.Add(type, configuration);
37+
}
38+
39+
internal ClickhouseEntityConfiguration? GetConfiguration<T>()
40+
{
41+
return _entityConfigurations.GetValueOrDefault(typeof(T));
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using ClickHouse.Client.Copy;
2+
using Microsoft.Extensions.Options;
3+
4+
namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse;
5+
6+
/// <summary>
7+
/// DapperContext that specialized for clickhouse.
8+
/// </summary>
9+
public abstract class ClickhouseDapperContext : DapperContext
10+
{
11+
private readonly ClickhouseContextOptions _options;
12+
13+
/// <summary>
14+
/// Create a <see cref="ClickhouseDapperContext"/>.
15+
/// </summary>
16+
/// <param name="dbConnectionFactoryCollection">The underlying <see cref="IDbConnectionFactory"/> collection.</param>
17+
/// <param name="options">The options used for this context.</param>
18+
protected ClickhouseDapperContext(
19+
IOptions<DbConnectionFactoryCollection> dbConnectionFactoryCollection,
20+
ClickhouseContextOptions options)
21+
: base(dbConnectionFactoryCollection)
22+
{
23+
_options = options;
24+
}
25+
26+
/// <summary>
27+
/// Init context, register models, etc.
28+
/// </summary>
29+
public void Init()
30+
{
31+
var builder = new ClickhouseModelCollectionBuilder();
32+
ConfigureModels(builder);
33+
builder.Build(_options);
34+
}
35+
36+
/// <summary>
37+
/// Configure models that related to this context.
38+
/// </summary>
39+
/// <param name="builder"><see cref="ClickhouseModelCollectionBuilder"/>.</param>
40+
protected abstract void ConfigureModels(ClickhouseModelCollectionBuilder builder);
41+
42+
/// <summary>
43+
/// Bulk write entities to clickhouse.
44+
/// </summary>
45+
/// <param name="entities">The entity to be written.</param>
46+
/// <typeparam name="T">The type of entity.</typeparam>
47+
/// <exception cref="InvalidOperationException">Throw when <typeparamref name="T"/> is not registered.</exception>
48+
public async Task BulkWriteAsync<T>(IEnumerable<T> entities)
49+
where T : class
50+
{
51+
var configuration = _options.GetConfiguration<T>();
52+
if (configuration is null)
53+
{
54+
throw new InvalidOperationException(
55+
$"The model type {typeof(T).Name} is not registered, make sure you have called builder.Entity<T>() at ConfigureModels()");
56+
}
57+
58+
using var bulkCopyInterface = new ClickHouseBulkCopy(_options.ConnectionString)
59+
{
60+
DestinationTableName = configuration.TableName
61+
};
62+
63+
var objs = entities.Select(x => configuration.ToObjectArray(x));
64+
await bulkCopyInterface.WriteToServerAsync(objs, configuration.ColumnNames);
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Reflection;
2+
3+
namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse;
4+
5+
internal record ClickhouseEntityConfiguration(string TableName, PropertyInfo[] Properties, string[] ColumnNames)
6+
{
7+
internal object?[] ToObjectArray(object entity)
8+
{
9+
return Properties.Select(x => x.GetValue(entity)).ToArray();
10+
}
11+
}

0 commit comments

Comments
 (0)