From 89241529e0b35280c6926615d8ce2b11d918423f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E6=98=9F=E7=B9=81?= Date: Thu, 28 Aug 2025 15:05:34 +0800 Subject: [PATCH] fix: isolate clickhouse db connection factory between different clickhouse db contexts --- .../ClickhouseDbConnectionFactory.cs | 5 +- .../DapperConfigurationBuilderExtension.cs | 8 ++- ...hitecture.Ddd.Cqrs.Dapper.SqlServer.csproj | 2 +- .../DapperConfigurationBuilder.cs | 13 ++++ .../ServiceCollectionInjector.cs | 6 ++ ...Infrastructure.CacheProviders.Redis.csproj | 2 +- ...itecture.Ddd.Infrastructure.MongoDb.csproj | 2 +- ...blogs.Architecture.IntegrationTests.csproj | 4 +- .../Cnblogs.Architecture.UnitTests.csproj | 3 +- .../ClickhouseDependencyInjectorTests.cs | 47 ++++++++++++++ .../Dapper/DapperConfigurationBuilderTests.cs | 63 +++++++++++++++++++ .../DapperServiceCollectionInjectorTests.cs | 32 ++++++++++ .../TestAlterClickhouseDapperContext.cs | 20 ++++++ .../Dapper/TestClickhouseDapperContext.cs | 22 +++++++ .../Cqrs/Dapper/TestDapperContext.cs | 15 +++++ .../Cqrs/Dapper/TestDbConnectionFactory.cs | 14 +++++ 16 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/ClickhouseDependencyInjectorTests.cs create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperConfigurationBuilderTests.cs create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperServiceCollectionInjectorTests.cs create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestAlterClickhouseDapperContext.cs create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestClickhouseDapperContext.cs create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDapperContext.cs create mode 100644 test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDbConnectionFactory.cs diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs index e2e43b7..461a19e 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/ClickhouseDbConnectionFactory.cs @@ -1,13 +1,16 @@ using System.Data; using ClickHouse.Client; using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; namespace Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; /// /// Clickhouse connection factory. /// -public class ClickhouseDbConnectionFactory(IClickHouseDataSource dataSource) : IDbConnectionFactory +/// The this connection factory belongs to. +public class ClickhouseDbConnectionFactory(IClickHouseDataSource dataSource) : IDbConnectionFactory + where TContext : ClickhouseDapperContext { /// public IDbConnection CreateDbConnection() diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs index bf5d921..3ae00f5 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse/DapperConfigurationBuilderExtension.cs @@ -1,3 +1,4 @@ +using ClickHouse.Client; using Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; using Microsoft.Extensions.DependencyInjection; @@ -21,8 +22,11 @@ public static void UseClickhouse( string connectionString) where TContext : ClickhouseDapperContext { - builder.UseDbConnectionFactory(); - builder.Services.AddClickHouseDataSource(connectionString); + var contextName = typeof(TContext).Name; + builder.UseDbConnectionFactory(sp + => new ClickhouseDbConnectionFactory( + sp.GetRequiredKeyedService(contextName))); + builder.Services.AddClickHouseDataSource(connectionString, serviceKey: contextName); 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/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer.csproj b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer.csproj index 89a9658..b0b49aa 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer.csproj +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer/Cnblogs.Architecture.Ddd.Cqrs.Dapper.SqlServer.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs index 62a3542..6673de2 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/DapperConfigurationBuilder.cs @@ -36,6 +36,19 @@ public void UseDbConnectionFactory(TFactory factory) c => c.AddDbConnectionFactory(_dapperContextTypeName, typeof(TFactory))); } + /// + /// Add by . + /// + /// The object initializer. + /// The type of the factory. + public void UseDbConnectionFactory(Func implementationFactory) + where TFactory : class + { + Services.AddSingleton(implementationFactory); + Services.Configure( + c => c.AddDbConnectionFactory(_dapperContextTypeName, typeof(TFactory))); + } + /// /// Add as and get instance from DI when used. /// diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs index f5a3080..b8d4bb2 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.Dapper/ServiceCollectionInjector.cs @@ -18,6 +18,12 @@ public static class ServiceCollectionInjector public static DapperConfigurationBuilder AddDapperContext(this IServiceCollection services) where TContext : DapperContext { + var alreadyAdded = services.Any(s => s.ServiceType == typeof(TContext)); + if (alreadyAdded) + { + throw new InvalidOperationException($"Dapper context with name {typeof(TContext).Name} already added"); + } + services.AddScoped(); return new DapperConfigurationBuilder(services); } diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj index 26ad41f..756e73b 100644 --- a/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis/Cnblogs.Architecture.Ddd.Infrastructure.CacheProviders.Redis.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj b/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj index 06072a5..18fdb2f 100644 --- a/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj +++ b/src/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb/Cnblogs.Architecture.Ddd.Infrastructure.MongoDb.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/Cnblogs.Architecture.IntegrationTests/Cnblogs.Architecture.IntegrationTests.csproj b/test/Cnblogs.Architecture.IntegrationTests/Cnblogs.Architecture.IntegrationTests.csproj index aae480f..535e7de 100644 --- a/test/Cnblogs.Architecture.IntegrationTests/Cnblogs.Architecture.IntegrationTests.csproj +++ b/test/Cnblogs.Architecture.IntegrationTests/Cnblogs.Architecture.IntegrationTests.csproj @@ -1,7 +1,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -9,7 +9,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/test/Cnblogs.Architecture.UnitTests/Cnblogs.Architecture.UnitTests.csproj b/test/Cnblogs.Architecture.UnitTests/Cnblogs.Architecture.UnitTests.csproj index 1f86a30..3406913 100644 --- a/test/Cnblogs.Architecture.UnitTests/Cnblogs.Architecture.UnitTests.csproj +++ b/test/Cnblogs.Architecture.UnitTests/Cnblogs.Architecture.UnitTests.csproj @@ -3,7 +3,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -15,6 +15,7 @@ + diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/ClickhouseDependencyInjectorTests.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/ClickhouseDependencyInjectorTests.cs new file mode 100644 index 0000000..a02e036 --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/ClickhouseDependencyInjectorTests.cs @@ -0,0 +1,47 @@ +using ClickHouse.Client; +using Cnblogs.Architecture.Ddd.Cqrs.Dapper; +using Cnblogs.Architecture.Ddd.Cqrs.Dapper.Clickhouse; +using Microsoft.Extensions.DependencyInjection; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class ClickhouseDependencyInjectorTests +{ + [Fact] + public void UseClickhouse_InjectSingle_Injected() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDapperContext().UseClickhouse("HOST=clickhouse"); + var serviceProvider = services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + var clickhouseDataSource = + serviceProvider.GetRequiredKeyedService(nameof(TestClickhouseDapperContext)); + + // Assert + Assert.NotNull(dbContext); + Assert.NotNull(clickhouseDataSource); + } + + [Fact] + public void UseClickhouse_InjectMultiple_Injected() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDapperContext().UseClickhouse("HOST=clickhouse"); + services.AddDapperContext().UseClickhouse("HOST=alterclickhouse"); + var serviceProvider = services.BuildServiceProvider(); + var dbContext = serviceProvider.GetRequiredService(); + var alterContext = serviceProvider.GetRequiredService(); + var dbFactory = dbContext.Factory; + var alterFactory = alterContext.Factory; + + // Assert + Assert.True(dbFactory is ClickhouseDbConnectionFactory); + Assert.True(alterFactory is ClickhouseDbConnectionFactory); + } +} diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperConfigurationBuilderTests.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperConfigurationBuilderTests.cs new file mode 100644 index 0000000..f506cbf --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperConfigurationBuilderTests.cs @@ -0,0 +1,63 @@ +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class DapperConfigurationBuilderTests +{ + [Fact] + public void UseDbConnectionFactory_Instance_AddInstance() + { + // Arrange + var services = new ServiceCollection(); + var factory = new TestDbConnectionFactory(); + + // Act + services.AddDapperContext().UseDbConnectionFactory(factory); + var serviceProvider = services.BuildServiceProvider(); + var fetchedFactory = serviceProvider.GetService(); + var collection = serviceProvider.GetRequiredService>().Value; + + // Assert + Assert.Equal(factory, fetchedFactory); + Assert.Equal(typeof(TestDbConnectionFactory), collection.GetFactory(nameof(TestDapperContext))); + } + + [Fact] + public void UseDbConnectionFactory_Type_AddType() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDapperContext().UseDbConnectionFactory(); + var serviceProvider = services.BuildServiceProvider(); + var fetchedFactory = serviceProvider.GetService(); + var collection = serviceProvider.GetRequiredService>().Value; + + // Assert + Assert.NotNull(fetchedFactory); + Assert.Equal(typeof(TestDbConnectionFactory), collection.GetFactory(nameof(TestDapperContext))); + } + + [Fact] + public void UseDbConnectionFactory_Func_AddFunc() + { + // Arrange + var services = new ServiceCollection(); + var factory = new TestDbConnectionFactory(); + services.AddKeyedSingleton("test", factory); + + // Act + services.AddDapperContext() + .UseDbConnectionFactory(sp => sp.GetRequiredKeyedService("test")); + var serviceProvider = services.BuildServiceProvider(); + var fetchedFactory = serviceProvider.GetRequiredService(); + var collection = serviceProvider.GetRequiredService>().Value; + + // Assert + Assert.Equal(factory, fetchedFactory); + Assert.Equal(typeof(TestDbConnectionFactory), collection.GetFactory(nameof(TestDapperContext))); + } +} diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperServiceCollectionInjectorTests.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperServiceCollectionInjectorTests.cs new file mode 100644 index 0000000..0219564 --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/DapperServiceCollectionInjectorTests.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class DapperServiceCollectionInjectorTests +{ + [Fact] + public void AddDapperContext_AddContext_Success() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDapperContext(); + + // Assert + Assert.Contains(services, s => s.ServiceType == typeof(TestDapperContext)); + } + + [Fact] + public void AddDapperContext_AddSameContextMultipleTimes_ThrowsException() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDapperContext(); + + // Assert + Assert.Throws(() => services.AddDapperContext()); + } +} diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestAlterClickhouseDapperContext.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestAlterClickhouseDapperContext.cs new file mode 100644 index 0000000..ea0b88f --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestAlterClickhouseDapperContext.cs @@ -0,0 +1,20 @@ +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; +using Microsoft.Extensions.Options; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class TestAlterClickhouseDapperContext( + IOptions dbConnectionFactoryCollection, + ClickhouseContextOptions options, + IServiceProvider serviceProvider) + : ClickhouseDapperContext(dbConnectionFactoryCollection, options, serviceProvider) +{ + /// + protected override void ConfigureModels(ClickhouseModelCollectionBuilder builder) + { + // ignore + } + + public IDbConnectionFactory Factory => DbConnectionFactory; +} diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestClickhouseDapperContext.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestClickhouseDapperContext.cs new file mode 100644 index 0000000..ccc16cc --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestClickhouseDapperContext.cs @@ -0,0 +1,22 @@ +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper.Clickhouse; +using Microsoft.Extensions.Options; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class TestClickhouseDapperContext( + IOptions dbConnectionFactoryCollection, + ClickhouseContextOptions options, + IServiceProvider serviceProvider) + : ClickhouseDapperContext(dbConnectionFactoryCollection, options, serviceProvider) +{ + public bool ConfigureModelsCalled { get; set; } + + public IDbConnectionFactory Factory => DbConnectionFactory; + + /// + protected override void ConfigureModels(ClickhouseModelCollectionBuilder builder) + { + ConfigureModelsCalled = true; + } +} diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDapperContext.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDapperContext.cs new file mode 100644 index 0000000..1bf36b5 --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDapperContext.cs @@ -0,0 +1,15 @@ +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using Microsoft.Extensions.Options; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class TestDapperContext : DapperContext +{ + /// + public TestDapperContext( + IOptions dbConnectionFactoryCollection, + IServiceProvider sp) + : base(dbConnectionFactoryCollection, sp) + { + } +} diff --git a/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDbConnectionFactory.cs b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDbConnectionFactory.cs new file mode 100644 index 0000000..7fca503 --- /dev/null +++ b/test/Cnblogs.Architecture.UnitTests/Cqrs/Dapper/TestDbConnectionFactory.cs @@ -0,0 +1,14 @@ +using System.Data; +using Cnblogs.Architecture.Ddd.Infrastructure.Dapper; +using NSubstitute; + +namespace Cnblogs.Architecture.UnitTests.Cqrs.Dapper; + +public class TestDbConnectionFactory : IDbConnectionFactory +{ + /// + public IDbConnection CreateDbConnection() + { + return Substitute.For(); + } +}