From cd1ed2d209388f4b56b8a52c11ec0cc63ff20281 Mon Sep 17 00:00:00 2001 From: Alex Soffronow-Pagonidis Date: Tue, 7 Jul 2026 15:38:06 +0200 Subject: [PATCH 1/3] Tag ClickHouse User-Agent with lib identity ClickHouse.Driver 1.3.0 adds ClickHouseClientSettings.ApplicationInfo, a set of free-form tags appended to the HTTP User-Agent for per-application query attribution. Set the "lib" tag to "ClickHouse.EntityFrameworkCore" on the connections/data sources the provider constructs so queries issued through EF Core are attributable server-side. Only the connection-string path is tagged; when the caller supplies their own DbConnection or DbDataSource the provider does not construct the client and leaves it alone. - Bump ClickHouse.Driver 1.1.0 -> 1.3.0. - Add ClickHouseClientIdentity.CreateSettings helper. - Build the pooled ClickHouseDataSource from tagged settings (ClickHouseDataSourceManager) and apply the tag to the fallback ClickHouseConnection in CreateDbConnection. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../EFCore.ClickHouse.csproj | 2 +- .../Internal/ClickHouseClientIdentity.cs | 26 +++++++++++++++++++ .../Internal/ClickHouseDataSourceManager.cs | 3 ++- .../ClickHouseRelationalConnection.cs | 7 ++--- 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 src/EFCore.ClickHouse/Storage/Internal/ClickHouseClientIdentity.cs diff --git a/src/EFCore.ClickHouse/EFCore.ClickHouse.csproj b/src/EFCore.ClickHouse/EFCore.ClickHouse.csproj index 2e616f1..9d36a85 100644 --- a/src/EFCore.ClickHouse/EFCore.ClickHouse.csproj +++ b/src/EFCore.ClickHouse/EFCore.ClickHouse.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/EFCore.ClickHouse/Storage/Internal/ClickHouseClientIdentity.cs b/src/EFCore.ClickHouse/Storage/Internal/ClickHouseClientIdentity.cs new file mode 100644 index 0000000..b101cb0 --- /dev/null +++ b/src/EFCore.ClickHouse/Storage/Internal/ClickHouseClientIdentity.cs @@ -0,0 +1,26 @@ +using ClickHouse.Driver.ADO; + +namespace ClickHouse.EntityFrameworkCore.Storage.Internal; + +/// +/// Tags the ClickHouse client's User-Agent with this provider's identity so that +/// queries it issues are attributable server-side via +/// (ClickHouse.Driver 1.3.0+). +/// +internal static class ClickHouseClientIdentity +{ + /// + /// Value of the lib User-Agent tag identifying this library. + /// + internal const string LibraryName = "ClickHouse.EntityFrameworkCore"; + + /// + /// Builds from a connection string with the + /// lib User-Agent tag set to . + /// + internal static ClickHouseClientSettings CreateSettings(string connectionString) + => new(connectionString) + { + ApplicationInfo = new Dictionary { ["lib"] = LibraryName }, + }; +} diff --git a/src/EFCore.ClickHouse/Storage/Internal/ClickHouseDataSourceManager.cs b/src/EFCore.ClickHouse/Storage/Internal/ClickHouseDataSourceManager.cs index 47993e3..6d68c90 100644 --- a/src/EFCore.ClickHouse/Storage/Internal/ClickHouseDataSourceManager.cs +++ b/src/EFCore.ClickHouse/Storage/Internal/ClickHouseDataSourceManager.cs @@ -29,7 +29,8 @@ private ClickHouseDataSource GetOrCreateDataSource(string connectionString, bool if (_dataSources.TryGetValue(effectiveConnectionString, out var existing)) return existing; - var newDataSource = new ClickHouseDataSource(effectiveConnectionString); + var newDataSource = new ClickHouseDataSource( + ClickHouseClientIdentity.CreateSettings(effectiveConnectionString)); var added = _dataSources.GetOrAdd(effectiveConnectionString, newDataSource); if (!ReferenceEquals(added, newDataSource)) diff --git a/src/EFCore.ClickHouse/Storage/Internal/ClickHouseRelationalConnection.cs b/src/EFCore.ClickHouse/Storage/Internal/ClickHouseRelationalConnection.cs index 680d691..33f7084 100644 --- a/src/EFCore.ClickHouse/Storage/Internal/ClickHouseRelationalConnection.cs +++ b/src/EFCore.ClickHouse/Storage/Internal/ClickHouseRelationalConnection.cs @@ -37,9 +37,10 @@ private ClickHouseRelationalConnection( protected override DbConnection CreateDbConnection() => _dataSource?.CreateConnection() ?? new ClickHouseConnection( - _joinNullSemanticsDisabled - ? ConnectionString! - : ClickHouseDataSourceManager.EnsureDefaultSettings(ConnectionString!)); + ClickHouseClientIdentity.CreateSettings( + _joinNullSemanticsDisabled + ? ConnectionString! + : ClickHouseDataSourceManager.EnsureDefaultSettings(ConnectionString!))); protected override bool SupportsAmbientTransactions => false; From 7bec48e22aa64065a35ef802061914bb0316cdf2 Mon Sep 17 00:00:00 2001 From: Alex Soffronow-Pagonidis Date: Tue, 7 Jul 2026 16:16:13 +0200 Subject: [PATCH 2/3] Bump test-project ClickHouse.Driver to 1.3.0 The functional and unit test projects referenced ClickHouse.Driver 1.1.0 directly, which downgrades below the provider's 1.3.0 reference (NU1605 as error) after the bump. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../EFCore.ClickHouse.FunctionalTests.csproj | 2 +- test/EFCore.ClickHouse.Tests/EFCore.ClickHouse.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/EFCore.ClickHouse.FunctionalTests/EFCore.ClickHouse.FunctionalTests.csproj b/test/EFCore.ClickHouse.FunctionalTests/EFCore.ClickHouse.FunctionalTests.csproj index 7f51093..6b2c019 100644 --- a/test/EFCore.ClickHouse.FunctionalTests/EFCore.ClickHouse.FunctionalTests.csproj +++ b/test/EFCore.ClickHouse.FunctionalTests/EFCore.ClickHouse.FunctionalTests.csproj @@ -9,7 +9,7 @@ - + all diff --git a/test/EFCore.ClickHouse.Tests/EFCore.ClickHouse.Tests.csproj b/test/EFCore.ClickHouse.Tests/EFCore.ClickHouse.Tests.csproj index e570710..9f28695 100644 --- a/test/EFCore.ClickHouse.Tests/EFCore.ClickHouse.Tests.csproj +++ b/test/EFCore.ClickHouse.Tests/EFCore.ClickHouse.Tests.csproj @@ -9,7 +9,7 @@ - + From 875ea96d1e2c428f55637eee89799f56e752f8be Mon Sep 17 00:00:00 2001 From: Alex Soffronow-Pagonidis Date: Tue, 7 Jul 2026 16:53:58 +0200 Subject: [PATCH 3/3] Add functional test asserting lib User-Agent in query_log Runs a query through an EF Core DbContext, flushes system.query_log, and verifies http_user_agent carries lib:ClickHouse.EntityFrameworkCore. Verification uses a separate untagged connection so the tag can only originate from the provider. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../UserAgentClickHouseTest.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/EFCore.ClickHouse.FunctionalTests/UserAgentClickHouseTest.cs diff --git a/test/EFCore.ClickHouse.FunctionalTests/UserAgentClickHouseTest.cs b/test/EFCore.ClickHouse.FunctionalTests/UserAgentClickHouseTest.cs new file mode 100644 index 0000000..d0a3ba4 --- /dev/null +++ b/test/EFCore.ClickHouse.FunctionalTests/UserAgentClickHouseTest.cs @@ -0,0 +1,57 @@ +using ClickHouse.Driver.ADO; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Verifies end-to-end that the provider tags its ClickHouse HTTP requests with the +/// lib User-Agent token, by reading it back from system.query_log. +/// +public class UserAgentClickHouseTest +{ + private sealed class ProbeContext(string connectionString) : DbContext + { + private readonly string _connectionString = connectionString; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseClickHouse(_connectionString); + } + + [Fact] + public async Task Queries_are_tagged_with_lib_in_user_agent() + { + var connectionString = TestEnvironment.DefaultConnection; + var marker = $"ua_probe_{Guid.NewGuid():N}"; + + // Issue a query through EF Core's (tagged) connection. The marker (a GUID) is + // concatenated rather than interpolated to keep clear of EF1002. + var probeSql = "SELECT 1 /* " + marker + " */"; + await using (var context = new ProbeContext(connectionString)) + { + await context.Database.ExecuteSqlRawAsync(probeSql); + } + + // Use a separate, untagged connection for verification so the lib tag we assert + // on can only have come from the provider's own connection. + await using var connection = new ClickHouseConnection(connectionString); + await connection.OpenAsync(); + + using (var flush = connection.CreateCommand()) + { + flush.CommandText = "SYSTEM FLUSH LOGS"; + await flush.ExecuteNonQueryAsync(); + } + + using var query = connection.CreateCommand(); + query.CommandText = + "SELECT count() FROM system.query_log " + + $"WHERE query LIKE '%{marker}%' " + + "AND http_user_agent LIKE '%lib:ClickHouse.EntityFrameworkCore%' " + + "AND event_time > now() - INTERVAL 5 MINUTE"; + + var count = Convert.ToInt64(await query.ExecuteScalarAsync()); + Assert.True(count > 0, + "Expected EF Core's query to carry lib:ClickHouse.EntityFrameworkCore in the HTTP User-Agent."); + } +}