From a0b5ab7c043bcae59605bb2ef3190831abe96706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E6=98=9F=E7=B9=81?= Date: Thu, 2 Mar 2023 16:41:24 +0800 Subject: [PATCH] feat: add clickhouse support --- Cnblogs.Architecture.sln | 14 ++++ .../ClickhouseContextCollection.cs | 14 ++++ .../ClickhouseDbConnectionFactory.cs | 28 ++++++++ .../ClickhouseInitializeHostedService.cs | 47 +++++++++++++ ...itecture.Ddd.Cqrs.Dapper.Clickhouse.csproj | 14 ++++ .../DapperConfigurationBuilderExtension.cs | 29 ++++++++ .../DapperConfigurationBuilderExtension.cs | 20 +++--- .../DapperConfigurationBuilder.cs | 21 +++--- .../ServiceCollectionInjector.cs | 6 +- .../ClickhouseContextOptions.cs | 43 ++++++++++++ .../ClickhouseDapperContext.cs | 66 +++++++++++++++++++ .../ClickhouseEntityConfiguration.cs | 11 ++++ .../ClickhouseModelBuilder.cs | 64 ++++++++++++++++++ .../ClickhouseModelCollectionBuilder.cs | 30 +++++++++ .../ClickhouseModelPropertyBuilder.cs | 48 ++++++++++++++ ...dd.Infrastructure.Dapper.Clickhouse.csproj | 19 ++++++ .../IClickhouseModelBuilder.cs | 6 ++ .../DapperContext.cs | 2 +- .../DbConnectionFactoryCollection.cs | 9 +-- 19 files changed, 467 insertions(+), 24 deletions(-) create mode 100644 src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseContextCollection.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseInitializeHostedService.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse.csproj create mode 100644 src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseContextOptions.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseDapperContext.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseEntityConfiguration.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelBuilder.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelCollectionBuilder.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelPropertyBuilder.cs create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj create mode 100644 src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/IClickhouseModelBuilder.cs diff --git a/Cnblogs.Architecture.sln b/Cnblogs.Architecture.sln index 560d570..3e04f06 100644 --- a/Cnblogs.Architecture.sln +++ b/Cnblogs.Architecture.sln @@ -62,6 +62,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.Ddd.In EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cnblogs.Architecture.TestShared", "test\Cnblogs.Architecture.TestShared\Cnblogs.Architecture.TestShared.csproj", "{3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}" EndProject +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}" +EndProject +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}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +98,8 @@ Global {E7ABC399-AF71-46A6-A4D4-A38972BC7D50} = {D3A6DF01-017E-4088-936C-B3791F41DF53} {A6A8FDC5-20E7-4776-9CB6-A2E43DCCBE7B} = {D3A6DF01-017E-4088-936C-B3791F41DF53} {3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F} = {772497F8-2CB1-4EA6-AEB8-482C3ECD0A9D} + {73665E32-3D10-4F71-B893-4C65F36332D0} = {D3A6DF01-017E-4088-936C-B3791F41DF53} + {4BD98FBF-FB98-4172-B352-BB7BF8761FCB} = {D3A6DF01-017E-4088-936C-B3791F41DF53} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {54D9D850-1CFC-485E-97FE-87F41C220523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -200,5 +206,13 @@ Global {3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {3B22F0CC-9A61-4D95-8ED9-F41B7FCBFC6F}.Release|Any CPU.Build.0 = Release|Any CPU + {73665E32-3D10-4F71-B893-4C65F36332D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73665E32-3D10-4F71-B893-4C65F36332D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73665E32-3D10-4F71-B893-4C65F36332D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73665E32-3D10-4F71-B893-4C65F36332D0}.Release|Any CPU.Build.0 = Release|Any CPU + {4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BD98FBF-FB98-4172-B352-BB7BF8761FCB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseContextCollection.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseContextCollection.cs new file mode 100644 index 0000000..d93603d --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseContextCollection.cs @@ -0,0 +1,14 @@ +namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; + +/// +/// The collection for clickhouse contexts. +/// +public class ClickhouseContextCollection +{ + internal List ContextTypes { get; } = new(); + + internal void Add() + { + ContextTypes.Add(typeof(TContext)); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs new file mode 100644 index 0000000..6021841 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs @@ -0,0 +1,28 @@ +using System.Data; +using ClickHouse.Client.ADO; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; + +namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; + +/// +/// Clickhouse connection factory. +/// +public class ClickhouseDbConnectionFactory : IDbConnectionFactory +{ + private readonly string _connectionString; + + /// + /// Create a clickhouse connection factory. + /// + /// The connection string. + public ClickhouseDbConnectionFactory(string connectionString) + { + _connectionString = connectionString; + } + + /// + public IDbConnection CreateDbConnection() + { + return new ClickHouseConnection(_connectionString); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseInitializeHostedService.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseInitializeHostedService.cs new file mode 100644 index 0000000..94420e4 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseInitializeHostedService.cs @@ -0,0 +1,47 @@ +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; + +/// +/// Hosed service to initialize clickhouse contexts. +/// +public class ClickhouseInitializeHostedService : IHostedService +{ + private readonly ClickhouseContextCollection _collection; + private readonly IServiceProvider _serviceProvider; + + /// + /// Create a . + /// + /// The contexts been registered. + /// The provider for contexts. + public ClickhouseInitializeHostedService( + IOptions collections, + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + _collection = collections.Value; + } + + /// + public Task StartAsync(CancellationToken cancellationToken) + { + using var scope = _serviceProvider.CreateScope(); + foreach (var collectionContextType in _collection.ContextTypes) + { + var context = scope.ServiceProvider.GetRequiredService(collectionContextType) as ClickhouseDapperContext; + context?.Init(); + } + + return Task.CompletedTask; + } + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse.csproj b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse.csproj new file mode 100644 index 0000000..d482735 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse.csproj @@ -0,0 +1,14 @@ + + + + + Provides clickhouse dapper integration. + + + + + + + + + diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs new file mode 100644 index 0000000..590eace --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs @@ -0,0 +1,29 @@ +using Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; +using Microsoft.Extensions.DependencyInjection; + +// ReSharper disable once CheckNamespace +namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper; + +/// +/// Extension methods for inject clickhouse to dapper context. +/// +public static class DapperConfigurationBuilderExtension +{ + /// + /// Use clickhouse as the underlying database. + /// + /// . + /// The connection string for clickhouse. + /// The context type been used. + public static void UseClickhouse( + this DapperConfigurationBuilder builder, + string connectionString) + where TContext : ClickhouseDapperContext + { + builder.UseDbConnectionFactory(new ClickhouseDbConnectionFactory(connectionString)); + builder.Services.AddSingleton(new ClickhouseContextOptions(connectionString)); + builder.Services.Configure(x => x.Add()); + builder.Services.AddHostedService(); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/DapperConfigurationBuilderExtension.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/DapperConfigurationBuilderExtension.cs index 0e46e57..496efc5 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/DapperConfigurationBuilderExtension.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/DapperConfigurationBuilderExtension.cs @@ -1,19 +1,23 @@ -using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; -namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer; +// ReSharper disable once CheckNamespace +namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper; /// -/// 用于配置 Dapper Configuration 的扩展方法。 +/// Extension methods to configure dapper context. /// public static class DapperConfigurationBuilderExtension { /// - /// 使用 SqlServer 配置 + /// Configure to use sql server as underlying database. /// - /// - /// 连接字符串。 - public static void UseSqlServer(this DapperConfigurationBuilder builder, string connectionString) + /// + /// The connection string for sql server. + /// The type of context been configured. + public static void UseSqlServer(this DapperConfigurationBuilder builder, string connectionString) + where TContext : DapperContext { builder.UseDbConnectionFactory(new SqlServerDbConnectionFactory(connectionString)); } -} \ No newline at end of file +} diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs index c04f9cb..95cb79a 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs @@ -7,20 +7,20 @@ namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper; /// /// Dapper 配置类。 /// -public class DapperConfigurationBuilder +/// The context type been configured. +public class DapperConfigurationBuilder + where TContext : DapperContext { - private readonly IServiceCollection _services; private readonly string _dapperContextTypeName; /// /// 创建一个 DapperConfigurationBuilder。 /// - /// 正在配置的 DapperContext 名称。 /// - public DapperConfigurationBuilder(string dapperContextTypeName, IServiceCollection services) + public DapperConfigurationBuilder(IServiceCollection services) { - _dapperContextTypeName = dapperContextTypeName; - _services = services; + _dapperContextTypeName = typeof(TContext).Name; + Services = services; } /// @@ -31,7 +31,12 @@ public DapperConfigurationBuilder(string dapperContextTypeName, IServiceCollecti public void UseDbConnectionFactory(TFactory factory) where TFactory : IDbConnectionFactory { - _services.Configure( + Services.Configure( c => c.AddDbConnectionFactory(_dapperContextTypeName, factory)); } -} \ No newline at end of file + + /// + /// The underlying . + /// + public IServiceCollection Services { get; } +} diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs index 95970e6..f5a3080 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs @@ -15,10 +15,10 @@ public static class ServiceCollectionInjector /// 。 /// 的实现类型。 /// - public static DapperConfigurationBuilder AddDapperContext(this IServiceCollection services) + public static DapperConfigurationBuilder AddDapperContext(this IServiceCollection services) where TContext : DapperContext { services.AddScoped(); - return new DapperConfigurationBuilder(typeof(TContext).Name, services); + return new DapperConfigurationBuilder(services); } -} \ No newline at end of file +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseContextOptions.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseContextOptions.cs new file mode 100644 index 0000000..7aa7869 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseContextOptions.cs @@ -0,0 +1,43 @@ +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +/// +/// The options for clickhouse context. +/// +/// The type of been configured. +public class ClickhouseContextOptions : ClickhouseContextOptions + where TContext : ClickhouseDapperContext +{ + /// + /// Create a with given connection string. + /// + /// The connection string for clickhouse. + public ClickhouseContextOptions(string connectionString) + : base(connectionString) + { + } +} + +/// +/// The options for . +/// +public class ClickhouseContextOptions +{ + private readonly Dictionary _entityConfigurations = new(); + + internal ClickhouseContextOptions(string connectionString) + { + ConnectionString = connectionString; + } + + internal string ConnectionString { get; } + + internal void Add(Type type, ClickhouseEntityConfiguration configuration) + { + _entityConfigurations.Add(type, configuration); + } + + internal ClickhouseEntityConfiguration? GetConfiguration() + { + return _entityConfigurations.GetValueOrDefault(typeof(T)); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseDapperContext.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseDapperContext.cs new file mode 100644 index 0000000..bd12207 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseDapperContext.cs @@ -0,0 +1,66 @@ +using ClickHouse.Client.Copy; +using Microsoft.Extensions.Options; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +/// +/// DapperContext that specialized for clickhouse. +/// +public abstract class ClickhouseDapperContext : DapperContext +{ + private readonly ClickhouseContextOptions _options; + + /// + /// Create a . + /// + /// The underlying collection. + /// The options used for this context. + protected ClickhouseDapperContext( + IOptions dbConnectionFactoryCollection, + ClickhouseContextOptions options) + : base(dbConnectionFactoryCollection) + { + _options = options; + } + + /// + /// Init context, register models, etc. + /// + public void Init() + { + var builder = new ClickhouseModelCollectionBuilder(); + ConfigureModels(builder); + builder.Build(_options); + } + + /// + /// Configure models that related to this context. + /// + /// . + protected abstract void ConfigureModels(ClickhouseModelCollectionBuilder builder); + + /// + /// Bulk write entities to clickhouse. + /// + /// The entity to be written. + /// The type of entity. + /// Throw when is not registered. + public async Task BulkWriteAsync(IEnumerable entities) + where T : class + { + var configuration = _options.GetConfiguration(); + if (configuration is null) + { + throw new InvalidOperationException( + $"The model type {typeof(T).Name} is not registered, make sure you have called builder.Entity() at ConfigureModels()"); + } + + using var bulkCopyInterface = new ClickHouseBulkCopy(_options.ConnectionString) + { + DestinationTableName = configuration.TableName + }; + + var objs = entities.Select(x => configuration.ToObjectArray(x)); + await bulkCopyInterface.WriteToServerAsync(objs, configuration.ColumnNames); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseEntityConfiguration.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseEntityConfiguration.cs new file mode 100644 index 0000000..2cb45aa --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseEntityConfiguration.cs @@ -0,0 +1,11 @@ +using System.Reflection; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +internal record ClickhouseEntityConfiguration(string TableName, PropertyInfo[] Properties, string[] ColumnNames) +{ + internal object?[] ToObjectArray(object entity) + { + return Properties.Select(x => x.GetValue(entity)).ToArray(); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelBuilder.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelBuilder.cs new file mode 100644 index 0000000..3850e69 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelBuilder.cs @@ -0,0 +1,64 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +/// +/// Configure mapping between clickhouse and C# class. +/// +/// The type of model been configured. +public class ClickhouseModelBuilder : IClickhouseModelBuilder +{ + private readonly Dictionary> _propertyBuilders; + private string _tableName; + + internal ClickhouseModelBuilder() + { + _tableName = typeof(T).Name; + _propertyBuilders = typeof(T).GetProperties().Where(x => x.GetGetMethod() != null) + .Select(x => new ClickhouseModelPropertyBuilder(this, x)).ToDictionary(x => x.PropertyInfo.Name, x => x); + } + + /// + /// Map model type to specific table. + /// + /// The full name of table, includes database name. e.x. <database>.<table> + /// . + public ClickhouseModelBuilder ToTable(string tableName) + { + _tableName = tableName; + return this; + } + + /// + /// Start configure property. + /// + /// The property been configured. + /// The type of property. + /// . + /// When no suitable property was fount by . + public ClickhouseModelPropertyBuilder Property(Expression> propertyGetter) + { + if ((propertyGetter.Body as MemberExpression)?.Member is not PropertyInfo propertyInfo) + { + throw new InvalidOperationException("No suitable property can be found from given expression"); + } + + var builder = _propertyBuilders.GetValueOrDefault(propertyInfo.Name); + if (builder is null) + { + throw new InvalidOperationException($"No suitable property was found by name: {propertyInfo.Name}"); + } + + return builder; + } + + ClickhouseEntityConfiguration IClickhouseModelBuilder.Build() + { + var builders = _propertyBuilders.Values.Where(x => x.IsIgnored == false).ToArray(); + return new ClickhouseEntityConfiguration( + _tableName, + builders.Select(x => x.PropertyInfo).ToArray(), + builders.Select(x => x.ColumnName).ToArray()); + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelCollectionBuilder.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelCollectionBuilder.cs new file mode 100644 index 0000000..f755002 --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelCollectionBuilder.cs @@ -0,0 +1,30 @@ +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +/// +/// Configure models for clickhouse. +/// +public class ClickhouseModelCollectionBuilder +{ + private readonly Dictionary _builders = new(); + + /// + /// Register an entity to context. + /// + /// The type of entity. + /// . + public ClickhouseModelBuilder Entity() + where T : class + { + var builder = new ClickhouseModelBuilder(); + _builders.Add(typeof(T), builder); + return builder; + } + + internal void Build(ClickhouseContextOptions options) + { + foreach (var (key, value) in _builders) + { + options.Add(key, value.Build()); + } + } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelPropertyBuilder.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelPropertyBuilder.cs new file mode 100644 index 0000000..066d26c --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/ClickhouseModelPropertyBuilder.cs @@ -0,0 +1,48 @@ +using System.Reflection; + +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +/// +/// Configuration builder for clickhouse model property; +/// +/// The entity type that property belongs. +public class ClickhouseModelPropertyBuilder +{ + /// + /// Create a ClickhouseModelPropertyBuilder from entity builder. + /// + /// The parent entity builder. + /// The property been configured. + public ClickhouseModelPropertyBuilder(ClickhouseModelBuilder entityBuilder, PropertyInfo propertyInfo) + { + PropertyInfo = propertyInfo; + ColumnName = propertyInfo.Name; + } + + /// + /// Configure column name for this property. + /// + /// New column name. + /// + public ClickhouseModelPropertyBuilder HasColumnName(string name) + { + ColumnName = name; + return this; + } + + /// + /// Ignore this property from mapping. + /// + /// + public ClickhouseModelPropertyBuilder Ignore() + { + IsIgnored = true; + return this; + } + + internal string ColumnName { get; private set; } + + internal PropertyInfo PropertyInfo { get; } + + internal bool IsIgnored { get; private set; } +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj new file mode 100644 index 0000000..dd522db --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse.csproj @@ -0,0 +1,19 @@ + + + + + Provides implementation for persistance layer of DDD with Dapper and Clickhouse. + Commonly used types: + Cnblogs.Architecture.Ddd.Infrasturcture.Dapper.Clickhouse.ClickhouseDapperContext + + + + + + + + + + + + diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/IClickhouseModelBuilder.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/IClickhouseModelBuilder.cs new file mode 100644 index 0000000..1e9a36d --- /dev/null +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse/IClickhouseModelBuilder.cs @@ -0,0 +1,6 @@ +namespace Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; + +internal interface IClickhouseModelBuilder +{ + ClickhouseEntityConfiguration Build(); +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DapperContext.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DapperContext.cs index f00ab04..77acd16 100644 --- a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DapperContext.cs +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DapperContext.cs @@ -28,4 +28,4 @@ protected DapperContext(IOptions dbConnectionFact /// /// public IDbConnection CreateDbConnection() => DbConnectionFactory.CreateDbConnection(); -} \ No newline at end of file +} diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DbConnectionFactoryCollection.cs b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DbConnectionFactoryCollection.cs index d99f62c..3b25a93 100644 --- a/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DbConnectionFactoryCollection.cs +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.Dapper/DbConnectionFactoryCollection.cs @@ -12,12 +12,13 @@ public class DbConnectionFactoryCollection /// /// 名称。 /// 工厂示例。 + /// Throw when already been registered with other factory. public void AddDbConnectionFactory(string name, IDbConnectionFactory factory) { - if (_factories.ContainsKey(name)) + if (_factories.TryGetValue(name, out var value)) { - _factories[name] = factory; - return; + throw new InvalidOperationException( + $"The dapper context already configured with db connection factory: {value.GetType().Name}"); } _factories.Add(name, factory); @@ -32,4 +33,4 @@ public IDbConnectionFactory GetFactory(string name) { return _factories[name]; } -} \ No newline at end of file +}