-
ClickHouseClient(recommandé) : un client de haut niveau, thread-safe, conçu pour être utilisé comme singleton. Il fournit une API asynchrone simple pour les requêtes et les insertions en masse. C’est le meilleur choix pour la plupart des applications. -
ADO.NET (
ClickHouseDataSource,ClickHouseConnection,ClickHouseCommand) : les abstractions standard de base de données de .NET. Indispensable pour l’intégration avec les ORM (Dapper, Linq2db) et lorsque vous avez besoin de la compatibilité ADO.NET.ClickHouseBulkCopyest une classe utilitaire qui permet d’insérer efficacement des données à l’aide d’une connexion ADO.NET.ClickHouseBulkCopyest obsolète et sera supprimé dans une prochaine version ; utilisez plutôtClickHouseClient.InsertBinaryAsync.
Guide de migration
- Mettez à jour votre fichier
.csprojavec le nouveau nom du paquetClickHouse.Driveret la dernière version disponible sur NuGet. - Remplacez dans votre code toutes les références à
ClickHouse.ClientparClickHouse.Driver.
Versions de .NET prises en charge
ClickHouse.Driver prend en charge les versions de .NET suivantes :
- .NET 6.0
- .NET 8.0
- .NET 9.0
- .NET 10.0
Versions de ClickHouse prises en charge
Le client prend officiellement en charge les 3 dernières versions, ainsi que les deux dernières versions LTS.Installation
Installez le paquet à partir de NuGet :Démarrage rapide
Configuration
Il existe deux façons de configurer votre connexion à ClickHouse :- Chaîne de connexion : paires clé-valeur séparées par des points-virgules, qui indiquent l’hôte, les informations d’authentification et d’autres options de connexion.
- Objet
ClickHouseClientSettings: objet de configuration fortement typé, qui peut être chargé depuis des fichiers de configuration ou défini dans le code.
Paramètres de connexion
Format des données et sérialisation
Gestion des sessions
L’option
UseSession active la persistance de la session du serveur, ce qui permet d’utiliser des instructions SET et des tables temporaires. Les sessions sont réinitialisées après 60 secondes d’inactivité (délai d’expiration par défaut). La durée de vie de la session peut être prolongée en définissant des paramètres de session via des instructions ClickHouse ou la configuration du serveur.La classe ClickHouseConnection permet normalement un fonctionnement en parallèle (plusieurs threads peuvent exécuter des requêtes simultanément). Toutefois, l’activation de l’option UseSession limite cela à une seule requête active par connexion à un instant donné (il s’agit d’une limitation côté serveur).Sécurité
Configuration du client HTTP
Journalisation et débogage
Paramètres personnalisés et rôles
Lorsque vous utilisez une chaîne de connexion pour définir des paramètres personnalisés, utilisez le préfixe
set_, par ex. : “set_max_threads=4”. Si vous utilisez un objet ClickHouseClientSettings, n’utilisez pas le préfixe set_.Pour obtenir la liste complète des paramètres disponibles, consultez cette page.Exemples de chaînes de connexion
Connexion de base
Avec des paramètres ClickHouse personnalisés
QueryOptions
QueryOptions vous permet de surcharger les paramètres définis au niveau du client pour chaque requête. Toutes les propriétés sont facultatives et ne remplacent les valeurs par défaut du client que lorsqu’elles sont spécifiées.
Exemple :
InsertOptions
InsertOptions étend QueryOptions avec des paramètres spécifiques aux opérations d’insertion en masse via InsertBinaryAsync.
Toutes les propriétés de
QueryOptions sont également disponibles dans InsertOptions.
Exemple :
Ignorer la requête de sondage du schéma
Par défaut,InsertBinaryAsync envoie une requête SELECT ... WHERE 1=0 avant chaque insertion afin d’identifier les types de colonnes. Pour les scénarios à haut débit, vous pouvez supprimer cette surcharge de deux façons :
Option 1 : fournir explicitement les types de colonnes
Lorsque vous connaissez le schéma de la table à la compilation, transmettez-le directement via ColumnTypes. Aucune requête de schéma n’est alors envoyée :
UseSchemaCache = true pour n’interroger le schéma qu’une seule fois et le réutiliser lors des insertions suivantes sur la même instance ClickHouseClient :
ColumnTypesest prioritaire surUseSchemaCache. Si les deux sont définis, les types explicites sont utilisés.- Le cache de schéma ne détecte pas les modifications apportées via
ALTER TABLE. Si vous modifiez le schéma de la table, créez un nouveauClickHouseClientou évitezUseSchemaCachepour cette table. - Le cache est propre à l’instance
ClickHouseClientet indexé par (database, table). Différents sous-ensembles de colonnes d’une même table partagent un schéma mis en cache unique.
ClickHouseClient
ClickHouseClient est l’API recommandée pour interagir avec ClickHouse. Il est thread-safe, conçu pour être utilisé comme singleton et gère en interne le pool de connexions HTTP.
Créer un client
Créez unClickHouseClient à l’aide d’une chaîne de connexion ou d’un objet ClickHouseClientSettings. Consultez la section Configuration pour connaître les options disponibles.
Les informations de votre service ClickHouse Cloud sont disponibles dans la ClickHouse Cloud console.
Sélectionnez un service, puis cliquez sur Connect :
Choisissez C#. Les détails de connexion s’affichent ci-dessous.
Si vous utilisez ClickHouse autogéré, les détails de connexion sont définis par votre administrateur ClickHouse.
Avec une chaîne de connexion :
ClickHouseClientSettings :
IHttpClientFactory :
ClickHouseClient est conçu pour être conservé sur la durée et partagé dans toute votre application. Créez-le une seule fois (généralement sous forme de singleton) et réutilisez-le pour toutes les opérations sur la base de données. Le client gère en interne le pool de connexions HTTP.Exécution des requêtes
UtilisezExecuteNonQueryAsync pour les instructions qui ne renvoient pas de résultats :
ExecuteScalarAsync pour récupérer une seule valeur :
Insérer des données
Insertions paramétrées
Insérez des données à l’aide de requêtes paramétrées avecExecuteNonQueryAsync. Les types des paramètres doivent être spécifiés dans le SQL à l’aide de la syntaxe {name:Type} :
Insertions en masse
UtilisezInsertBinaryAsync pour insérer efficacement un grand nombre de lignes. Il transmet les données en flux à l’aide du format binaire natif de lignes de ClickHouse, prend en charge les envois parallèles par lots et évite les erreurs “URL trop longue” qui peuvent survenir avec les requêtes paramétrées.
InsertOptions :
- Le client récupère automatiquement la structure de la table via
SELECT * FROM <table> WHERE 1=0avant d’insérer les données. Les valeurs fournies doivent correspondre aux types des colonnes cibles. Pour ignorer cette requête, utilisezInsertOptions.ColumnTypesouInsertOptions.UseSchemaCache. - Lorsque
MaxDegreeOfParallelism > 1, les batches sont envoyés en parallèle. Les sessions ne sont pas compatibles avec l’insertion en parallèle ; désactivez-les ou définissezMaxDegreeOfParallelism = 1. - Utilisez
RowBinaryFormat.RowBinaryWithDefaultsdansInsertOptions.Formatsi vous souhaitez que le serveur applique les valeurs DEFAULT aux colonnes non fournies.
Insertion de POCO
Au lieu de construire des tableauxobject[], vous pouvez insérer directement des objets POCO fortement typés. Enregistrez le type une seule fois, puis passez IEnumerable<T> :
Lorsque toutes les propriétés mappées spécifient un
Type explicite, la requête de sondage du schéma est entièrement omise. Lorsque seules certaines propriétés ont des types explicites, le pilote revient à la requête de sondage du schéma pour l’ensemble des colonnes.
InsertBinaryAsync<T> prend en charge les mêmes InsertOptions (batching, parallélisme, mise en cache du schéma) que la surcharge object[].
Contrairement à la surcharge
object[], InsertBinaryAsync<T> n’accepte pas de liste explicite de colonnes. Les colonnes sont déterminées par les propriétés mappées du type enregistré. Pour contrôler les colonnes insérées, utilisez [ClickHouseNotMapped] pour exclure des propriétés ou [ClickHouseColumn(Name = "...")] pour les renommer.Si ColumnTypes est défini dans InsertOptions, elles remplaceront les attributs POCO.Évolution du schéma
Les insertions de POCO fonctionnent de façon transparente lorsque des colonnes sont ajoutées à la table cible après l’enregistrement du type. Comme le pilote n’insère que les colonnes associées au POCO, toute nouvelle colonne avecDEFAULT (ou d’autres expressions par défaut) est automatiquement remplie par le serveur. Aucune modification du code ni aucun nouvel enregistrement ne sont nécessaires.
Placement de la requête d’insertion
Une insertion binaire écrit son instructionINSERT INTO ... FORMAT ... sur la première ligne du corps de la requête, avant les lignes de données. Le corps étant compressé par défaut, les mécanismes de routage et de journalisation qui n’inspectent que l’URL ne voient pas cette instruction. Définissez InsertOptions.QueryPlacement sur InsertQueryPlacement.Url pour envoyer plutôt l’instruction dans le paramètre d’URL query, le corps ne contenant alors que les lignes de données :
query, ou lorsque vous souhaitez que l’instruction apparaisse dans les logs d’accès et les outils d’observability. Cette option est facultative, car l’instruction est alors comptabilisée dans la longueur de l’URL. La limite effective est la plus basse de celles imposées par le runtime .NET, par un intermédiaire et par le server. De .NET 6 à .NET 9, System.Uri limite l’URI de requête complet et encodé à 65 519 caractères ; le pilote lève une InvalidOperationException qui vous renvoie vers InsertQueryPlacement.Body lorsque cette limite est dépassée. Le paramètre http_max_uri_size de ClickHouse vaut 1 Mio par défaut, tandis qu’un intermédiaire peut imposer une limite plus basse. En mode corps, l’instruction et les rows ne sont soumis à aucune limite de longueur d’URL ; d’autres options de requête peuvent toujours figurer dans l’URL.
Ce paramètre est indépendant de Compressor : le corps est encodé de la même manière dans les deux modes.
Lecture des données
UtilisezExecuteReaderAsync pour exécuter des requêtes SELECT. Le ClickHouseDataReader renvoyé fournit un accès typé aux colonnes de résultat via des méthodes comme GetInt64(), GetString() et GetFieldValue<T>().
Appelez Read() pour passer à la ligne suivante. Cette méthode renvoie false lorsqu’il n’y a plus de lignes. Accédez aux colonnes par index (à partir de 0) ou par nom de colonne.
Lecture de POCO
Au lieu de lire les colonnes par index ou par nom, vous pouvez acheminer directement le résultat de la requête vers vos propres classes. Enregistrez le type une fois auprès du client, puis utilisezQueryAsync<T> :
RegisterPocoType<T>() configure à la fois les mappages d’insert et de lecture, et valide les deux en amont. RegisterBinaryInsertType<T>() reste inchangé et demeure réservé à l’insert pour des raisons de compatibilité descendante.
Un type enregistré doit avoir :
- Un constructeur public sans paramètre.
- Au moins une propriété publique avec un setter public non-
init. Les propriétésrequiredsont prises en charge.
InvalidOperationException. Une propriété object accepte donc n’importe quelle colonne.
QueryAsync<T> lit chacune de ces colonnes directement dans une propriété correspondante :
Chaque ligne accepte également la forme nullable de son type de propriété (
long?, DateOnly?, etc.),
que la colonne soit Nullable(...) ou non. Une propriété de type valeur non nullable sur une colonne
Nullable(T) est acceptée à l’enregistrement, mais lève une exception à l’arrivée d’un NULL.
Les wrappers tels que LowCardinality(T), SimpleAggregateFunction(f, T) et Object(T) se mappent exactement comme T.
Les colonnes composites sont également prises en charge et adoptent le type du framework indiqué dans la
référence des types en lecture : Array(T) vers T[], Tuple(...)
vers System.Tuple<...>, Nested(...) vers Tuple<...>[], JSON vers JsonObject (ou string
avec JsonReadMode=String), et Variant/Dynamic vers object.
Une colonne Map(K, V) constitue un cas particulier : une propriété List<KeyValuePair<K, V>> ou KeyValuePair<K, V>[]
est lue sans boxing et conserve l’ordre de transmission ainsi que les éventuelles clés répétées, quel que soit le
MapReadMode. Une propriété Dictionary<K, V> ne fonctionne qu’en mode par défaut.
Les types de clé et de valeur doivent correspondre exactement : ainsi,
Map(String, Nullable(Int32)) requiert KeyValuePair<string, int?>.
Lorsqu’une colonne propose plusieurs types de propriété (une colonne DateTime en tant que DateTime,
DateTimeOffset ou DateOnly, une colonne String en tant que string ou byte[]), le type de propriété déclaré détermine la représentation. Ces représentations alternatives
appartiennent au chemin POCO : QueryAsync<T> en dispose, mais pas MapTo<T>.
Lorsque vous parcourez manuellement un lecteur, utilisez ClickHouseDataReader.MapTo<T>() pour matérialiser la ligne en cours dans un POCO enregistré sans faire avancer le lecteur :
MapTo<T> lorsque vous devez piloter vous-même la boucle du lecteur — par exemple pour combiner un accès brut aux colonnes
avec la matérialisation en POCO. Cette méthode lit la ligne via les valeurs boxées du lecteur : elle n’offre donc pas
les types de propriétés alternatifs présentés plus haut, et elle alloue davantage que QueryAsync<T>. Préférez
QueryAsync<T> lorsque vous n’avez besoin que des lignes ; voir
choisir le chemin de matérialisation pour les chiffres.
Un convertisseur de valeurs en lecture défini au niveau du client ou par requête s’applique aux deux chemins et
ne désactive pas la lecture sans boxing. Le pilote convertit chaque colonne via la surcharge correspondant à la manière dont
il l’a lue : le ConvertValue<T> typé pour une colonne
sans boxing, et le ConvertValue boxé pour une colonne composite. Implémentez ces deux surcharges de façon
cohérente, sans quoi une même colonne donnera des résultats différents selon le chemin emprunté.
Si un LoggerFactory est configuré, RegisterPocoType<T>() et RegisterBinaryInsertType<T>() génèrent un journal de niveau Débogage (catégorie ClickHouse.Driver.Client) indiquant quelles propriétés ont été mappées à quelles colonnes, ainsi que celles qui ont été ignorées et pour quelle raison. Voir Journalisation et diagnostics.
Paramètres SQL
Dans ClickHouse, le format standard des paramètres dans les requêtes SQL est{parameter_name:DataType}.
Exemples :
Les paramètres SQL ‘bind’ sont transmis sous forme de paramètres de requête dans l’URI HTTP. En utiliser un trop grand nombre peut donc entraîner une exception “URL too long”. Utilisez
InsertBinaryAsync pour l’insertion en masse de données afin d’éviter cette limitation.Placeholders @name de style ADO
Le driver accepte également les placeholders @name, tels qu’en émettent les ORM comme Dapper. Il s’agit d’une
commodité côté client : avant l’envoi de la requête, chacun d’eux est réécrit en
{name:ResolvedType}, si bien que le serveur ne voit jamais de @. Consultez la
résolution de type pour savoir comment le type est choisi. Privilégiez la forme
explicite {name:Type} lorsque c’est possible.
Un @name sans paramètre correspondant est laissé tel quel, à charge pour le serveur de le rejeter. La correspondance est
case-sensitive : @ID ne lie donc pas un paramètre nommé id.
Pour désactiver la rewrite, activez le commutateur AppContext
ClickHouse.Driver.DisableReplacingParameters
avant la première utilisation du driver. Seule la réécriture du texte est désactivée ; les paramètres sont toujours envoyés, de sorte que
les requêtes écrites avec la syntaxe native {name:Type} continuent de fonctionner.Paramètres Identifier
Le type de paramètreIdentifier vous permet de lier en toute sécurité un nom de base de données, de table ou de colonne, au lieu d’un littéral de chaîne entre guillemets. Utilisez-le avec la syntaxe {name:Identifier} en SQL, ou en définissant ClickHouseDbParameter.ClickHouseType = "Identifier" :
ID de requête
Chaque requête se voit attribuer unquery_id unique, qui peut être utilisé pour extraire des données de la table system.query_log ou annuler des requêtes de longue durée. Vous pouvez spécifier un ID de requête personnalisé via QueryOptions:
Correspondance personnalisée des types de paramètres
Lorsque vous utilisez des paramètres de style@ (par exemple, WHERE id = @id), le driver déduit automatiquement le type ClickHouse à partir du type de valeur .NET. Par exemple, int correspond à Int32.
Pour remplacer ces valeurs par défaut, définissez ParameterTypeResolver dans ClickHouseClientSettings. C’est utile si vous voulez que tous les paramètres DateTime utilisent DateTime64(3) pour une précision à la milliseconde, ou que toutes les valeurs décimales utilisent une échelle spécifique, sans avoir à définir ClickHouseType sur chaque paramètre individuellement.
Utilisation de DictionaryParameterTypeResolver pour des correspondances de types simples :
IParameterTypeResolver personnalisé pour les cas d’usage avancés :
Pour une résolution basée sur le nom ou tenant compte de la valeur, implémentez directement l’interface IParameterTypeResolver. Renvoyez null pour utiliser l’inférence par défaut :
QueryOptions.ParameterTypeResolver. Lorsqu’il est défini, il prévaut sur le résolveur défini au niveau du client.
Ordre de priorité pour la résolution des types :
Le résolveur s’inscrit lui aussi dans une chaîne de priorité. De la priorité la plus élevée à la plus faible :
ClickHouseTypeexplicite défini sur le paramètre- annotation de type SQL issue de la syntaxe
{name:Type}dans la requête IParameterTypeResolver(viaQueryOptions.ParameterTypeResolver, avec repli surClickHouseClientSettings.ParameterTypeResolver)- Inférence de type intégrée (
TypeConverter.ToClickHouseType)
ClickHouseConnection d’ADO.NET : les paramètres de configuration sont hérités par les connexions créées à partir du client.
Formatage personnalisé des valeurs de paramètre
IParameterFormatter est un hook qui détermine comment les valeurs de paramètre sont sérialisées. Utilisez-le lorsque le formatage intégré (par ex. la précision de DateTime, la gestion des décimaux selon les paramètres régionaux, l’échappement des chaînes et la représentation des nombres) ne correspond pas à ce qu’attendent votre schéma ou vos outils en aval.
Définissez ParameterFormatter dans ClickHouseClientSettings pour installer un formateur pour toutes les requêtes paramétrées. Le formateur reçoit la valeur, le nom de type ClickHouse résolu et le nom du paramètre, puis renvoie la représentation sous forme de chaîne envoyée au serveur. Renvoyez null pour utiliser le formateur par défaut.
Utilisation de DictionaryParameterFormatter pour un formatage simple par type CLR :
IParameterFormatter personnalisé pour les cas d’usage avancés :
QueryOptions.ParameterFormatter. Lorsqu’il est défini, il prévaut sur le formateur défini au niveau du client.
Valeurs composites :
Le formateur s’applique à la fois aux paramètres de collection de premier niveau et à chaque élément à l’intérieur des valeurs composites (Array, Tuple, Map, Nullable, LowCardinality, Variant). Par exemple, un mappage typeof(int) formate individuellement chaque élément Int32 d’un Array(Int32).
Encadrement par apostrophes simples dans les contextes composites :
Pour les types ClickHouse de type chaîne (String, FixedString, Enum8, Enum16, IPv4, IPv6, UUID) intégrés dans un littéral composite, le driver entoure la sortie du formateur d’apostrophes simples, mais n’en échappe pas le contenu. Si la chaîne renvoyée contient une apostrophe simple ou un antislash non échappé, le littéral composite sera mal formé et le server rejettera la query.
Les paramètres de chaîne de premier niveau (non intégrés dans un composite) sont utilisés tels quels, sans encadrement ; aucun échappement n’est donc nécessaire dans ce cas.
Priorité du formateur :
IParameterFormatter(issu deQueryOptions.ParameterFormatter, ou à défaut deClickHouseClientSettings.ParameterFormatter). S’il renvoie une valeur non nulle, c’est cette valeur qui est utilisée.- Le formatage intégré, spécifique au type, dans
HttpParameterFormatter.
null ou DBNull, qui sont toujours sérialisées comme la valeur sentinelle null de ClickHouse (\N).
Conversion personnalisée des valeurs lues
IReadValueConverter vous permet de transformer les valeurs renvoyées par le lecteur de données après la désérialisation, sans modifier leur type CLR. Cas d’usage typiques : définir DateTime.Kind = Utc sur une colonne DateTime sans fuseau horaire, tronquer ou normaliser des chaînes, ou post-traiter une colonne JSON avant qu’elle n’arrive au code d’application.
Définissez ReadValueConverter dans ClickHouseClientSettings pour installer un convertisseur pour toutes les opérations de lecture. Le convertisseur est appelé une fois par colonne et par ligne, à la fois via le chemin encapsulé (GetValue) et le chemin générique (GetFieldValue<T>). Lorsqu’aucun convertisseur n’est défini, il n’y a aucun overhead : le lecteur renvoie directement les valeurs.
Utilisation de DictionaryReadValueConverter pour une conversion simple par type CLR :
For<T> sont transmises inchangées. La sélection s’effectue sur le type CLR exact ; enregistrez donc le type réel renvoyé par le lecteur (par ex. For<JsonObject> pour une colonne JSON en JsonReadMode.Binary).
IReadValueConverter personnalisé pour les scénarios avancés :
Si vous devez effectuer la sélection en fonction de la chaîne de type côté ClickHouse (par exemple, pour distinguer DateTime de DateTime('UTC') — tous deux apparaissent comme le même type CLR), implémentez directement IReadValueConverter :
GetFieldType, GetSchemaTable) ne transitent pas par lui et doivent rester cohérentes avec ce qui est renvoyé.
Vous pouvez également définir un convertisseur par requête via QueryOptions.ReadValueConverter ; lorsqu’il est défini, il prévaut sur le convertisseur au niveau du client.
Limite de dispatch :
Le convertisseur est invoqué une fois par colonne avec la valeur complète de la cellule désérialisée ; il ne parcourt pas récursivement les conteneurs composés. Pour une colonne Array(Int32), la valeur transmise est un int[] ; pour Tuple(Int32, String), c’est un ITuple.
Quelle surcharge est exécutée :
Les deux surcharges doivent être cohérentes, car celle que le driver appelle dépend de la façon dont l’appelant a lu la
colonne :
ConvertValue<T>— les accesseurs typésGetByte,GetSByte,GetInt16/32/64,GetUInt16/32/64,GetFloat,GetDouble,GetGuid,GetDateTime,GetIPAddress,GetBigIntegeretGetFieldValue<T>, ainsi que chaque colonne sans encapsulation sur le chemin de lecture POCO.ConvertValue(encapsulé) —GetValue,GetValues, les indexeurs,GetChar,GetTuple, et les chemins de coercition dansGetBoolean,GetDecimaletGetString.
IsDBNull n’exécute aucun convertisseur : il lit directement le drapeau null, de sorte qu’un convertisseur ne peut jamais
modifier le fait qu’une valeur soit considérée comme null. TryGetEnumOrdinal le contourne également — voir
lecture de l’ordinal d’un enum.
Le convertisseur fonctionne avec le chemin ADO.NET ClickHouseConnection — les paramètres sont hérités par les connexions créées à partir du client.
Flux brut
UtilisezExecuteRawResultAsync pour transmettre directement le résultat de la requête dans un format spécifique, sans passer par le lecteur de données. Cela est utile pour exporter des données vers des fichiers ou les acheminer vers d’autres systèmes :
JSONEachRow, CSV, TSV, Parquet, Native. Consultez la documentation des formats pour voir toutes les options.
Compression du transport par requête
Par défaut, le client négociezstd, lz4, gzip, deflate lorsque Compression=true (valeur par défaut de la chaîne de connexion) et décode lui-même le flux, de manière transparente.
Pour les exports bruts (par ex. Parquet, Arrow, Native), vous pouvez souhaiter négocier un codec différent (par ex. zstd ou lz4) afin de troquer du CPU contre du débit sans modifier le paramètre défini pour l’ensemble de la connexion. QueryOptions.AcceptEncoding et ClickHouseCommand.AcceptEncoding définissent l’en-tête HTTP Accept-Encoding pour une seule requête, en remplaçant toute valeur par défaut appliquée jusque-là, et forcent enable_http_compression=1 dans l’URL (condition exigée par ClickHouse pour prendre en compte Accept-Encoding).
Configuration de HttpClient
Rien à configurer : leHttpClient construit par le driver laisse AutomaticDecompression à DecompressionMethods.None et décode lui-même les réponses ; ainsi, Content-Encoding n’est jamais retiré à votre insu et le corps brut vous parvient exactement tel que le server l’a envoyé.
Corps des erreurs
Lorsque le serveur renvoie une réponse 4xx/5xx et queenable_http_compression=1 est défini, il compresse le corps de l’erreur avec le même codec que celui qu’il aurait utilisé pour une réponse réussie. Le driver décode ces corps pour tous les codecs qu’il prend en charge (lz4, zstd, gzip, deflate, br/brotli), afin que le message affiché dans ClickHouseServerException soit lisible. Pour tout autre codec (snappy, …), il renvoie un message de substitution qui indique le codec et pointe vers system.query_log pour le texte d’erreur d’origine.
Décompression des réponses
Accept-Encoding demande uniquement au serveur de compresser la réponse — encore faut-il que quelque chose la décode. Le driver s’en charge lui-même, à partir du Content-Encoding de la réponse, si bien que toutes les API de lecture ordinaires (ExecuteReaderAsync, ExecuteScalarAsync, ExecuteNonQueryAsync, QueryAsync<T>, Dapper, EF Core, linq2db) fonctionnent sur une réponse compressée sans aucune configuration. Il décode lz4, zstd, gzip, deflate et br ; snappy n’est pas pris en charge.
Par défaut, le driver annonce zstd, lz4, gzip, deflate, et ClickHouse répond en zstd. Pour un autre choix, définissez vous-même Accept-Encoding — pour l’ensemble du client :
ClickHouseClientSettings :
enable_http_compression=1 dans l’URL, ce que ClickHouse exige avant même de prendre l’en-tête en compte — y compris lorsque UseCompression vaut false, puisque nommer explicitement un codec revient à en demander un. Sans valeur définie, UseCompression=false n’envoie aucun Accept-Encoding.
Accept-Encoding peut être défini à quatre endroits. Le premier d’entre eux qui nomme un codec l’emporte :
QueryOptions.AcceptEncoding(ouClickHouseCommand.AcceptEncoding)CustomHeaders["Accept-Encoding"]sur la requêteCustomHeaders["Accept-Encoding"]sur le clientClickHouseClientSettings.AcceptEncoding, ou le keyword de chaîne de connectionAcceptEncoding
identity.
C’est le server, et non le client, qui choisit le codec. ClickHouse parcourt Accept-Encoding à la recherche de tokens selon son propre ordre de préférence figé — zstd > br > lz4 > snappy > gzip > deflate — et ignore aussi bien l’ordre dans lequel vous les listez que les q-values. L’en-tête constitue donc une announcement de capability plutôt qu’une exigence, et le seul moyen d’orienter le choix consiste à omettre certains tokens. La liste par défaut inclut zstd : une requête par défaut reçoit donc une réponse en zstd, les tokens restants servant de fallback. br est décodable, mais n’est pas annoncé par défaut.
La comparaison des codecs en matière de taille de payload, de CPU côté server et de CPU côté client dépend de vos données, de votre lien réseau et de la valeur de http_zlib_compression_level du server (valeur par défaut livrée : 3) — voir Réglage de la compression.
http_zlib_compression_level. Ce SETTING s’applique à tous les codecs HTTP, et sa default value est 3. Cette valeur doit être ajustée en fonction de vos données, de la vitesse du lien réseau et de l’utilisation CPU.- Un client CPU-bound sur un lien rapide. Le driver décode le response corps sur le thread appelant : lorsque le réseau n’est pas le goulot d’étranglement, la vitesse de décodage client-side peut devenir le facteur limitant.
Content-Encoding l’indique, quelle que soit la requête initiale : en l’absence d’en-tête ou avec identity, il passe tel quel ; un codec pris en charge est décodé ; toute autre valeur lève une erreur la mentionnant. Il n’y a aucun risque de double décodage — si l’AutomaticDecompression d’un handler fourni par l’appelant a déjà décodé un corps, il supprime également Content-Encoding, si bien que le driver voit du plaintext et n’y touche pas.
Les résultats bruts n’annoncent aucun codec. ExecuteRawResultAsync (ainsi que les méthodes publiques PostStreamAsync / InsertRawStreamAsync) vous transmettent leur corps tel quel : à moins que vous ne spécifiiez vous-même un codec, elles n’en demandent aucun — rien dans le driver ne décode un tel corps, si bien que proposer un codec à cet endroit transformerait silencieusement un export en fichier compressé. La règle est donc simple, et indépendante de la configuration du HttpClient : un corps transmis tel quel arrive exactement comme le server l’a envoyé, et le server envoie du plaintext sauf si vous avez demandé un codec. En demander un (au niveau du client ou par requête) est le moyen d’exporter délibérément des compressed bytes.
Un AcceptEncoding explicite (à l’un ou l’autre niveau) s’applique malgré tout aux requêtes brutes, et ClickHouseRawResult.ReadDecompressedStreamAsync() décode le résultat lorsque vous le souhaitez ; ReadAsStreamAsync, ReadAsByteArrayAsync, ReadAsStringAsync et CopyToAsync renvoient toujours les octets exactement tels qu’ils sont arrivés.
leaveOpen : sa libération laisse donc la réponse intacte ; lorsqu’elle n’est pas compressée, vous obtenez le flux de contenu HTTP lui-même, et sa libération met fin au corps. Dans les deux cas, c’est le ClickHouseRawResult qui est propriétaire de la réponse — n’appelez pas ses autres membres de lecture une fois le flux libéré. Libérer le ClickHouseRawResult est toujours nécessaire et suffit à lui seul : cela libère à la fois la réponse et tout decoder inséré ici (les decoders retiennent des buffers issus du pool). Le await using ci-dessus est donc facultatif, et peut être conservé sans risque. Des appels séquentiels répétés renvoient le même flux ; le type ne peut pas être utilisé de manière concurrente.
Consultez Select_007_ResponseCompression.cs pour un exemple exécutable.
Compression des insertions (requêtes)
Zstd est le codec par défaut pour les insertions :InsertOptions.Compressor vaut initialement ZstdCompressor.Default,
c’est-à-dire zstd au niveau 3. Affectez-lui un autre compresseur pour changer de codec, ou null pour envoyer le
corps non compressé.
Default ainsi que d’un constructeur qui prend en paramètres un niveau
et la taille du write buffer :
Partagez les instances de compresseur. Chaque
Default est une instance partagée unique, et les quatre compresseurs
peuvent être utilisés sans risque depuis plusieurs threads à la fois — ce qui est le cas lorsque
InsertOptions.MaxDegreeOfParallelism est supérieur à 1, puisqu’un insert utilise un compresseur par
batch. Aucun d’eux n’implémente IDisposable. Créez votre propre instance une seule fois et réutilisez-la, de la
même manière que Default est utilisé.IClickHouseCompressor est public, et une implémentation ne doit fournir que deux membres :
Content-Encoding que vous indiquez. Les autres membres —
Decompress, MethodByte, MaxEncodedLength, Encode et Decode — disposent d’implémentations
par défaut qui lèvent NotSupportedException : ne surchargez donc que celles dont votre codec a besoin.
Implémentez Decompress pour décoder les corps de réponse en plus de compresser les requêtes, et levez
InvalidDataException depuis le flux qu’il renvoie lorsqu’un corps est corrompu ou dans un format incorrect.
InsertOptions.Compressor ne régit que les insertions binaires. Les autres corps de requête du driver sont compressés selon d’autres règles, et aucun d’eux n’y fait appel :
- Toute requête en texte SQL (
ExecuteReaderAsync,ExecuteScalarAsync,ExecuteNonQueryAsync,QueryAsync<T>,ExecuteRawResultAsync, la couche ADO.NET) envoie son statement avecContent-Encoding: gzipdès lors queUseCompressionvauttrue— c’est-à-dire par défaut. Le codec n’est pas configurable :AcceptEncodingne pilote que la réponse, le choix se limite donc à gzip ou rien. AvecCompression=false, le statement part en clair. Les statements étant de petite taille, il est rarement utile de s’en préoccuper — mais mieux vaut le savoir lorsque vous observez les requêtes dans un proxy ou une capture de packets. - Un corps multipart — une requête dont les parameters sont envoyés sous forme de form data (
UseFormDataParameters=true) — est toujours envoyé uncompressed, quelle que soit la valeur deUseCompression. - Un téléversement raw (
InsertRawStreamAsync,PostStreamAsync) s’appuie sur son propre flag, propre à chaque appel, et ne consulte niUseCompressionniInsertOptions.Compressor: gzip lorsque le flag est positionné, uncompressed sinon. Notez que le parameteruseCompressiondeInsertRawStreamAsyncvauttruepar défaut : un téléversement raw est donc gzippé sauf si vous passezfalse— même avecCompression=falsesur le client.
Ajuster la compression
La compression troque du CPU contre des octets. Le gain réel dépend presque entièrement de la rapidité de votre lien réseau par rapport à la vitesse d’exécution du codec. Aucun réglage ne convient à tout le monde.Le chiffre décisif
La compression en vaut la peine tant que le codec est plus rapide que le réseau. Ce seuil est plus bas qu’on ne l’imagine généralement sur le chemin de lecture, car ClickHouse compresse les réponses HTTP en mono-thread dans le buffer de sortie. Mesuré sur un service ClickHouse Cloud à 16 vCPU (hits, RowBinary, niveau 3), le serveur produit une sortie compressée à environ 100-200 Mo/s.
Ainsi, pour un résultat volumineux, et en supposant qu’une seule requête soit traitée à la fois, la compression cesse d’être rentable aux alentours de 100 Mo/s. Un flux HTTPS unique
au sein d’une même région cloud dépasse couramment ce débit, tandis que tout trafic traversant l’Internet public, un VPN ou une frontière de région se situe généralement en dessous.
Le chemin d’insertion tolère la compression jusqu’à des débits de liaison plus élevés, car votre client compresse sur un cœur qui lui est propre et se révèle généralement plus rapide que la compression des réponses côté serveur.
Guide approximatif par déploiement
Trois éléments que ce tableau ne prend pas en compte :
- Le coût de l’egress : si le transfert de données vous est facturé, les octets ont un prix qui dépasse la seule question de la latence, ce qui incite à compresser davantage, quelle que soit la vitesse du lien.
- Les petits résultats : tout ce qui précède concerne des payloads volumineux. Pour de petites réponses, le codec n’a quasiment aucune importance et c’est l’overhead par requête qui domine.
- Les insertions parallèles relèvent les seuils d’insertion. Chaque valeur de débit ci-dessus vaut pour un seul thread.
InsertOptions.MaxDegreeOfParallelismvaut1par défaut, mais l’augmenter permet de compresser les batches en parallèle : le taux d’encodage agrégé du client évolue donc à peu près proportionnellement au nombre de cœurs que vous lui allouez. Ainsi, sur un lien rapide, il reste intéressant de compresser une insertion parallèle bien au-delà de la vitesse à laquelle cela cesse d’être rentable pour une insertion mono-thread. Considérez les lignes d’insertion du tableau comme un plancher et, si vous regroupez déjà en batches parallèles, refaites vos tests avant de conclure que votre lien est trop rapide pour la compression.
Choisir un codec
Levels
La compression des réponses est régie par un unique server setting,http_zlib_compression_level, qui s’applique à tous les codecs HTTP, et pas seulement à zlib. Sa valeur par défaut est 3.
N’y touchez pas sans avoir mesuré une raison de le faire. Au-dessus de la valeur par défaut, le gain de taille est minime au regard du coût CPU (pour zstd, passer de 3 à 6 double environ la charge CPU du server pour ~14 % d’octets en moins), et br devient pathologique. En dessous, au niveau 1, la donne change vraiment : lz4 devient bien moins coûteux et zstd perd son avantage CPU sur lui. Définissez-le par requête si nécessaire :
Mesurer votre propre point de bascule
Le moyen le plus rapide d’optimiser votre choix de codec et de niveau de compression consiste à chronométrer la même requête avec plusieurs codecs, puis à comparer les résultats.ProfileEvents dans system.query_log — définissez
QueryOptions.QueryId afin de pouvoir retrouver la ligne :
LIMIT n sans ORDER BY renvoie des rows différentes
à chaque exécution, si bien que chaque répétition compresse des données différentes et que les ratios ne sont plus que du bruit. Comparez
plutôt sur un result set fixe.
Insertion via raw stream
UtilisezInsertRawStreamAsync pour insérer des données directement depuis un fichier ou un flux en mémoire, dans des formats tels que CSV, JSON, Parquet ou tout autre format ClickHouse pris en charge.
Insertion depuis un fichier CSV :
Consultez la documentation des paramètres de format pour connaître les options permettant de contrôler le comportement de l’ingestion de données.
Autres exemples
Pour d’autres exemples pratiques d’utilisation, consultez le répertoire examples du dépôt GitHub.ADO.NET
La bibliothèque offre une prise en charge complète d’ADO.NET viaClickHouseConnection, ClickHouseCommand et ClickHouseDataReader. Cette API est nécessaire pour l’intégration avec les ORM (Dapper, Linq2db) et lorsque vous avez besoin des abstractions .NET standard pour les bases de données.
Gestion du cycle de vie avec ClickHouseDataSource
Créez toujours des connexions à partir d’unClickHouseDataSource afin de garantir une gestion correcte du cycle de vie ainsi qu’un pool de connexions. Le ClickHouseDataSource gère en interne une unique instance de ClickHouseClient, et toutes les connexions partagent son pool de connexions HTTP.
Utilisation de ClickHouseCommand
Créez des commandes à partir d’une connexion pour exécuter des requêtes SQL :ExecuteNonQueryAsync()- Pour les instructions INSERT, UPDATE, DELETE et DDLExecuteScalarAsync()- Renvoie la première colonne de la première ligneExecuteReaderAsync()- Renvoie unClickHouseDataReaderpermettant de parcourir les résultats
Utilisation de ClickHouseDataReader
Le ClickHouseDataReader permet un accès typé au résultat de la requête :
Lecture de l’ordinal d’un enum
Une colonneEnum8 ou Enum16 se matérialise sous la forme de son label : GetFieldType renvoie string, et
GetString, GetValue et GetFieldValue<string> retournent tous le label. Les accesseurs numériques
lèvent une InvalidCastException sur une colonne enum, car la valeur stockée est une chaîne de caractères.
Utilisez TryGetEnumOrdinal pour obtenir le nombre associé au label :
true et définit value pour une colonne Enum8/Enum16, ainsi que pour une colonne
Nullable(Enum...) dont la cellule n’est pas NULL. Elle renvoie false, avec value défini à 0, pour une cellule NULL ou pour
toute colonne qui n’est pas un enum. L’ordinal correspond à la
valeur signée transmise sur le wire : il peut donc être négatif, et un ordinal Enum16 peut dépasser la taille d’un
octet.
Bonnes pratiques
Durée de vie des connexions et pool de connexions
ClickHouse.Driver utilise System.Net.Http.HttpClient en interne. HttpClient dispose d’un pool de connexions par endpoint. Par conséquent :
- Les sessions de base de données sont multiplexées via des connexions HTTP gérées par le pool de connexions.
- Les connexions HTTP sont automatiquement recyclées par le pool.
- Les connexions peuvent rester actives après la libération des objets
ClickHouseClientouClickHouseConnection.
Gestion des valeurs DateTime
-
Utilisez UTC chaque fois que possible. Stockez les horodatages dans des colonnes
DateTime('UTC')et utilisezDateTimeKind.Utcdans votre code. Cela élimine toute ambiguïté liée au fuseau horaire. -
Utilisez
DateTimeOffsetpour gérer explicitement le fuseau horaire. Il représente toujours un instant précis et inclut les informations de décalage. -
Spécifiez le fuseau horaire dans les annotations de type SQL. Lorsque vous utilisez des paramètres avec des valeurs DateTime
Unspecifiedpour des colonnes non UTC, incluez le fuseau horaire dans le SQL :
Insertions asynchrones
Les insertions asynchrones transfèrent la responsabilité du batching du client vers le serveur. Au lieu d’exiger un batching côté client, le serveur met les données entrantes en mémoire tampon, puis les écrit dans le stockage en fonction de seuils configurables. Cela est utile dans les scénarios à forte concurrence, comme les workloads d’observability où de nombreux agents envoient de petites charges utiles. Activez les insertions asynchrones viaCustomSettings ou la chaîne de connexion :
wait_for_async_insert) :
Paramètres clés :
Sessions
N’activez les sessions que si vous avez besoin de fonctionnalités côté serveur avec état, par exemple :- Tables temporaires (
CREATE TEMPORARY TABLE) - Conservation du contexte de requête d’une instruction à l’autre
- Paramètres de session (
SET max_threads = 4)
Types de données pris en charge
ClickHouse.Driver prend en charge tous les types de données de ClickHouse. Les tableaux ci-dessous présentent les correspondances entre les types ClickHouse et les types .NET natifs lors de la lecture des données depuis la base de données.
Correspondance de types : lecture à partir de ClickHouse
Types d’entiers
Types à virgule flottante
Types décimaux
La conversion du type Decimal est gérée par le paramètre UseCustomDecimals.
Type booléen
Types de chaînes
Par défaut, les colonnes
String et FixedString(N) sont toutes deux renvoyées sous forme de string. Définissez ReadStringsAsByteArrays=true dans votre chaîne de connexion pour les lire sous forme de byte[]. Cela est utile lorsque vous stockez des données binaires qui peuvent ne pas être en UTF-8 valide.Ce paramètre s’applique aussi aux chaînes imbriquées dans d’autres types : ainsi, Array(String) est lu comme byte[][]
et Map(String, String) comme Dictionary<byte[], byte[]> — clés comprises. La seule exception est une
colonne JSON, dont les feuilles de type chaîne sont toujours du texte ; voir Type JSON.Types de date et d’heure
ClickHouse stocke les valeurs
DateTime et DateTime64 en interne sous forme de timestamps Unix (secondes ou fractions de seconde écoulées depuis l’époque). Bien que le stockage soit toujours en UTC, les colonnes peuvent avoir un fuseau horaire associé, qui influe sur la manière dont les valeurs sont affichées et interprétées.
Lors de la lecture de valeurs DateTime, la propriété DateTime.Kind est définie en fonction du fuseau horaire de la colonne :
Pour les colonnes non UTC, le
DateTime renvoyé représente l’heure d’horloge dans ce fuseau horaire. Utilisez ClickHouseDataReader.GetDateTimeOffset() pour obtenir un DateTimeOffset avec le décalage correct pour ce fuseau horaire :
DateTime au lieu de DateTime('Europe/Amsterdam')), le pilote renvoie un DateTime avec Kind=Unspecified. Cela préserve exactement l’heure telle qu’elle est stockée, sans supposer de fuseau horaire.
Si vous avez besoin d’un comportement tenant compte du fuseau horaire pour des colonnes sans fuseau horaire explicite, vous pouvez :
- Utiliser des fuseaux horaires explicites dans vos définitions de colonnes :
DateTime('UTC')ouDateTime('Europe/Amsterdam') - Appliquer vous-même le fuseau horaire après la lecture.
Type JSON
Le type de retour des colonnes JSON dépend du paramètre
JsonReadMode :
-
Binary(par défaut) : renvoieSystem.Text.Json.Nodes.JsonObject. Fournit un accès structuré aux données JSON, mais les types ClickHouse spécialisés (comme les adresses IP, les UUID ou les grands nombres décimaux) sont convertis en représentations sous forme de chaîne dans la structure JSON. -
String: renvoie le JSON brut sous forme destring. Préserve la représentation JSON exacte de ClickHouse, ce qui est utile lorsque vous devez transmettre le JSON sans l’analyser, ou lorsque vous souhaitez gérer vous-même la désérialisation.
None est un troisième mode. Il effectue la lecture exactement comme Binary, mais n’envoie aucun paramètre serveur avec la
requête — utilisez-le sur une connexion qui n’est pas autorisée à en définir un.
Un chemin déclaré dans le type de la colonne est un typed path ; tout autre chemin du document est un
dynamic path. Les deux diffèrent lorsqu’une valeur est nulle.
Un typed path apparaît toujours dans le JsonObject. Déclaré Nullable(T) ou Dynamic, il est renvoyé
sous la forme d’un null JSON aussi bien lorsque la valeur stockée est nulle que lorsque le document ne contient pas ce chemin — les deux
cas sont impossibles à distinguer :
JSON(x String)
donne {"x":""} et JSON(x Int64) donne {"x":0}.
Un chemin dynamique dont la valeur est null est entièrement supprimé de l’objet, si bien que ContainsKey renvoie
false pour celui-ci. La lecture de {"x":null} depuis une colonne JSON simple donne {}.
Les chemins typés imbriqués créent leurs parents : JSON(a.b Nullable(Int64)) produit donc {"a":{"b":null}}
même pour un document vide.
C’est ce que le server restitue lui-même, si bien que les modes
Binary et String concordent désormais. Avant la version 1.4.0, un
chemin typé contenant null était supprimé du JsonObject, si bien que {"x":null} était relu comme
{} — et, pour un chemin imbriqué tel que JSON(a.b Nullable(Int64)), tout le sous-arbre a disparaissait.JSON sont toujours renvoyées sous forme de texte, quelle que soit la valeur de
ReadStringsAsByteArrays — JsonValue n’a pas de forme tableau d’octets, un byte[] serait donc
rendu en base64. Cela vaut pour String, FixedString, ainsi que pour ces types encapsulés dans
LowCardinality, Nullable ou SimpleAggregateFunction, et pour les chaînes contenues dans Array et Map,
clés de map incluses.
Un tableau d’octets dont le lecteur JSON ne peut pas déterminer le type est bel et bien rendu en base64 : un
chemin typé
Variant ou Dynamic contient une valeur dont le type n’est connu que ligne par ligne ; ainsi, une chaîne
sous Variant(Array(UInt8), String) est renvoyée encodée en base64. Le comportement est identique avec les deux réglages.Un type de clé de map JSON autre que String exactement — Map(LowCardinality(String), String), par
exemple — lève une NotSupportedException.JSON(a Int64, a.b Int64). Les deux chemins sont présents dans chaque ligne, si bien que le serveur
restitue la ligne avec une clé dupliquée : {"a":0,"a":{"b":7}}. Un JsonObject ne peut pas contenir deux valeurs
pour une même clé ; JsonReadMode.Binary lève donc une SerializationException désignant les deux chemins. Il
en va de même lorsque la valeur est un Map, comme dans JSON(a Map(String, Int64)) lu depuis une ligne qui
possède également un a.b dynamique.
Cela ne s’applique que si les deux côtés portent une valeur dans cette ligne. Un côté qui ne contient rien — un null,
un objet vide ou un sous-arbre dont toutes les valeurs sont nulles — cède la place à celui qui porte la donnée,
quel que soit celui des deux chemins que le serveur envoie en premier. Un chevauchement déclaré avec des types Nullable ne renseigne donc qu’un seul côté par ligne et se lit sans
erreur : JSON(a Nullable(Int64), a.b Nullable(Int64)) donne bien {"a":5} et {"a":{"b":7}}, comme
attendu.
Lisez une telle colonne avec JsonReadMode.String pour obtenir le texte JSON du serveur inchangé, clé dupliquée
comprise.
Définissez AllowDuplicateJsonKeys pour continuer à lire la colonne en tant que JsonObject au lieu de lever une exception. Le
pilote conserve alors celle des deux valeurs qui apparaît en dernier dans la ligne et abandonne l’autre : le
résultat est donc incomplet : JSON(a Int64, a.b Int64) contenant {"a.b":7} se lit comme {"a":0}. Un chemin qui
porte une valeur et dont le parent porte un scalar ou un Array lève malgré tout une exception, car un sous-arbre ne peut être
placé sous ni l’un ni l’autre.
Map type
Un
Map(K, V) ClickHouse est physiquement un Array(Tuple(K, V)) et peut contenir plusieurs entrées associées à la même clé. Ce n’est pas le cas d’un Dictionary : en mode par défaut, une clé répétée ne conserve donc que sa dernière valeur et les paires précédentes sont abandonnées. Le paramètre MapReadMode détermine la représentation utilisée :
-
Dictionary(par défaut) : renvoieDictionary<K, V>. -
KeyValuePairs: renvoieList<KeyValuePair<K, V>>dans l’ordre où le serveur a envoyé les paires, si bien que chaque paire est préservée, y compris les entrées qui répètent une clé.
Map ; il s’applique donc également à GetFieldValue<T>, aux types de schema signalés par le pilote et au mapping des propriétés POCO. Il s’applique partout où une map apparaît dans l’arborescence de types d’une colonne — y compris Array(Map(...)), Map(K, Map(...)), Tuple(..., Map(...)) et Dynamic.
Les deux représentations sont acceptées à l’écriture, quel que soit le mode — voir écriture des maps.
Autres types
Les types Dynamic et Variant sont convertis dans le type correspondant au type sous-jacent réel de chaque ligne.
Types de géométrie
Le type Geometry est un type Variant qui peut contenir n’importe quel type de géométrie. Il sera converti en type correspondant.
Correspondance des types : écriture dans ClickHouse
Lors de l’insertion de données, le pilote convertit les types .NET vers leurs types ClickHouse correspondants. Les tableaux ci-dessous indiquent quels types .NET sont pris en charge pour chaque type de colonne ClickHouse.Types entiers
Types en virgule flottante
Type Boolean
Types de chaînes
Types de date et d’heure
Valeurs hors plageLors d’une écriture binaire, les valeurs
Date, Date32, DateTime et DateTime32 en dehors de leur plage prise en charge lèvent une ArgumentOutOfRangeException au moment de Write, en indiquant le type de colonne et la plage prise en charge. Auparavant, les valeurs hors plage pouvaient être tronquées silencieusement via un entier 32 bits puis réinterprétées par le serveur, produisant des timestamps réels mais erronés.DateTime.Kind lors de l’écriture des valeurs :
Les valeurs
DateTimeOffset préservent toujours l’instant exact.
Exemple : DateTime UTC (instant préservé)
DateTimeKind.Utc ou DateTimeOffset pour toutes les opérations sur les types DateTime. Cela garantit que votre code fonctionne de manière cohérente, quel que soit le fuseau horaire du serveur, du client ou de la colonne.
Paramètres HTTP vs copie en masse
Il existe une différence importante entre la liaison de paramètres HTTP et la copie en masse lors de l’écriture de valeurs DateTimeUnspecified :
Copie en masse connaît le fuseau horaire de la colonne cible et interprète correctement les valeurs Unspecified dans ce fuseau horaire.
Paramètres HTTP ne connaissent pas automatiquement le fuseau horaire de la colonne. Vous devez le spécifier dans l’indication de type SQL :
Types décimaux
Type JSON
Le comportement lors de l’écriture de JSON est contrôlé par le paramètre
JsonWriteMode :
-
String(par défaut) : Acceptestring,JsonObject,JsonNodeou tout objet. Toutes les entrées sont sérialisées viaSystem.Text.Json.JsonSerializeret envoyées sous forme de chaînes JSON pour un traitement côté serveur. C’est le mode le plus flexible et il fonctionne sans enregistrement préalable de type. -
Binary: Accepte uniquement les types POCO enregistrés. Les données sont converties côté client au format JSON binaire de ClickHouse avec une prise en charge complète des indications de type. Nécessite d’appelerconnection.RegisterJsonSerializationType<T>()avant utilisation. L’écriture de valeursstringouJsonNodedans ce mode lèveArgumentException.
JSON(id UInt64, price Decimal128(2))), le driver utilise ces indications pour sérialiser les valeurs en respectant pleinement les types. Cela préserve la précision de types comme UInt64, Decimal, UUID et DateTime64, qui perdraient sinon en précision s’ils étaient sérialisés sous forme de JSON générique.
Les POCO peuvent être écrits dans des colonnes JSON de deux manières, selon le JsonWriteMode :
Mode String (par défaut) : les POCO sont sérialisés via System.Text.Json.JsonSerializer. Aucun enregistrement de type n’est nécessaire. C’est l’approche la plus simple, et elle fonctionne avec les objets anonymes.
Mode binaire : les POCO sont sérialisés à l’aide du format JSON binaire du driver, avec prise en charge complète des indications de type. Les types doivent être enregistrés avec connection.RegisterJsonSerializationType<T>() avant utilisation. Ce mode prend en charge des mappages de chemins personnalisés via des attributs :
-
[ClickHouseJsonPath("path")]: associe une propriété à un chemin JSON personnalisé. Utile pour les structures imbriquées ou lorsque le nom de la propriété diffère de la clé JSON souhaitée. Fonctionne uniquement en mode binaire. -
[ClickHouseJsonIgnore]: exclut une propriété de la sérialisation. Fonctionne uniquement en mode binaire.
UserId ne correspondra qu’à une indication définie comme UserId, et non userid. Cela correspond au comportement de ClickHouse, qui permet à des chemins comme userName et UserName de coexister en tant que champs distincts.
Limitations (mode binaire uniquement) :
- Les types POCO doivent être enregistrés sur la connexion avec
connection.RegisterJsonSerializationType<T>()avant la sérialisation. Toute tentative de sérialiser un type non enregistré lève uneClickHouseJsonSerializationException. - Les propriétés de type Dictionary et array/list nécessitent des indications de type dans la définition de la colonne pour être sérialisées correctement. Sans ces indications, utilisez plutôt String mode.
- Les valeurs nulles des propriétés POCO ne sont écrites que lorsque le chemin possède une indication de type
Nullable(T)dans la définition de la colonne. ClickHouse n’autorise pas les typesNullabledans les chemins JSON dynamiques ; les propriétés nulles sans indication sont donc ignorées. - Les attributs
ClickHouseJsonPathetClickHouseJsonIgnoresont ignorés en String mode (ils ne fonctionnent qu’en mode binaire).
Autres types
Types de géométrie
Non pris en charge en écriture
Gestion du type Nested
Les types Nested de ClickHouse (Nested(...)) peuvent être lus et écrits avec la sémantique des tableaux.
Journalisation et diagnostics
Le client .NET ClickHouse s’intègre aux abstractionsMicrosoft.Extensions.Logging afin d’offrir une journalisation légère, activée sur demande. Lorsqu’elle est activée, le driver émet des messages structurés pour les événements du cycle de vie de la connexion, l’exécution des commandes, les opérations de transport et les opérations d’insertion en masse. La journalisation est entièrement facultative : les applications qui ne configurent pas de logger continuent de s’exécuter sans surcharge supplémentaire.
Démarrage rapide
Utilisation du fichier appsettings.json
Vous pouvez configurer les niveaux de journalisation à l’aide de la configuration .NET standard :Utilisation d’une configuration en mémoire
Vous pouvez également configurer le niveau de verbosité de la journalisation par catégorie dans le code :Catégories et émetteurs
Le driver utilise des catégories dédiées afin de vous permettre d’ajuster finement les niveaux de journalisation par composant :Exemple : diagnostic des problèmes de connexion
- Sélection de la fabrique de clients HTTP (pool par défaut ou connexion unique)
- Configuration du gestionnaire HTTP (SocketsHttpHandler ou HttpClientHandler)
- Paramètres du pool de connexions (MaxConnectionsPerServer, PooledConnectionLifetime, etc.)
- Paramètres de délai d’expiration (ConnectTimeout, Expect100ContinueTimeout, etc.)
- Configuration SSL/TLS
- Événements d’ouverture/fermeture des connexions
- Suivi des ID de session
Mode Débogage : tracing réseau et diagnostics
Pour faciliter le diagnostic des problèmes réseau, la bibliothèque du driver inclut un utilitaire qui active le tracing de bas niveau des mécanismes réseau internes de .NET. Pour l’activer, vous devez fournir une instance de LoggerFactory avec le niveau défini sur Trace, et définir EnableDebugMode sur true (ou l’activer manuellement via la classeClickHouse.Driver.Diagnostic.TraceHelper). Les événements seront consignés dans la catégorie ClickHouse.Driver.NetTrace. Avertissement : cela générera des logs extrêmement verbeux et aura un impact sur les performances. Il n’est pas recommandé d’activer le mode Débogage en production.
OpenTelemetry
Le driver intègre une prise en charge native du tracing distribué avec OpenTelemetry via l’API .NETSystem.Diagnostics.Activity. Lorsqu’il est activé, le driver émet des spans pour les opérations sur la base de données, qui peuvent être exportés vers des backends d’observabilité comme Jaeger ou ClickHouse lui-même (via l’OpenTelemetry Collector).
Activer le tracing
Dans les applications ASP.NET Core, ajoutez l’ActivitySource du driver ClickHouse à votre configuration OpenTelemetry :
Attributs des spans
Chaque span inclut les attributs de base de données standard d’OpenTelemetry, ainsi que des statistiques de requête propres à ClickHouse utiles pour le débogage.Options de configuration
Contrôlez le comportement du tracing à l’aide deClickHouseDiagnosticsOptions :
Configuration TLS
Lorsque vous vous connectez à ClickHouse via HTTPS, vous pouvez configurer le comportement de TLS/SSL de plusieurs manières.Validation personnalisée des certificats
Pour les environnements de production nécessitant une logique de validation des certificats personnalisée, fournissez votre propreHttpClient avec un gestionnaire ServerCertificateCustomValidationCallback configuré :
Points importants à prendre en compte lors de la fourniture d’un HttpClient personnalisé
- Décompression automatique : laissez
AutomaticDecompressiondésactivé. Le driver décode lui-même les réponses compressées, ce n’est donc pas nécessaire — et l’activer joue en votre défaveur côté requête : au moment de l’envoi, le handler ajoute aussi chaque algorithme de son masque à l’en-têteAccept-Encodingsortant, élargissant ce que le driver avait annoncé, si bien que ClickHouse peut répondre avec un codec que vous n’aviez pas demandé. Voir Décompression des réponses. - Délai d’inactivité : définissez
PooledConnectionIdleTimeoutsur une valeur inférieure aukeep_alive_timeoutdu serveur (10 secondes pour ClickHouse Cloud) afin d’éviter les erreurs de connexion dues à des connexions semi-ouvertes.
Performance tuning
Cette section décrit comment utiliser le client pour obtenir des performances optimales, ainsi que les différentes options que vous pouvez ajuster pour adapter les performances du client à votre cas d’usage.En un coup d’œil
| Si vous | Faites ceci | |---|---|---| | Lisez des rows dans des POCO | UtilisezQueryAsync<T>, et non MapTo<T> |
| Effectuez des insertions volumineuses | Augmentez InsertOptions.BatchSize |
| Exécutez une application console ou worker à forte charge d’insertion | Activez le Server GC |
| Lisez de gros résultats via le réseau | Laissez la compression des réponses activée (comportement par défaut) |
| Insérez via une liaison rapide | Essayez InsertOptions.Compressor = null |
| Insérez de nombreuses fois dans la même table | Utilisez UseSchemaCache ou ColumnTypes |
| Lisez de très gros résultats | Augmentez ReadBufferSize |
Lecture : choisir le chemin de matérialisation
Il existe trois façons d’extraire une row d’un résultat, et leur coût n’est pas le même. Certains chemins encapsulent les résultats (boxing), ce qui augmente les allocations et dégrade les performances.
Pour une lecture de 1 000 000 de rows sur 105 colonnes du dataset hits :
Les ORM bénéficient du chemin rapide lorsqu’ils utilisent des accesseurs typés. linq2db enregistre
GetInt64,
GetDouble et GetDateTime pour chaque colonne, et lit donc sans boxing. Le code qui lit via
GetValue (y compris un résultat dynamic renvoyé par Dapper) applique un boxing à chaque valeur. Si une requête ORM est très sollicitée
et lit via GetValue, utilisez QueryAsync<T> pour cette requête précise.Insertion : taille de batch et parallélisme
La taille de batch est le principal levier sur le throughput d’insertion.InsertOptions.BatchSize vaut par défaut
100 000 rows.
Utilisez de grands batches. Pour une insertion de 1 000 000 rows, passer de 10 000 à 100 000 rows par
batch a donné :
Si vous ne maîtrisez pas la taille de batch (par exemple lorsque de nombreux petits producteurs envoient des rows indépendamment), utilisez les async inserts et laissez le server se charger du batching.
Téléversements parallèles.
InsertOptions.MaxDegreeOfParallelism vaut 1 par défaut. Augmentez cette valeur pour envoyer
plusieurs batches simultanément. Le gain est surtout net lorsque la compression est activée, car chaque batch est alors compressé
sur son propre thread. Les sessions sont incompatibles avec les inserts parallèles : désactivez les sessions ou conservez
MaxDegreeOfParallelism = 1.
Supprimez la schema probe. Chaque appel à InsertBinaryAsync envoie d’abord une requête SELECT ... WHERE 1=0
afin de déterminer les types de colonne. Voir Ignorer la schema probe requête pour éliminer cet
aller-retour à l’aide de ColumnTypes ou UseSchemaCache.
Le chemin d’insertion sans boxing s’applique au format
RowBinary par défaut. RowBinaryWithDefaults doit
examiner chaque value pour repérer le marker DBDefault : il conserve donc le chemin le plus lent.Compression : les deux directions ne s’accordent pas
La compression échange du CPU contre des octets. L’intérêt de cet échange dépend du sens du transfert, du débit de votre connexion au serveur ClickHouse, de la manière dont vos données réagissent à l’algorithme de compression choisi, et du fait que vous payiez ou non chaque octet transféré. Lectures : laissez la compression activée, sauf si votre serveur s’exécute sur la même machine. C’est le comportement par défaut. Par rapport à l’absence de compression,zstd au niveau 1
a donné :
Insertions : mesurez avant de compresser. Les gains ne justifient pas toujours son activation. Gardez également à l’esprit que la décompression ajoute de la charge sur le serveur ; cette charge reste modeste pour Zstd et LZ4, mais peut être élevée pour d’autres algorithmes (par exemple Brotli).
Pour désactiver la compression des insertions :
Buffers
ReadBufferSize définit la taille du buffer qui lit les réponses HTTP. Sa valeur par défaut est de 64 Kio.
Le pilote emprunte ce buffer à un pool partagé et le restitue lorsqu’il libère le reader : il ne s’agit
donc pas d’une allocation pour chaque requête. Augmentez cette valeur pour réduire le nombre de
remplissages du buffer sur des résultats volumineux. Le pilote conserve un buffer par reader
ouvert simultanément : la consommation mémoire augmente donc avec la buffer size et avec le nombre
de readers concurrents.
Runtime et GC
Activez le Server GC pour les applications réalisant de nombreuses insertions. À code identique et à nombre d’octets alloués identique, le Workstation GC s’est révélé jusqu’à 97 % plus lent que le Server GC sur les insertions.Le GC Server est un réglage de throughput, pas de latency. Dans ces mêmes mesures, le GC Server a passé moins de la moitié du temps total en pause, mais ses pauses individuelles étaient plus longues (95ᵉ percentile à 114,6 ms contre 61,9 ms). Si votre service est sensible à la tail latency, mesurez les deux modes avant de choisir.
Latence : réutiliser les connexions
L’établissement d’une nouvelle connexion TCP et la réalisation du handshake TLS prennent un temps considérable. Réutiliser les connexions réduit nettement la latence de vos requêtes.- Ne créez pas un client pour chaque requête. Chaque nouveau client, avec son propre
HttpClient, crée un nouveau pool de connexions et repaie le coût du handshake. Utilisez un seulClickHouseClientpendant toute la durée de vie de l’application : il est thread-safe et conçu pour un usage en singleton. - Pour ADO.NET et les ORM, utilisez
ClickHouseDataSource, afin que toutes les connexions partagent un même pool.
Mesurez par vous-même
Dans bien des cas, les performances dépendront de la shape de vos données, de la vitesse de votre liaison au server, du fait que vous souhaitiez ou non troquer du CPU client contre du CPU server (ou l’inverse), des limitations de votre matériel, etc. Il est donc recommandé de mesurer vous-même les performances en fonction de vos données et de votre environnement. Pour connaître la part de travail effectuée par le server, définissezQueryOptions.QueryId et relisez les counters :
Prise en charge des ORM
Les ORM nécessitent l’API ADO.NET (ClickHouseConnection). Pour gérer correctement le cycle de vie des connexions, créez-les à partir d’un ClickHouseDataSource :
Dapper
ClickHouse.Driver est compatible avec Dapper. Le pilote convertit automatiquement la syntaxe @parameter de Dapper en syntaxe native {parameter:Type} de ClickHouse, en déduisant les types à partir des valeurs .NET.
Utilisez ClickHouseDataSource pour gérer correctement le cycle de vie de la connexion :
Modes de passage des paramètres
Tous les modes standard de passage des paramètres de Dapper sont pris en charge : Objets anonymes :DynamicParameters (à partir d’un dictionnaire ou d’un objet anonyme) :
Requêtes vers des POCO
Dapper associe les colonnes aux propriétés par leur nom (sans tenir compte de la casse) :Syntaxe native des paramètres ClickHouse
Lorsque vous avez besoin d’un contrôle explicite des types, utilisez directement dans le SQL la syntaxe{param:Type} de ClickHouse avec un Dictionary<string, object> pour les valeurs de paramètre. N’utilisez pas à la fois la syntaxe @param et la syntaxe {param:Type} pour un même paramètre.
WHERE IN
L’expansion native de IN dans Dapper fonctionne :WHERE id IN (@Ids1, @Ids2, @Ids3), et le driver convertit chaque paramètre étendu.
La fonction has() de ClickHouse avec un paramètre Array fonctionne également :
Gestionnaires de types personnalisés
Certains types ClickHouse, par exempleITuple, BigInteger et ClickHouseDecimal, nécessitent l’enregistrement de gestionnaires au démarrage :
Dapper.Contrib
GetAll<T>() et Get<T>(id) fonctionnent. En revanche, Insert<T>() ne fonctionne pas : il génère une syntaxe SQL Server (SCOPE_IDENTITY, []). Il est recommandé d’utiliser à la place la méthode native InsertBinaryAsync de ClickHouseClient.
Limitations
Linq2db
Ce pilote est compatible avec linq2db, un ORM léger et un fournisseur LINQ pour .NET. Consultez le site du projet pour une documentation détaillée. Exemple d’utilisation : Créez uneDataConnection à l’aide du fournisseur ClickHouse :
BulkCopyAsync pour effectuer efficacement des insertions en bloc.
Entity Framework Core
Le fournisseur officiel Entity Framework Core pour ClickHouse. Associez des classes C# à des tables ClickHouse, effectuez des requêtes avec LINQ et insérez des données viaSaveChanges — le tout avec les conventions EF Core habituelles.
- NuGet :
ClickHouse.EntityFrameworkCore - Source : GitHub
Ce fournisseur est activement développé. La version actuelle prend en charge les requêtes LINQ (y compris les JOIN, les sous-requêtes et les opérations ensemblistes),
INSERT via SaveChanges / BulkInsertAsync, les migrations avec DDL complet (CREATE / ALTER / DROP), ainsi que la configuration du moteur de table spécifique à ClickHouse. UPDATE / DELETE ne sont pas pris en charge.Installation
Démarrage rapide
Définissez votre entité et leDbContext, puis effectuez des requêtes avec LINQ :
Types pris en charge
Utilisez
ClickHouseDecimal (de ClickHouse.Driver.Numerics) au lieu de decimal lorsque vous avez besoin de toute la précision des colonnes Decimal128/Decimal256 — decimal en .NET est limité à 28–29 chiffres significatifs.
Opérations LINQ prises en charge
Requêtes :Where, OrderBy, Take, Skip, Select, First, Single, Any, All, Count, Distinct, AsNoTracking
GROUP BY et agrégats : GroupBy avec Count, LongCount, Sum, Average, Min, Max — y compris HAVING (.Where() après .GroupBy()), plusieurs agrégats dans une même projection et OrderBy sur les résultats agrégés.
JOINs : Join (INNER), schémas GroupJoin/SelectMany (LEFT et CROSS). LEFT JOIN renvoie de vraies valeurs null pour les lignes sans correspondance (voir la sémantique des valeurs nulles de LEFT JOIN ci-dessous).
Sous-requêtes : Contains / IN corrélés, Any / EXISTS, All, et sous-requêtes scalaires dans les projections.
Opérations ensemblistes : Concat (→ UNION ALL), Union (→ UNION DISTINCT), Intersect, Except.
Collections locales en mémoire : les joins et Contains sur des collections en mémoire (int[], List<T>, etc.) sont traduits en une série de UNION.
Méthodes de chaîne : Contains, StartsWith, EndsWith, IndexOf, Replace, Substring, Trim/TrimStart/TrimEnd, ToLower, ToUpper, Length, IsNullOrEmpty, Concat (et l’opérateur +).
Fonctions mathématiques : les méthodes standard de Math et MathF sont traduites en leurs équivalents ClickHouse — fonctions arithmétiques, logarithmiques, trigonométriques et utilitaires.
Le fournisseur injecte automatiquement set_join_use_nulls=1 dans chaque chemin de connexion afin de correspondre aux attentes d’Entity Framework concernant le comportement des JOIN.
Si votre serveur ClickHouse ou votre profil interdit la modification de ce paramètre (par exemple, un profil readonly=1), désactivez ce comportement avec :
0 / "" plutôt que == null.
Insertion de données
SaveChanges utilise l’API native InsertBinaryAsync du pilote — l’encodage RowBinary avec un corps de requête compressé est bien plus efficace que le SQL paramétré :
Added à Unchanged après la sauvegarde, comme avec tout autre fournisseur EF Core.
La taille du lot est configurable (1000 par défaut) :
Insertion en masse
Pour les chargements à haut débit, utilisezBulkInsertAsync au lieu de SaveChanges. Il s’agit d’une méthode d’extension sur DbContext qui contourne entièrement le suivi des modifications d’EF Core, la résolution des identités et la gestion d’état — elle appelle directement InsertBinaryAsync du pilote avec l’encodage RowBinary et un corps de requête compressé.
Cette méthode convient donc au chargement de grands ensembles de données lorsque vous n’avez pas besoin du suivi des entités après l’insertion :
IEnumerable<T> — les entités sont traitées en flux, sans être toutes chargées en mémoire. La valeur de retour est le nombre de lignes insérées. Les entités ne sont pas rattachées au DbContext après l’insertion, il n’y a donc pas de transition d’état Added → Unchanged.
Énumérations
Les colonnes ClickHouseEnum8/Enum16 peuvent être mappées sur des propriétés string ou sur des types C# enum. Lorsqu’on utilise des énumérations C#, le fournisseur convertit automatiquement l’énumération depuis et vers sa représentation sous forme de chaîne :
Conversions de type personnalisées
Le systèmeValueConverter d’EF Core vous permet d’associer des types personnalisés à des types déjà pris en charge par le fournisseur. Le fournisseur ne voit jamais votre type personnalisé — EF Core effectue la conversion à la limite entre les deux.
Conversion par propriété :
Annotations de type de colonne
Pour les types scalaires commestring, int, DateTime, etc., le fournisseur détermine automatiquement le type ClickHouse. Pour les types paramétrés et les wrappers, vous devez spécifier explicitement le type ClickHouse.
Utilisation des annotations de données (attributs) :
OnModelCreating :
Array(Nullable(Int32)) et LowCardinality(Nullable(String)) sont pris en charge — le fournisseur retire automatiquement les wrappers Nullable et LowCardinality à chaque niveau d’imbrication.
Colonnes Variant et Dynamic
Dans .NET, les colonnes ClickHouseVariant(T1, T2, ...) et Dynamic sont mappées à object. Comme object est trop générique pour une inférence de type automatique, vous devez déclarer explicitement le type de stockage via .HasColumnType() :
string, ulong, ulong[]).
Colonnes JSON
Le fournisseur prend en charge le type de colonneJson de ClickHouse, qu’il associe à System.Text.Json.Nodes.JsonNode (par défaut) ou à string (via un ValueConverter automatique) :
SaveChanges et BulkInsertAsync :
string avec un type de colonne Json — le fournisseur applique automatiquement un ValueConverter :
- Pas de traduction des chemins JSON —
entity.Data["name"]dans LINQ n’est pas converti en syntaxe SQL ClickHousedata.name. Filtrez sur des colonnes non JSON et examinez le JSON en mémoire. - Sémantique de NULL — le type JSON de ClickHouse renvoie
{}(objet vide) pour les valeurs NULL au lieu de SQL NULL. - Précision des entiers — le JSON de ClickHouse stocke tous les entiers en
Int64. Lors de la lecture viaJsonNode, utilisezGetValue<long>()plutôt queGetValue<int>().
Moteurs de table
Configurez les moteurs de table ClickHouse et les clauses propres à chaque moteur à l’aide de l’API fluideToTable(name, t => ...). Si aucun moteur n’est configuré, le fournisseur utilise par défaut MergeTree, avec ORDER BY déduit de la clé primaire de l’entité.
Clauses du moteur :
WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings. Elles s’appliquent toutes au builder de moteur renvoyé par HasXxxEngine().
Fonctionnalités au niveau des colonnes : HasCodec, HasTtl, HasComment, HasDefault — toutes sont prises en compte dans les migrations.
Index de saut de données — via HasIndex(...).HasSkippingIndexType(...):
Migrations
Processus standard des migrations EF Core :Limitations des migrations
Au-delà des migrations, le fournisseur ne prend pas encore en charge :
UPDATE/DELETE- Transactions :
BeginTransactionest sans effet. Les transactions ACID ne sont pas prises en charge dans ClickHouse. - Traduction des requêtes avec chemin JSON :
entity.Data["key"]dans LINQ ne se traduit pas en syntaxe SQL ClickHousedata.key. Filtrez sur des colonnes non JSON et inspectez le JSON en mémoire.
Limites
Tuples de 8 éléments ou plus avec un tuple imbriqué en dernière position
Les types C#ValueTuple de plus de 7 éléments utilisent un schéma d’imbrication généré par le compilateur : le 8e argument générique (TRest) est lui-même un ValueTuple qui contient les éléments restants. Par exemple, (int, int, int, int, int, int, int, string, string) est compilé en ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>.
Cela crée une ambiguïté lorsque la colonne ClickHouse est un tuple de 8 éléments dont le dernier est lui-même un tuple — par exemple, Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Tuple(String, String)). Le pilote ne peut pas faire la distinction entre :
- Un tuple plat de 9 éléments (imbrication TRest générée par le compilateur)
- Un tuple de 8 éléments dont le dernier élément est un
Tuple(String, String)imbriqué
ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>.
Le pilote traite le 8e argument comme TRest (c’est-à-dire qu’il l’aplatit), ce qui signifie que le cas du tuple de 8 éléments avec tuple imbriqué sera sérialisé de manière incorrecte.
Cela affecte à la fois System.Tuple et ValueTuple, car tous deux utilisent l’imbrication TRest au-delà de 7 éléments. Les tuples de 7 éléments ou moins, ou les tuples dont le dernier élément n’est pas lui-même un tuple, ne sont pas affectés.
Contournement : encapsulez le tuple interne dans une couche supplémentaire afin que le pilote puisse le distinguer de l’imbrication TRest :
Colonnes de type AggregateFunction
Les colonnes de typeAggregateFunction(...) ne peuvent pas être interrogées ni faire l’objet d’une insertion directe.
Pour insérer :