-
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 choix le plus adapté à la plupart des applications. -
ADO.NET (
ClickHouseDataSource,ClickHouseConnection,ClickHouseCommand) : les abstractions de base de données standard de .NET. Elles sont indispensables pour l’intégration avec un ORM (Dapper, Linq2db) et lorsque vous avez besoin de la compatibilité ADO.NET.ClickHouseBulkCopyest une classe utilitaire permettant d’insérer efficacement des données via une connexion ADO.NET.ClickHouseBulkCopyest obsolète et sera supprimée dans une version ultérieure ; utilisez plutôtClickHouseClient.InsertBinaryAsync.
Guide de migration
- Mettez à jour votre fichier
.csprojen indiquant le nouveau nom du paquet,ClickHouse.Driver, et sa dernière version disponible sur NuGet. - Remplacez toutes les références à
ClickHouse.ClientparClickHouse.Driverdans votre code source.
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
Vous pouvez configurer votre connexion à ClickHouse de deux façons :- Chaîne de connexion : ensemble de paires clé/valeur séparées par des points-virgules, précisant l’hôte, les identifiants d’authentification et les autres options de connexion.
- Objet
ClickHouseClientSettings: objet de configuration fortement typé, qui peut être chargé à partir de fichiers de configuration ou défini dans le code.
Paramètres de connexion
Format de données et sérialisation
Gestion des sessions
L’indicateur
UseSession permet de conserver la session côté serveur, et ainsi d’utiliser des statements SET et des tables temporaires. Les sessions sont réinitialisées après 60 secondes d’inactivité (délai d’expiration par défaut). Leur durée de vie peut être prolongée en définissant des paramètres de session à l’aide de statements ClickHouse ou dans 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’indicateur UseSession limite l’exécution à une seule requête active par connexion à la fois (cette limite est imposée par le serveur).Sécurité
Configuration du client HTTP
Journalisation et débogage
Paramètres personnalisés et rôles
Pour définir des paramètres personnalisés dans une chaîne de connexion, utilisez le préfixe
set_, par ex. « set_max_threads=4 ». Avec un objet ClickHouseClientSettings, n’utilisez pas le préfixe set_.Pour 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 permet de remplacer, requête par requête, les paramètres définis au niveau du client. Toutes les propriétés sont facultatives et ne remplacent les valeurs par défaut du client que si elles sont spécifiées.
Exemple :
InsertOptions
InsertOptions étend QueryOptions avec des paramètres propres aux insertions en masse effectuées via InsertBinaryAsync.
Toutes les propriétés de
QueryOptions sont également disponibles dans InsertOptions.
Exemple :
Éviter la requête de détection du schéma
Par défaut,InsertBinaryAsync envoie une requête SELECT ... WHERE 1=0 avant chaque insertion afin de déterminer les types des colonnes. Pour les scénarios à haut débit, vous pouvez éliminer ce surcoût de deux manières :
Option 1 : fournir explicitement les types de colonnes
Si vous connaissez le schéma de la table au moment de la compilation, transmettez-le directement via ColumnTypes. Aucune requête de schéma n’est alors envoyée :
UseSchemaCache = true pour ne récupérer le schéma qu’une seule fois et le réutiliser lors des insertions suivantes sur la même instance de ClickHouseClient :
ColumnTypesa priorité surUseSchemaCache. Si les deux sont définis, ce sont les types explicites qui sont utilisés.- Le cache de schéma ne détecte pas les modifications effectuées par
ALTER TABLE. Si vous modifiez le schéma de la table, créez un nouveauClickHouseClientou n’utilisez pasUseSchemaCachepour cette table. - Le cache est propre à chaque instance de
ClickHouseClientet indexé par le couple (base de données, table). Des sous-ensembles de colonnes différents d’une même table partagent un seul et même schéma mis en cache.
ClickHouseClient
ClickHouseClient est l’API recommandée pour interagir avec ClickHouse. Conçue pour être utilisée comme singleton, elle peut être employée simultanément par plusieurs threads en toute sécurité et gère en interne un 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 console ClickHouse Cloud.
Sélectionnez un service et cliquez sur Connect :
Choisissez C#. Les informations de connexion s’affichent en dessous.
Si vous utilisez ClickHouse en autogéré, les informations de connexion vous sont fournies par votre administrateur ClickHouse.
Avec une chaîne de connexion :
ClickHouseClientSettings :
IHttpClientFactory :
ClickHouseClient est conçu pour être utilisé sur la durée et partagé dans l’ensemble de votre application. Créez une seule instance (généralement un singleton) et réutilisez-la pour toutes les opérations sur la base de données. Le client gère lui-même le pool de connexions HTTP.Exécution des requêtes
UtilisezExecuteNonQueryAsync pour les statements qui ne renvoient aucun résultat :
ExecuteScalarAsync pour récupérer une valeur unique :
Insertion de données
Insertions paramétrées
Pour insérer des données, utilisez des requêtes paramétrées avecExecuteNonQueryAsync. Indiquez le type de chaque paramètre dans la requête SQL selon la syntaxe {name:Type} :
Insertions en masse
UtilisezInsertBinaryAsync pour insérer efficacement un grand nombre de lignes. Cette méthode envoie les données en continu au format binaire natif des lignes de ClickHouse, permet de téléverser des lots en parallèle et évite les erreurs « URL trop longue » qui peuvent survenir avec les requêtes paramétrées.
InsertOptions :
- Avant chaque insertion, le client récupère automatiquement la structure de la table à l’aide de
SELECT * FROM <table> WHERE 1=0. Les valeurs fournies doivent correspondre aux types des colonnes cibles. Pour éviter cette requête, utilisezInsertOptions.ColumnTypesouInsertOptions.UseSchemaCache. - Lorsque
MaxDegreeOfParallelism > 1, les lots sont téléversé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.Formatpour que le serveur applique les valeurs DEFAULT aux colonnes pour lesquelles aucune valeur n’est fournie.
Insertions de POCO
Plutôt que de créer des tableauxobject[], vous pouvez insérer directement des objets POCO fortement typés. Enregistrez le type une fois, puis transmettez un IEnumerable<T> :
Lorsque toutes les propriétés mappées définissent explicitement un
Type, la requête de détection du schéma est entièrement évitée. Si seules certaines propriétés définissent explicitement un type, le driver revient à cette requête pour l’ensemble des colonnes.
InsertBinaryAsync<T> prend en charge les mêmes InsertOptions (traitement par lots, 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 choisir les colonnes à insérer, utilisez [ClickHouseNotMapped] pour exclure des propriétés ou [ClickHouseColumn(Name = "...")] pour les renommer.Si ColumnTypes est défini dans InsertOptions, ses valeurs prévalent sur les attributs POCO.Évolution du schéma
Les insertions POCO continuent de fonctionner sans problème lorsque des colonnes sont ajoutées à la table cible après l’enregistrement du type. Comme le driver n’insère que les colonnes auxquelles les propriétés du POCO sont associées, le serveur renseigne automatiquement toute nouvelle colonne dotée d’une clauseDEFAULT (ou d’une autre expression par défaut). Il n’est pas nécessaire de modifier le code ni de réenregistrer le type.
Emplacement de la requête d’insertion
Lors d’une insertion binaire, le statementINSERT INTO ... FORMAT ... est placé sur la première ligne du corps de la requête, avant les lignes de données. Comme le corps est compressé par défaut, les mécanismes de routage et de journalisation qui n’inspectent que l’URL ne voient pas ce statement. Définissez InsertOptions.QueryPlacement sur InsertQueryPlacement.Url pour envoyer plutôt le statement dans le paramètre d’URL query, de sorte que le corps ne contienne que les lignes de données :
query, ou si vous souhaitez que le statement apparaisse dans les logs d’accès et les outils d’observability. Elle doit être activée explicitement, car le statement entre alors dans le calcul de la longueur de l’URL. La limite applicable est la plus basse de celles imposées par le runtime .NET, un intermédiaire ou le serveur. De .NET 6 à .NET 9, System.Uri limite l’URI complète de la requête encodée à 65 519 caractères ; si cette limite est dépassée, le driver lève une InvalidOperationException qui vous invite à revenir à InsertQueryPlacement.Body. Dans ClickHouse, la valeur par défaut de http_max_uri_size est de 1 Mio, mais un intermédiaire peut imposer une limite inférieure. En mode corps, le statement et les lignes ne sont pas soumis à cette limite de longueur d’URL ; d’autres options de requête peuvent néanmoins 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é permet d’accéder aux colonnes du résultat de façon typée, à l’aide de 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 leur position (à partir de 0) ou par leur nom.
Lecture des données dans des POCO
Plutôt que de lire les colonnes par index ou par nom, vous pouvez recevoir les résultats de la requête directement dans vos propres classes, au fil de l’eau. Enregistrez le type une seule fois auprès du client, puis utilisezQueryAsync<T> :
RegisterPocoType<T>() configure les correspondances pour l’insertion et la lecture, puis les valide toutes deux dès l’enregistrement. RegisterBinaryInsertType<T>() reste inchangé et ne concerne que l’insertion, afin de préserver la compatibilité descendante.
Un type enregistré doit comporter :
- Un constructeur public sans paramètre.
- Au moins une propriété publique dotée d’un accesseur en écriture public qui n’utilise pas
init. Les propriétésrequiredsont prises en charge.
InvalidOperationException. Une propriété de type object accepte donc n’importe quelle colonne.
QueryAsync<T> lit directement chacune de ces colonnes dans une propriété du type correspondant :
Chaque ligne accepte aussi la forme nullable du type de propriété (
long?, DateOnly?, etc.),
que la colonne soit de type Nullable(...) ou non. Une propriété de type valeur non nullable associée à une colonne
Nullable(T) est acceptée à l’enregistrement, mais provoque une exception si une valeur NULL est lue.
Les types enveloppants comme LowCardinality(T), SimpleAggregateFunction(f, T) et Object(T) se comportent exactement comme T.
Les colonnes composites sont également prises en charge et utilisent le type du framework indiqué dans la
référence des types de lecture : Array(T) devient T[], Tuple(...)
devient System.Tuple<...>, Nested(...) devient Tuple<...>[], JSON devient JsonObject (ou string
avec JsonReadMode=String), et Variant/Dynamic deviennent object.
Une colonne Map(K, V) est un cas particulier : une propriété List<KeyValuePair<K, V>> ou KeyValuePair<K, V>[]
est lue sans mise en boîte et conserve l’ordre de transmission ainsi que les 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 des clés et des valeurs doivent correspondre exactement : ainsi,
Map(String, Nullable(Int32)) nécessite KeyValuePair<string, int?>.
Lorsqu’une colonne admet plusieurs types de propriétés (une colonne DateTime sous forme de DateTime,
DateTimeOffset ou DateOnly, une colonne String sous forme de string ou byte[]), le type de propriété déclaré détermine la représentation. Ces autres représentations
sont propres à la lecture des POCO : QueryAsync<T> les prend donc en charge, mais pas MapTo<T>.
Lorsque vous parcourez un lecteur manuellement, utilisez ClickHouseDataReader.MapTo<T>() pour matérialiser la ligne en cours sous forme de POCO enregistré, sans passer à la ligne suivante :
MapTo<T> lorsque vous devez gérer vous-même la boucle de lecture, par exemple pour combiner l’accès direct aux colonnes
avec la materialization des POCO. Cette méthode lit la ligne à partir des valeurs encapsulées du lecteur : elle ne prend donc pas en charge
les autres types de propriétés mentionnés ci-dessus et génère davantage d’allocations que QueryAsync<T>. Préférez
QueryAsync<T> si vous n’avez besoin que des lignes ; consultez
choisir la méthode de materialization pour connaître les chiffres.
Un convertisseur de valeurs à la lecture configuré au niveau du client ou pour une requête donnée s’applique aux deux chemins et
ne désactive pas la lecture sans mise en boîte. Le driver convertit chaque colonne au moyen de la surcharge correspondant à son mode de lecture : la surcharge typée ConvertValue<T> pour une colonne lue sans mise en boîte,
et la surcharge ConvertValue avec mise en boîte pour une colonne composite. Implémentez les deux surcharges
de manière cohérente, faute de quoi une même colonne produira des résultats différents selon le chemin emprunté.
Lorsqu’une LoggerFactory est configurée, RegisterPocoType<T>() et RegisterBinaryInsertType<T>() émettent un message de journalisation de niveau Debug (catégorie ClickHouse.Driver.Client) précisant quelles propriétés sont associées à quelles colonnes, et lesquelles ont été ignorées, avec les raisons de leur exclusion. Consultez Journalisation et diagnostics.
Paramètres SQL
Dans ClickHouse, les paramètres des requêtes SQL s’écrivent selon le format standard{parameter_name:DataType}.
Exemples :
Les paramètres SQL ‘bind’ sont transmis sous forme de paramètres de requête dans l’URI HTTP. S’ils sont trop nombreux, une exception “URL trop longue” peut se produire. Pour éviter cette limitation lors d’insertions massives de données, utilisez
InsertBinaryAsync.Caractères de substitution @name de style ADO
Le driver accepte également les caractères de substitution @name utilisés par des ORM comme Dapper. Ils simplifient
l’utilisation côté client : avant l’envoi de la requête, chacun est réécrit sous la forme
{name:ResolvedType}. Le serveur ne voit donc jamais de @. Consultez
la résolution des types pour savoir comment le type est déterminé. Privilégiez la forme explicite
{name:Type} lorsque c’est possible.
Un @name sans paramètre correspondant est laissé tel quel et sera rejeté par le serveur. La correspondance est
sensible à la casse : @ID ne correspond donc pas au paramètre nommé id.
Pour désactiver cette réécriture, 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. Les
requêtes utilisant la syntaxe native {name:Type} continuent donc de fonctionner.Paramètres d’identifiant
Le type de paramètreIdentifier permet de passer en toute sécurité un nom de base de données, de table ou de colonne, plutôt qu’une chaîne littérale entre guillemets. Utilisez la syntaxe {name:Identifier} en SQL ou définissez ClickHouseDbParameter.ClickHouseType = "Identifier" :
Identifiant de requête
Chaque requête reçoit unquery_id unique, qui permet de récupérer des données dans la table system.query_log ou d’annuler les requêtes de longue durée. Vous pouvez définir un identifiant de requête personnalisé à l’aide de QueryOptions :
Correspondance personnalisée des types de paramètres
Lorsque vous utilisez des paramètres de la forme@ (par exemple, WHERE id = @id), le driver déduit automatiquement le type ClickHouse du type de la valeur .NET. Par exemple, le type int correspond à Int32.
Pour modifier ce comportement par défaut, définissez ParameterTypeResolver sur ClickHouseClientSettings. Vous pouvez ainsi utiliser DateTime64(3) pour tous les paramètres DateTime afin d’obtenir une précision à la milliseconde, ou appliquer un scale donné à tous les nombres décimaux, sans définir ClickHouseType pour chaque paramètre.
Utilisation de DictionaryParameterTypeResolver pour des correspondances de types simples :
IParameterTypeResolver pour les cas d’usage avancés :
Pour déterminer le type d’un paramètre en fonction de sa valeur ou de son nom, implémentez directement l’interface IParameterTypeResolver. Renvoyez null pour utiliser l’inférence par défaut :
QueryOptions.ParameterTypeResolver. Dans ce cas, il prend le pas sur le résolveur configuré au niveau du client.
Ordre de priorité pour la résolution des types :
Le résolveur intervient dans une chaîne de priorité. Par ordre décroissant :
ClickHouseTypedéfini explicitement sur le paramètre- Annotation de type SQL indiquée par la syntaxe
{name:Type}dans la requête IParameterTypeResolver(celui deQueryOptions.ParameterTypeResolverou, à défaut, celui deClickHouseClientSettings.ParameterTypeResolver)- Inférence de type intégrée (
TypeConverter.ToClickHouseType)
ClickHouseConnection via ADO.NET : les connexions créées à partir du client héritent de ces paramètres.
Formatage personnalisé des valeurs de paramètres
IParameterFormatter est un point d’extension qui détermine comment les valeurs des paramètres sont sérialisées. Utilisez-le lorsque le formatage intégré (par exemple, la précision de DateTime, les conventions locales pour les décimaux, l’échappement des chaînes ou la représentation des nombres) ne correspond pas à ce qu’attendent votre schéma ou les outils en aval.
Définissez ParameterFormatter dans ClickHouseClientSettings pour appliquer un formateur à toutes les requêtes paramétrées. Ce formateur reçoit la valeur, le nom du 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 laisser le formateur par défaut prendre le relais.
Utilisation de DictionaryParameterFormatter pour un formatage simple selon le type CLR :
IParameterFormatter personnalisé pour les cas d’usage avancés :
QueryOptions.ParameterFormatter. Dans ce cas, il prévaut sur celui configuré au niveau du client.
Valeurs composites :
Le formateur s’applique aussi bien aux paramètres de collection de premier niveau qu’à chaque élément des valeurs composites (Array, Tuple, Map, Nullable, LowCardinality, Variant). Par exemple, une correspondance pour typeof(int) formate séparément chaque élément Int32 d’un Array(Int32).
Encadrement par des apostrophes dans les valeurs composites :
Pour les types ClickHouse assimilables à des chaînes (String, FixedString, Enum8, Enum16, IPv4, IPv6, UUID) présents dans un littéral composite, le driver entoure la sortie du formateur d’apostrophes, sans toutefois échapper son contenu. Si la chaîne renvoyée contient une apostrophe ou un antislash non échappé, le littéral composite sera mal formé et le serveur rejettera la requête.
Les paramètres de chaîne de premier niveau (non intégrés dans une valeur composite) sont utilisés tels quels, sans être entourés d’apostrophes ; il n’est donc pas nécessaire de les échapper.
Ordre de priorité des formateurs :
IParameterFormatter(défini viaQueryOptions.ParameterFormatter, ou à défaut viaClickHouseClientSettings.ParameterFormatter). S’il renvoie une valeur autre quenull, celle-ci est utilisée.- Le formatage intégré propre à chaque type dans
HttpParameterFormatter.
null ou DBNull : elles sont toujours sérialisées sous la forme du marqueur de valeur nulle de ClickHouse (\N).
Conversion personnalisée des valeurs lues
IReadValueConverter permet de transformer les valeurs renvoyées par le lecteur de données après désérialisation, sans modifier leur type CLR. Parmi les cas d’usage courants : définir DateTime.Kind = Utc pour une colonne DateTime sans fuseau horaire, supprimer les espaces en début et en fin de chaîne ou normaliser les chaînes de caractères, ou encore traiter une colonne JSON avant que ses valeurs ne parviennent au code de l’application.
Définissez ReadValueConverter dans ClickHouseClientSettings pour appliquer un convertisseur à toutes les lectures. Celui-ci est appelé une fois par colonne et par ligne, aussi bien par la voie des valeurs encapsulées (GetValue) que par la voie générique (GetFieldValue<T>). Si aucun convertisseur n’est défini, il n’y a aucun surcoût : le lecteur renvoie directement les valeurs.
Utilisation de DictionaryReadValueConverter pour une conversion simple par type CLR :
For<T> restent inchangées. La sélection repose sur une correspondance exacte du type CLR : enregistrez donc le type effectivement produit par le lecteur (par exemple, For<JsonObject> pour une colonne JSON en mode JsonReadMode.Binary).
Convertisseur IReadValueConverter personnalisé pour les cas avancés :
Si la sélection doit reposer sur la chaîne représentant le type côté ClickHouse (par exemple, pour distinguer DateTime de DateTime('UTC'), qui sont tous deux représentés par le même type CLR), implémentez directement IReadValueConverter :
GetFieldType, GetSchemaTable) ne passent pas par lui et doivent rester cohérentes avec la valeur renvoyée.
Vous pouvez également définir un convertisseur pour chaque requête via QueryOptions.ReadValueConverter ; s’il est défini, il prévaut sur le convertisseur configuré au niveau du client.
Périmètre d’application du convertisseur :
Le convertisseur est appelé une fois par colonne, avec la valeur complète de la cellule désérialisée ; il ne parcourt pas récursivement les conteneurs composites. Pour une colonne Array(Int32), la valeur transmise est un int[] ; pour Tuple(Int32, String), c’est un ITuple.
Surcharge appelée :
Les deux surcharges doivent produire des résultats cohérents, car celle qu’appelle le driver dépend de la façon dont le code 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 lue sans mise en boîte par le chemin de lecture des POCO.ConvertValue(avec mise en boîte) —GetValue,GetValues, les indexeurs,GetChar,GetTupleet les chemins de conversion deGetBoolean,GetDecimaletGetString.
IsDBNull n’appelle aucun convertisseur : il lit directement l’indicateur de valeur nulle. Un convertisseur ne peut donc jamais
modifier le statut nul d’une valeur. TryGetEnumOrdinal ne passe pas non plus par le convertisseur ; voir
la lecture de la valeur ordinale d’une énumération.
Le convertisseur fonctionne également avec ClickHouseConnection d’ADO.NET : les connexions créées à partir du client héritent de ces paramètres.
Diffusion en flux brut
UtilisezExecuteRawResultAsync pour diffuser directement les résultats de la requête au format souhaité, sans passer par le lecteur de données. Cette méthode est utile pour exporter des données dans des fichiers ou les transmettre à d’autres systèmes :
JSONEachRow, CSV, TSV, Parquet, Native. Consultez la documentation sur les formats pour connaître toutes les options.
Compression des données transportées par requête
Par défaut, le client négociezstd, lz4, gzip, deflate lorsque Compression=true (valeur par défaut dans la chaîne de connexion) et décompresse lui-même le flux, de manière transparente.
Pour les exportations de données brutes (par exemple, Parquet, Arrow, Native), vous pouvez négocier un autre codec (par exemple, zstd ou lz4) afin de réduire le débit nécessaire au prix d’une utilisation accrue du CPU, sans modifier le paramètre de compression de la connexion. QueryOptions.AcceptEncoding et ClickHouseCommand.AcceptEncoding définissent l’en-tête HTTP Accept-Encoding pour une seule requête, à la place de toute valeur par défaut, et ajoutent enable_http_compression=1 à l’URL (ce que ClickHouse exige pour prendre en compte Accept-Encoding).
Configuration de HttpClient
Rien à configurer : leHttpClient créé par le driver conserve AutomaticDecompression à DecompressionMethods.None, et le driver décode lui-même les réponses. Ainsi, l’en-tête Content-Encoding n’est jamais supprimé à votre insu et vous recevez le corps brut exactement tel que le serveur l’a envoyé.
Corps des erreurs
Lorsque le serveur renvoie un code 4xx/5xx alors queenable_http_compression=1 est défini, il compresse le corps de l’erreur avec le codec qu’il aurait utilisé pour une réponse réussie. Le driver décode ce corps pour chacun des codecs qu’il prend en charge (lz4, zstd, gzip, deflate, br/brotli), afin que le message de ClickHouseServerException soit lisible. Pour les autres codecs (snappy, …), il renvoie un caractère de substitution qui indique le nom du codec et renvoie à system.query_log pour retrouver le texte d’origine de l’erreur.
Décompression des réponses
Accept-Encoding demande seulement au serveur de compresser la réponse : encore faut-il la décompresser. Le driver s’en charge lui-même en se fondant sur l’en-tête Content-Encoding de la réponse. Toutes les API de lecture habituelles (ExecuteReaderAsync, ExecuteScalarAsync, ExecuteNonQueryAsync, QueryAsync<T>, Dapper, EF Core, linq2db) fonctionnent donc avec les réponses compressées sans configuration supplémentaire. Il décompresse 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 utiliser un autre format, définissez vous-même Accept-Encoding pour l’ensemble du client :
ClickHouseClientSettings :
enable_http_compression=1 à l’URL, condition exigée par ClickHouse pour prendre en compte l’en-tête, même lorsque UseCompression vaut false : nommer explicitement un codec revient à en demander un. Si aucune valeur n’est définie, UseCompression=false n’envoie pas d’en-tête Accept-Encoding.
Accept-Encoding peut être défini à quatre endroits. Le premier à indiquer un codec prévaut :
QueryOptions.AcceptEncoding(ouClickHouseCommand.AcceptEncoding)CustomHeaders["Accept-Encoding"]sur la requêteCustomHeaders["Accept-Encoding"]sur le clientClickHouseClientSettings.AcceptEncoding, ou le mot-cléAcceptEncodingde la chaîne de connexion
identity.
C’est le serveur, et non le client, qui choisit le codec. ClickHouse examine Accept-Encoding selon son propre ordre de préférence, qui est fixe — zstd > br > lz4 > snappy > gzip > deflate — sans tenir compte de l’ordre dans lequel vous indiquez les codecs ni des valeurs q. L’en-tête indique donc les codecs acceptés sans imposer de choix : pour orienter celui-ci, il faut omettre certains codecs. La liste par défaut contient zstd : une requête utilisant cette liste reçoit donc une réponse compressée avec zstd, les autres codecs servant de solutions de repli. br peut être décodé, mais ne figure pas dans la liste par défaut.
La taille des charges utiles, l’utilisation du CPU du serveur et celle du client varient selon le codec, vos données, votre connexion et le paramètre http_zlib_compression_level du serveur (valeur par défaut à la livraison : 3) — voir Optimiser la compression.
http_zlib_compression_level. Ce paramètre s’applique à tous les codecs HTTP et sa valeur par défaut est 3. Ajustez-le en fonction de vos données, du débit de votre connexion et de l’utilisation du CPU.- Un client limité par le CPU sur une connexion rapide. Le driver décode le corps de la réponse sur le thread appelant. Lorsque le réseau n’est pas le goulot d’étranglement, la vitesse de décodage côté client peut donc devenir le facteur limitant.
Content-Encoding l’indique, quelle que soit la requête initiale : si cet en-tête est absent ou vaut identity, le corps reste inchangé ; s’il indique un codec pris en charge, le corps est décodé ; toute autre valeur déclenche une erreur qui la mentionne. Il n’y a aucun risque de double décodage : si la propriété AutomaticDecompression d’un handler fourni par l’appelant a déjà décodé le corps, elle supprime également l’en-tête Content-Encoding. Le driver reçoit donc des données en clair et les laisse telles quelles.
Les résultats bruts n’annoncent aucun codec. ExecuteRawResultAsync (ainsi que les méthodes publiques PostStreamAsync / InsertRawStreamAsync) vous transmettent le corps de la réponse tel quel. Si vous ne spécifiez pas vous-même de codec, ces méthodes n’en demandent donc aucun : le driver ne décode pas ce corps, et annoncer un codec dans ce cas transformerait silencieusement une exportation en fichier compressé. La règle est donc simple, quelle que soit la configuration de HttpClient : le corps de la réponse arrive exactement tel que le serveur l’a envoyé, et le serveur envoie des données en clair sauf si vous avez demandé un codec. Demander un codec (pour l’ensemble du client ou pour une requête donnée) permet d’exporter délibérément des octets compressés.
Un AcceptEncoding explicite (à l’un ou l’autre niveau) s’applique aussi aux requêtes brutes, et ClickHouseRawResult.ReadDecompressedStreamAsync() décode le résultat si vous le souhaitez ; ReadAsStreamAsync, ReadAsByteArrayAsync, ReadAsStringAsync et CopyToAsync renvoient toujours les octets exactement tels qu’ils ont été reçus.
leaveOpen : le libérer n’affecte donc pas la réponse. Si elle n’est pas compressée, vous obtenez directement le flux du contenu HTTP : le libérer ferme donc le corps de la réponse. Dans les deux cas, ClickHouseRawResult est propriétaire de la réponse : n’appelez plus ses autres méthodes de lecture après avoir libéré le flux. Vous devez toujours libérer ClickHouseRawResult, et cela suffit : cette opération libère la réponse ainsi que tout décodeur créé ici (les décodeurs conservent des tampons provenant d’un pool). Le await using ci-dessus est donc facultatif, mais vous pouvez le conserver sans risque. Plusieurs appels successifs renvoient le même flux ; ce type ne peut pas être utilisé simultanément par plusieurs opérations.
Consultez Select_007_ResponseCompression.cs pour un exemple exécutable.
Compression des requêtes d’insertion
Zstd est le codec utilisé par défaut pour les insertions :InsertOptions.Compressor est initialisé à ZstdCompressor.Default,
qui correspond à zstd au niveau 3. Pour changer de codec, définissez un autre compresseur ; pour envoyer le
corps sans compression, définissez null.
Default et d’un constructeur qui accepte un niveau
et une taille de tampon d’écriture :
Partagez les instances de compresseur. Chaque
Default est une instance partagée, et les quatre compresseurs peuvent
être utilisés simultanément par plusieurs threads sans risque — ce qui se produit lorsque
InsertOptions.MaxDegreeOfParallelism est supérieur à 1, car une même insertion utilise un compresseur par
lot. Aucun d’entre eux n’implémente IDisposable. Créez votre propre instance une seule fois, puis réutilisez-la
comme vous le feriez avec Default.IClickHouseCompressor est public et toute 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 redéfinissez donc que ceux dont votre codec a besoin.
Implémentez Decompress pour décoder les corps des réponses, en complément de la compression des requêtes.
Si un corps est corrompu ou dans un format incorrect, le flux renvoyé doit lever InvalidDataException.
InsertOptions.Compressor ne s’applique qu’aux insertions binaires. Les autres corps de requête du driver suivent d’autres règles de compression et ne passent pas par ce paramètre :
- Toute requête contenant du texte SQL (
ExecuteReaderAsync,ExecuteScalarAsync,ExecuteNonQueryAsync,QueryAsync<T>,ExecuteRawResultAsync, la couche ADO.NET) envoie son statement avecContent-Encoding: gziplorsqueUseCompressionvauttrue, ce qui est le cas par défaut. Le codec n’est pas configurable :AcceptEncodingne concerne que la réponse. Pour la requête, c’est donc gzip ou aucune compression. AvecCompression=false, le statement est envoyé sans compression. Les statements sont petits : il est rarement nécessaire de s’en préoccuper, mais cette distinction est utile lorsque vous examinez les requêtes dans un proxy ou une capture de paquets. - Un corps multipart — une requête dont les paramètres sont envoyés sous forme de données de formulaire (
UseFormDataParameters=true) — est toujours envoyé sans compression, quelle que soit la valeur deUseCompression. - Un téléversement brut (
InsertRawStreamAsync,PostStreamAsync) dispose de son propre indicateur, défini à chaque appel, et ne tient compte ni deUseCompressionni deInsertOptions.Compressor: il utilise gzip si cet indicateur est activé, et aucune compression sinon. Notez que le paramètreuseCompressiond’InsertRawStreamAsyncvauttruepar défaut : un téléversement brut est donc compressé avec gzip, sauf si vous passezfalse, même lorsqueCompression=falseest défini sur le client.
Optimiser la compression
La compression réduit le volume de données transférées au prix d’une consommation accrue de CPU. Son intérêt dépend presque entièrement du débit de votre connexion par rapport à la vitesse d’exécution du codec. Aucun paramètre ne convient à tous les cas.Le chiffre qui fait toute la différence
La compression est avantageuse tant que le codec est plus rapide que le réseau. Ce seuil est plus bas qu’on ne le pense généralement lors de la lecture, car ClickHouse compresse les réponses HTTP sur un seul thread dans le tampon de sortie. Sur un service ClickHouse Cloud à 16 vCPU, les mesures (hits, RowBinary, niveau 3) montrent que le serveur produit des données compressées à un débit d’environ 100 à 200 Mo/s.
Ainsi, pour un résultat volumineux, si une seule requête est traitée à la fois, la compression cesse d’être avantageuse aux alentours de 100 Mo/s. Un seul flux HTTPS
au sein d’une même région cloud dépasse généralement ce débit, tandis qu’un flux traversant l’Internet public, un VPN ou une frontière entre régions reste généralement en dessous.
Lors des insertions, la compression reste avantageuse jusqu’à des débits réseau plus élevés, car le client compresse les données sur un cœur qui lui est dédié et le fait généralement plus vite que le serveur ne compresse les réponses.
Repères selon le type de déploiement
Ce tableau ne tient pas compte de trois facteurs :
- Coût du trafic sortant : si le transfert de données vous est facturé, chaque octet a un coût, indépendamment de la latence, ce qui incite à choisir une compression plus forte, quelle que soit la vitesse de la connexion.
- Petits résultats : les indications ci-dessus concernent les charges utiles volumineuses. Pour les petites réponses, le choix du codec a peu d’importance : c’est le coût de traitement de chaque requête qui prédomine.
- Les insertions parallèles repoussent les seuils de débit indiqués pour les insertions. Chaque valeur de débit ci-dessus correspond à un seul thread.
InsertOptions.MaxDegreeOfParallelismvaut1par défaut, mais augmenter cette valeur permet de compresser plusieurs lots en parallèle. Le débit d’encodage global du client augmente donc à peu près proportionnellement au nombre de cœurs qui lui sont alloués. Ainsi, même sur une connexion rapide, il peut rester intéressant de compresser une insertion parallèle à des débits bien supérieurs à ceux pour lesquels la compression cesse d’être avantageuse avec un seul thread. Considérez les débits des lignes du tableau consacrées aux insertions comme des planchers et, si vous traitez déjà les lots en parallèle, refaites des tests avant de conclure que votre connexion est trop rapide pour que la compression soit utile.
Choisir un codec
Niveaux
La compression des réponses est contrôlée par un seul paramètre de serveur,http_zlib_compression_level, qui s’applique à tous les codecs HTTP, et pas seulement à zlib. Sa valeur par défaut est 3.
Ne le modifiez que si vos mesures le justifient. Au-delà de la valeur par défaut, le gain en volume est faible au regard du coût en CPU (pour zstd, passer de 3 à 6 double environ l’utilisation du CPU du serveur pour seulement 14 % d’octets en moins), et le coût de br devient démesuré. À l’inverse, au niveau 1, la situation change nettement : lz4 consomme beaucoup moins de CPU et zstd perd son avantage sur ce point. Si nécessaire, définissez ce paramètre pour chaque requête :
Mesurer votre propre seuil de bascule
Pour choisir le codec et le niveau de compression les mieux adaptés, le plus simple est de mesurer le temps d’exécution d’une même requête avec plusieurs codecs, puis de comparer les résultats.ProfileEvents dans system.query_log — définissez
QueryOptions.QueryId pour pouvoir retrouver la ligne :
LIMIT n sans ORDER BY renvoie des lignes différentes
à chaque exécution. Chaque essai compresse donc des données différentes, et les ratios ne sont plus comparables. Faites la comparaison
sur un ensemble de résultats fixe.
Insertion à partir d’un flux brut
UtilisezInsertRawStreamAsync pour insérer des données directement à partir de flux provenant de fichiers ou de la mémoire, dans des formats tels que CSV, JSON, Parquet ou tout autre format pris en charge par ClickHouse.
Insertion à partir d’un fichier CSV :
Consultez la documentation des paramètres de format pour connaître les options permettant de contrôler l’ingestion des données.
Autres exemples
Pour découvrir d’autres exemples d’utilisation, consultez le répertoire des exemples du dépôt GitHub.ADO.NET
La bibliothèque prend entièrement en charge ADO.NET viaClickHouseConnection, ClickHouseCommand et ClickHouseDataReader. Cette API est indispensable pour intégrer des ORM (Dapper, Linq2db) ou utiliser les abstractions standard de .NET pour les bases de données.
Gestion du cycle de vie avec ClickHouseDataSource
Créez toujours les connexions à partir d’unClickHouseDataSource pour assurer une gestion correcte de leur cycle de vie et bénéficier du pool de connexions. Le ClickHouseDataSource gère en interne un seul ClickHouseClient, dont toutes les connexions partagent le 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 statements de type INSERT, UPDATE, DELETE ou DDLExecuteScalarAsync()- Renvoie la première colonne de la première ligneExecuteReaderAsync()- Renvoie unClickHouseDataReaderpermettant de parcourir les résultats
Utilisation de ClickHouseDataReader
LeClickHouseDataReader permet d’accéder aux résultats des requêtes avec des types de données définis :
Lecture de la valeur ordinale d’une énumération
Une colonneEnum8 ou Enum16 se présente sous la forme de son libellé : GetFieldType renvoie string, et
GetString, GetValue et GetFieldValue<string> renvoient tous le libellé. Les accesseurs numériques
lèvent une InvalidCastException pour une colonne d’énumération, car la valeur stockée est une chaîne.
Utilisez TryGetEnumOrdinal pour obtenir la valeur numérique associée au libellé :
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 et fixe value à 0 pour une cellule NULL ou pour
toute colonne qui n’est pas de type enum. La valeur ordinale est la
valeur signée issue du format wire : elle peut donc être négative et, dans le cas d’un Enum16, dépasser la capacité 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 pour chaque endpoint. Il en résulte que :
- Les sessions de base de données sont multiplexées sur les connexions HTTP gérées par le pool.
- Le pool renouvelle automatiquement les connexions HTTP.
- 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 timestamps dans des colonnes
DateTime('UTC')et utilisezDateTimeKind.Utcdans votre code. Vous éviterez ainsi toute ambiguïté liée aux fuseaux horaires. -
Utilisez
DateTimeOffsetpour gérer explicitement les fuseaux horaires. Ce type représente toujours un instant précis et contient le décalage par rapport à UTC. -
Précisez le fuseau horaire dans les annotations de type SQL. Si vous utilisez des paramètres contenant des valeurs DateTime
Unspecifiedpour des colonnes qui ne sont pas en UTC, indiquez le fuseau horaire dans la requête SQL :
Insertions asynchrones
Les insertions asynchrones confient au serveur, plutôt qu’au client, le regroupement des données en lots. Le serveur met les données entrantes en tampon, puis les écrit dans le stockage lorsque les seuils configurés sont atteints, sans nécessiter de regroupement en lots côté client. Cette approche est particulièrement utile lorsque de nombreux agents envoient de petites charges utiles simultanément, comme pour les charges de travail d’observability. Activez les insertions asynchrones à l’aide deCustomSettings ou de la chaîne de connexion :
wait_for_async_insert) :
Principaux paramètres :
Sessions
N’activez les sessions que si vous avez besoin de fonctionnalités côté serveur nécessitant la conservation d’un état, par exemple :- Les tables temporaires (
CREATE TEMPORARY TABLE) - La conservation du contexte de requête d’un statement à l’autre
- Les paramètres propres à la session (
SET max_threads = 4)
Types de données pris en charge
ClickHouse.Driver prend en charge tous les types de données ClickHouse. Les tableaux ci-dessous indiquent les correspondances entre les types ClickHouse et les types .NET natifs utilisés pour lire les données de la base de données.
Correspondance des types : lecture depuis ClickHouse
Types entiers
Types à virgule flottante
Types décimaux
La conversion des types décimaux dépend du paramètre UseCustomDecimals.
Type booléen
Types de chaînes de caractères
Par défaut, les colonnes
String et FixedString(N) sont renvoyées sous forme de string. Pour les lire sous forme de byte[], définissez ReadStringsAsByteArrays=true dans votre chaîne de connexion. C’est utile si vous stockez des données binaires qui ne sont pas forcément encodées en UTF-8 valide.Ce paramètre s’applique aussi aux chaînes de caractères imbriquées dans d’autres types : Array(String) est ainsi lu sous forme de byte[][]
et Map(String, String) sous forme de Dictionary<byte[], byte[]>, y compris pour les clés. Seules les
colonnes JSON font exception : leurs valeurs de type chaîne, même imbriquées, sont toujours renvoyées sous forme de texte ; voir Type JSON.Types de date et d’heure
ClickHouse stocke les valeurs
DateTime et DateTime64 en interne sous forme d’horodatages Unix (en secondes ou en fractions de seconde écoulées depuis l’époque Unix). Le stockage se fait toujours en UTC, mais un fuseau horaire peut être associé aux colonnes et influer sur l’affichage et l’interprétation des valeurs.
Lors de la lecture des valeurs DateTime, la propriété DateTime.Kind est définie selon le fuseau horaire de la colonne :
Pour les colonnes dont le fuseau horaire n’est pas UTC, la valeur
DateTime renvoyée représente l’heure locale dans ce fuseau horaire. Utilisez ClickHouseDataReader.GetDateTimeOffset() pour obtenir une valeur DateTimeOffset avec le décalage horaire correspondant :
DateTime plutôt que DateTime('Europe/Amsterdam')), le driver renvoie une valeur DateTime avec Kind=Unspecified. L’heure est ainsi conservée exactement telle qu’elle est stockée, sans présumer du fuseau horaire.
Si vous souhaitez tenir compte du fuseau horaire pour les colonnes qui n’en définissent pas explicitement, vous pouvez :
- Définir un fuseau horaire explicite pour vos colonnes :
DateTime('UTC')ouDateTime('Europe/Amsterdam') - Appliquer vous-même le fuseau horaire après la lecture.
Type JSON
Le type renvoyé pour les colonnes JSON dépend du paramètre
JsonReadMode :
-
Binary(par défaut) : renvoieSystem.Text.Json.Nodes.JsonObject. Ce mode permet d’accéder aux données JSON de manière structurée, mais convertit les types ClickHouse spécialisés (comme les adresses IP, les UUID et les nombres décimaux de grande taille) en chaînes de caractères au sein de la structure JSON. -
String: renvoie le JSON brut sous forme destring. Ce mode conserve la représentation JSON exacte fournie par ClickHouse. Il est utile si vous devez transmettre le JSON sans l’analyser ou gérer vous-même la désérialisation.
None est un troisième mode. Il lit les données de la même manière que Binary, mais n’envoie aucun paramètre serveur avec la
requête : utilisez-le sur une connexion qui n’autorise pas la définition d’un tel paramètre.
Un chemin déclaré dans le type de la colonne est un chemin typé ; tout autre chemin du document est un
chemin dynamique. La différence apparaît lorsque la valeur est nulle.
Un chemin typé figure toujours dans le JsonObject. S’il est déclaré comme Nullable(T) ou Dynamic, il est renvoyé
sous forme de valeur nulle JSON, que la valeur stockée soit nulle ou que le document ne contienne pas ce chemin : ces deux
cas sont indiscernables :
JSON(x String)
donne {"x":""} et JSON(x Int64) donne {"x":0}.
Un chemin dynamique dont la valeur est nulle est entièrement omis de l’objet ; ContainsKey renvoie donc
false pour ce chemin. La lecture de {"x":null} dans une colonne JSON sans type de chemin déclaré donne {}.
Les chemins typés imbriqués créent leurs éléments parents : JSON(a.b Nullable(Int64)) produit {"a":{"b":null}}
même pour un document vide.
C’est ainsi que le serveur lui-même représente ces valeurs : les modes
Binary et String donnent désormais le même résultat. Avant la version 1.4.0, un
chemin typé contenant une valeur nulle était omis du JsonObject ; {"x":null} était donc 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 ne prend pas en charge les tableaux d’octets ; un byte[] serait donc
représenté en base64. Cela vaut pour String et FixedString, y compris lorsqu’ils sont encapsulés dans
LowCardinality, Nullable ou SimpleAggregateFunction, ainsi que pour les chaînes de caractères dans Array et Map,
y compris les clés des maps.
Un tableau d’octets dont le lecteur JSON ne peut pas connaître le type est néanmoins représenté en base64 : un
chemin typé
Variant ou Dynamic contient une valeur dont le type n’est connu qu’au niveau de chaque ligne. Une chaîne
dans Variant(Array(UInt8), String) est donc renvoyée encodée en base64. Ce comportement est identique pour les deux valeurs du paramètre.Si le type d’une clé de map JSON n’est pas exactement String — par exemple,
Map(LowCardinality(String), String) — une NotSupportedException est levée.JSON(a Int64, a.b Int64). Les deux chemins sont présents dans chaque ligne : le serveur
produit donc une ligne avec une clé en double : {"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 qui indique les deux chemins. Il en va
de même lorsque la valeur est une Map, comme dans JSON(a Map(String, Int64)), si la ligne
contient aussi un chemin dynamique a.b.
Cela ne s’applique que si les deux chemins contiennent une valeur dans la ligne. Si l’un ne contient rien — une valeur nulle,
un objet vide ou un sous-arbre dont toutes les valeurs sont nulles —, il cède la place à celui qui contient des données,
quel que soit l’ordre dans lequel le serveur envoie les deux chemins. Avec des types Nullable, un seul des deux chemins est donc renseigné par ligne, et la lecture ne génère pas
d’erreur : JSON(a Nullable(Int64), a.b Nullable(Int64)) donne {"a":5} et {"a":{"b":7}}, comme
prévu.
Lisez une telle colonne avec JsonReadMode.String pour obtenir le texte JSON tel que le serveur l’envoie, sans modification, y compris la clé
en double.
Définissez AllowDuplicateJsonKeys pour continuer à lire la colonne comme un JsonObject sans lever d’exception. Le
driver conserve alors celle des deux valeurs qui apparaît en dernier dans la ligne et ignore l’autre : la lecture entraîne donc
une perte de données. Ainsi, JSON(a Int64, a.b Int64) contenant {"a.b":7} est lu comme {"a":0}. Si un chemin
contient une valeur et que son parent contient un scalaire ou un tableau, une exception est tout de même levée, car un sous-arbre ne peut être
placé sous aucun des deux.
Type Map
Dans ClickHouse, un
Map(K, V) est stocké sous la forme d’un Array(Tuple(K, V)) et peut contenir plusieurs entrées ayant la même clé. Ce n’est pas le cas d’un Dictionary : en mode par défaut, seule la dernière valeur associée à une clé répétée est conservée, et les paires précédentes sont supprimées. Le paramètre MapReadMode détermine la représentation utilisée :
-
Dictionary(par défaut) : renvoie unDictionary<K, V>. -
KeyValuePairs: renvoie unList<KeyValuePair<K, V>>dans l’ordre où le serveur a envoyé les paires. Toutes sont ainsi conservées, y compris celles dont la clé est répétée.
Map ; il s’applique donc aussi à GetFieldValue<T>, aux types de schéma renvoyés par le driver et au mappage des propriétés POCO. Il s’applique dès qu’un type Map apparaît dans l’arborescence des types d’une colonne, notamment dans Array(Map(...)), Map(K, Map(...)), Tuple(..., Map(...)) et Dynamic.
Les deux représentations sont acceptées à l’écriture, quel que soit le mode ; voir l’écriture des Map.
Autres types
Pour chaque ligne, les valeurs des types Dynamic et Variant sont converties dans le type correspondant à leur type sous-jacent réel.
Types géométriques
Le type Geometry est un type Variant qui peut contenir n’importe quel type géométrique. Sa valeur est convertie dans le type correspondant.
Correspondance des types : écriture dans ClickHouse
Lors de l’insertion de données, le driver convertit les types .NET en types ClickHouse correspondants. Les tableaux ci-dessous indiquent les types .NET acceptés pour chaque type de colonne ClickHouse.Types entiers
Types à virgule flottante
Type booléen
Types de chaînes de caractères
Types de date et d’heure
Valeurs hors plageLors de l’écriture en binaire, les valeurs
Date, Date32, DateTime et DateTime32 hors de leur plage prise en charge déclenchent une ArgumentOutOfRangeException au moment de l’appel à Write. L’exception indique le type de la colonne et la plage prise en charge. Auparavant, ces valeurs pouvaient être tronquées sans avertissement lors de leur conversion en entier 32 bits, puis réinterprétées par le serveur, ce qui produisait des timestamps valides, mais erronés.DateTime.Kind lors de l’écriture des valeurs :
Les valeurs
DateTimeOffset conservent toujours l’instant exact.
Exemple : DateTime UTC (instant préservé)
DateTimeKind.Utc ou DateTimeOffset pour toutes les opérations sur les valeurs DateTime. Votre code se comportera ainsi de la même manière, quel que soit le fuseau horaire du serveur, du client ou de la colonne.
Paramètres HTTP et copie en masse : quelle différence ?
La liaison de paramètres HTTP et la copie en masse ne traitent pas de la même façon les valeurs DateTimeUnspecified lors de l’écriture :
La copie en masse connaît le fuseau horaire de la colonne cible et interprète correctement les valeurs Unspecified dans ce fuseau horaire.
Les paramètres HTTP ne connaissent pas automatiquement le fuseau horaire de la colonne. Vous devez donc l’indiquer dans l’annotation de type SQL :
Types décimaux
Type JSON
Le paramètre
JsonWriteMode détermine le comportement lors de l’écriture de données JSON :
-
String(par défaut) : acceptestring,JsonObject,JsonNodeou tout autre objet. Toutes les entrées sont sérialisées avecSystem.Text.Json.JsonSerializer, puis envoyées sous forme de chaînes JSON pour analyse côté serveur. C’est le mode le plus souple et il ne nécessite pas d’enregistrer les types. -
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. Il faut appelerconnection.RegisterJsonSerializationType<T>()avant de l’utiliser. Dans ce mode, l’écriture de valeursstringouJsonNodelèveArgumentException.
JSON(id UInt64, price Decimal128(2))), le driver s’en sert pour sérialiser les valeurs en respectant exactement leurs types. La précision des types tels que UInt64, Decimal, UUID et DateTime64 est ainsi préservée, alors qu’elle serait perdue lors d’une sérialisation en JSON générique.
Les POCO peuvent être écrits dans des colonnes JSON de deux manières, selon la valeur de JsonWriteMode :
Mode String (par défaut) : les POCO sont sérialisés avec System.Text.Json.JsonSerializer. Il n’est pas nécessaire d’enregistrer les types. C’est la méthode la plus simple, qui fonctionne aussi avec les objets anonymes.
Mode Binary : les POCO sont sérialisés au format JSON binaire du driver, avec une prise en charge complète des indications de type. Les types doivent être enregistrés avec connection.RegisterJsonSerializationType<T>() avant d’être utilisés. Ce mode permet de personnaliser la correspondance entre les propriétés et les chemins JSON à l’aide d’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 Binary. -
[ClickHouseJsonIgnore]: exclut une propriété de la sérialisation. Fonctionne uniquement en mode Binary.
UserId ne correspond qu’à une indication définie comme UserId, et non comme userid. Cela reflète le comportement de ClickHouse, qui permet à des chemins tels que userName et UserName de coexister en tant que champs distincts.
Limitations (mode Binary uniquement) :
- Les types POCO doivent être enregistrés sur la connexion à l’aide de
connection.RegisterJsonSerializationType<T>()avant la sérialisation. La sérialisation d’un type non enregistré lève une exceptionClickHouseJsonSerializationException. - Pour être correctement sérialisées, les propriétés de type dictionnaire ou tableau/liste nécessitent des indications de type dans la définition de la colonne. À défaut, utilisez le mode String.
- Les valeurs nulles des propriétés POCO ne sont écrites que si 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 de type sont donc ignorées. - Les attributs
ClickHouseJsonPathetClickHouseJsonIgnoresont ignorés en mode String (ils ne fonctionnent qu’en mode Binary).
Autres types
Types géométriques
Non pris en charge en écriture
Gestion des types imbriqués
Les types imbriqués de ClickHouse (Nested(...)) se lisent et s’écrivent comme des tableaux.
Journalisation et diagnostic
Le client .NET de ClickHouse s’appuie sur les abstractions deMicrosoft.Extensions.Logging pour proposer une journalisation légère, activée uniquement si nécessaire. Lorsqu’elle est activée, le driver émet des messages structurés sur les événements liés au cycle de vie des connexions, l’exécution des commandes, les opérations de transport et les insertions en masse. La journalisation est entièrement facultative : les applications qui ne configurent pas de logger continuent de fonctionner sans surcoût.
Démarrage rapide
Utilisation d’appsettings.json
Vous pouvez définir les niveaux de journalisation avec la configuration standard de .NET :Utiliser une configuration en mémoire
Vous pouvez également définir dans le code le niveau de détail de la journalisation pour chaque catégorie :Catégories et émetteurs
Le driver utilise des catégories distinctes pour vous permettre de régler le niveau de journalisation de chaque composant :Exemple : diagnostiquer les problèmes de connexion
- Le choix de la fabrique de clients HTTP (pool par défaut ou connexion unique)
- La configuration du handler HTTP (SocketsHttpHandler ou HttpClientHandler)
- Les paramètres du pool de connexions (MaxConnectionsPerServer, PooledConnectionLifetime, etc.)
- Les paramètres de délai d’expiration (ConnectTimeout, Expect100ContinueTimeout, etc.)
- La configuration SSL/TLS
- Les événements d’ouverture et de fermeture des connexions
- Le suivi de l’ID de session
Mode de débogage : traçage réseau et diagnostics
Pour faciliter le diagnostic des problèmes réseau, la bibliothèque du driver comprend un utilitaire permettant de tracer à bas niveau le fonctionnement interne du réseau .NET. Pour activer ce traçage, vous devez fournir une LoggerFactory avec le niveau défini sur Trace et définir EnableDebugMode sur true (ou l’activer manuellement à l’aide de la classeClickHouse.Driver.Diagnostic.TraceHelper). Les événements seront consignés dans la catégorie ClickHouse.Driver.NetTrace. Avertissement : cette opération générera des logs extrêmement détaillés et nuira aux performances. Il est déconseillé d’activer le mode de débogage en production.
OpenTelemetry
Le driver prend en charge nativement le traçage distribué OpenTelemetry au moyen de l’API .NETSystem.Diagnostics.Activity. Une fois activé, il émet des spans pour les opérations de base de données. Ces spans peuvent être exportés vers des plateformes d’observability comme Jaeger ou ClickHouse lui-même (via l’OpenTelemetry Collector).
Activer le traçage
Dans les applications ASP.NET Core, ajoutez l’ActivitySource du driver ClickHouse à votre configuration OpenTelemetry :
Attributs des spans
Chaque span comporte les attributs OpenTelemetry standard relatifs aux bases de données, ainsi que des statistiques sur les requêtes propres à ClickHouse, utiles pour le débogage.Options de configuration
Configurez le comportement du traçage à l’aide deClickHouseDiagnosticsOptions :
Configuration TLS
Pour vous connecter à ClickHouse en HTTPS, vous pouvez configurer TLS/SSL de plusieurs manières.Validation personnalisée des certificats
Dans les environnements de production qui nécessitent une logique de validation des certificats personnalisée, fournissez votre propreHttpClient avec un handler ServerCertificateCustomValidationCallback configuré :
Précautions à prendre si vous fournissez un HttpClient personnalisé
- Décompression automatique : laissez
AutomaticDecompressiondésactivé. Le driver décompresse lui-même les réponses : cette option est donc inutile. L’activer peut même poser problème côté requête : lors de l’envoi, le handler ajoute également tous les algorithmes de son masque à l’en-têteAccept-Encodingsortant, en plus de ceux annoncés par le driver. ClickHouse peut alors répondre avec un codec que vous n’avez pas demandé. Consultez 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.
Optimisation des performances
Cette section explique comment tirer le meilleur parti du client et quelles options ajuster pour optimiser ses performances en fonction de votre cas d’utilisation.En bref
| Si vous | Faites ceci | |---|---|---| | Chargez des lignes dans des objets POCO | UtilisezQueryAsync<T> plutôt que MapTo<T> |
| Effectuez des insertions volumineuses | Augmentez InsertOptions.BatchSize |
| Exécutez une application console ou un worker qui effectue de nombreuses insertions | Activez le Server GC |
| Lisez des résultats volumineux via le réseau | Gardez la compression des réponses activée (c’est le cas par défaut) |
| Effectuez des insertions via une connexion rapide | Essayez InsertOptions.Compressor = null |
| Effectuez de nombreuses insertions dans la même table | Utilisez UseSchemaCache ou ColumnTypes |
| Lisez des résultats très volumineux | Augmentez ReadBufferSize |
Lecture : choisir le mode de matérialisation
Il existe trois façons de lire une ligne dans un résultat, et leur coût varie. Certaines impliquent une mise en boîte des valeurs, ce qui augmente les allocations mémoire et réduit les performances.
Pour la lecture de 1 000 000 de lignes comportant 105 colonnes du jeu de données hits :
Les ORM empruntent le chemin d’exécution optimisé lorsqu’ils utilisent des accesseurs typés. linq2db enregistre
GetInt64,
GetDouble et GetDateTime pour chaque colonne, ce qui permet de lire les valeurs sans mise en boîte. Le code qui passe par
GetValue (y compris pour un résultat dynamic de Dapper) met chaque valeur en boîte. Si une requête ORM est exécutée fréquemment
et passe par GetValue, utilisez QueryAsync<T> pour cette requête uniquement.Insertion : taille des lots et parallélisme
La taille des lots est le facteur qui influe le plus sur le débit des insertions. Par défaut,InsertOptions.BatchSize est fixé à
100 000 lignes.
Utilisez de grands lots. Pour une insertion de 1 000 000 de lignes, le passage de 10 000 à 100 000 lignes par
lot a donné les résultats suivants :
Si vous ne pouvez pas maîtriser la taille des lots (par exemple, lorsque de nombreux petits producteurs envoient des lignes séparément), utilisez les insertions asynchrones et laissez le serveur regrouper les lignes en lots.
Téléversements parallèles. Par défaut,
InsertOptions.MaxDegreeOfParallelism est fixé à 1. Augmentez cette valeur pour envoyer
plusieurs lots simultanément. Le gain est particulièrement important lorsque la compression est activée, car chaque lot est alors compressé
dans son propre thread. Les sessions sont incompatibles avec les insertions parallèles : désactivez-les ou conservez
MaxDegreeOfParallelism = 1.
Supprimez la requête d’inspection du schéma. Chaque appel à InsertBinaryAsync envoie d’abord une requête SELECT ... WHERE 1=0
pour déterminer les types des colonnes. Consultez Éviter la requête d’inspection du schéma pour éviter cet
aller-retour grâce à ColumnTypes ou à UseSchemaCache.
Le chemin d’insertion sans mise en boîte s’applique au format
RowBinary par défaut. RowBinaryWithDefaults doit
examiner chaque valeur pour repérer le marqueur DBDefault ; il emprunte donc le chemin plus lent.Compression : les résultats diffèrent selon le sens du transfert
La compression réduit le volume de données transférées au prix d’une consommation accrue de CPU. Son intérêt dépend du sens du transfert, du débit de votre connexion au serveur ClickHouse, de la façon dont vos données se prêtent à l’algorithme de compression choisi et du coût éventuel de chaque octet transféré. Lectures : laissez la compression activée, sauf si le serveur s’exécute sur la même machine. Elle est activée par défaut. Par rapport à un transfert sans compression,zstd au niveau 1
a donné les résultats suivants :
Insertions : mesurez les performances avant d’activer la compression. Les gains peuvent être insuffisants pour la justifier. N’oubliez pas non plus que la décompression accroît la charge du serveur : cette charge est modérée avec Zstd et LZ4, mais peut être élevée avec d’autres algorithmes, comme Brotli.
Pour désactiver la compression des insertions :
Tampons
ReadBufferSize définit la taille du tampon utilisé pour lire les réponses HTTP. Par défaut, cette taille est de 64 Kio.
Le driver emprunte ce tampon à un pool partagé et le restitue lorsqu’il libère le lecteur : il n’effectue donc
pas d’allocation à chaque requête. Augmentez cette taille pour limiter les remplissages du tampon lorsque les
résultats sont volumineux. Le driver conserve un tampon par lecteur ouvert simultanément : la consommation de mémoire
augmente donc avec la taille du tampon et le nombre de lecteurs ouverts en parallèle.
Environnement d’exécution et ramasse-miettes
Activez Server GC pour les applications qui effectuent de nombreuses insertions. À code identique et à nombre d’octets alloués égal, les insertions étaient jusqu’à 97 % plus lentes avec Workstation GC qu’avec Server GC.Le Server GC est un paramètre qui privilégie le débit, pas la latence. Lors de ces mêmes mesures, le temps total
passé en pause était plus de deux fois moindre avec le Server GC, mais chaque pause était plus longue
(114,6 ms au 95e percentile, contre 61,9 ms). Si votre service est sensible à la latence des requêtes les plus lentes, mesurez
les deux modes avant de faire votre choix.
Latence : réutiliser les connexions
L’établissement d’une nouvelle connexion TCP et la négociation TLS prennent beaucoup de temps. Réutiliser les connexions réduit considérablement la latence de vos requêtes.- Ne créez pas de client pour chaque requête. Chaque nouveau client disposant de son propre
HttpClientcrée un nouveau pool de connexions et doit répéter la négociation TLS. Utilisez un seulClickHouseClientpendant toute la durée de vie de l’application. Il peut être utilisé simultanément par plusieurs threads et est conçu pour être utilisé comme singleton. - Pour ADO.NET et les ORM, utilisez
ClickHouseDataSourceafin que toutes les connexions partagent le même pool.
Mesurez les performances par vous-même
Dans bien des cas, les performances dépendent de la structure de vos données, du débit de votre connexion au serveur, de la façon dont vous répartissez la charge CPU entre le client et le serveur, des limites de votre matériel, etc. Il est donc recommandé de mesurer les performances avec vos propres données, dans votre environnement. Pour connaître la part du travail effectuée par le serveur, définissezQueryOptions.QueryId et récupérez les compteurs :
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 driver convertit automatiquement la syntaxe @parameter de Dapper en syntaxe native {parameter:Type} de ClickHouse et déduit les types des valeurs .NET.
Utilisez ClickHouseDataSource pour gérer correctement le cycle de vie des connexions :
Modes de passage des paramètres
Dapper prend en charge tous les modes standard de passage des paramètres : Objets anonymes :DynamicParameters (à partir d’un dictionnaire ou d’un objet anonyme) :
Requêtes avec des POCO
Dapper associe les colonnes aux propriétés par leur nom (sans tenir compte de la casse) :Syntaxe des paramètres native de ClickHouse
Si vous devez définir explicitement le type d’un paramètre, utilisez directement la syntaxe{param:Type} de ClickHouse dans le code SQL, avec un Dictionary<string, object> contenant les valeurs des paramètres. N’utilisez pas à la fois les syntaxes @param et {param:Type} pour un même paramètre.
WHERE IN
L’expansion native de la clause IN par Dapper fonctionne :WHERE id IN (@Ids1, @Ids2, @Ids3), puis le driver convertit chaque paramètre issu de cette expansion.
La fonction has() de ClickHouse fonctionne aussi avec un paramètre Array :
Handlers de type personnalisés
Certains types ClickHouse, commeITuple, BigInteger et ClickHouseDecimal, nécessitent l’enregistrement de handlers au démarrage :
Dapper.Contrib
GetAll<T>() et Get<T>(id) fonctionnent. En revanche, Insert<T>() ne fonctionne pas : cette méthode génère une syntaxe propre à SQL Server (SCOPE_IDENTITY, []). Utilisez plutôt la méthode native InsertBinaryAsync de ClickHouseClient.
Limitations
Linq2db
Ce driver est compatible avec linq2db, un ORM léger et un fournisseur LINQ pour .NET. Pour plus de détails, consultez la documentation sur le site du projet. Exemple d’utilisation : Créez uneDataConnection à l’aide du fournisseur ClickHouse :
BulkCopyAsync pour effectuer des insertions en masse efficacement.
Entity Framework Core
Le fournisseur officiel Entity Framework Core pour ClickHouse. Associez des classes C# à des tables ClickHouse, interrogez-les avec LINQ et insérez des données viaSaveChanges — le tout selon les pratiques habituelles d’EF Core.
- NuGet :
ClickHouse.EntityFrameworkCore - Code source : GitHub
Ce fournisseur est activement développé. La version actuelle prend en charge les requêtes LINQ (y compris les jointures, les sous-requêtes et les opérations ensemblistes), les insertions avec
INSERT via SaveChanges / BulkInsertAsync, les migrations avec une prise en charge complète du DDL (CREATE / ALTER / DROP) et la configuration des table engines spécifiques à ClickHouse. UPDATE / DELETE ne sont pas pris en charge.Installation
Démarrage rapide
Définissez votre entité et votreDbContext, puis effectuez une requête avec LINQ :
Types pris en charge
Utilisez
ClickHouseDecimal (de ClickHouse.Driver.Numerics) plutôt que decimal si vous avez besoin de la pleine précision des colonnes Decimal128/Decimal256 : le type decimal de .NET est limité à 28 ou 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égations : GroupBy avec Count, LongCount, Sum, Average, Min, Max — y compris HAVING (.Where() après .GroupBy()), plusieurs agrégations dans une même projection et OrderBy sur les résultats agrégés.
Jointures : Join (INNER), combinaisons GroupJoin/SelectMany (LEFT et CROSS). Une jointure LEFT JOIN renvoie 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 sur les ensembles : Concat (→ UNION ALL), Union (→ UNION DISTINCT), Intersect, Except.
Collections locales intégrées : les jointures et les appels à Contains portant sur des collections en mémoire (int[], List<T>, etc.) sont traduits en une série d’opérations UNION.
Méthodes sur les chaînes de caractères : 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 fonctions ClickHouse équivalentes — fonctions arithmétiques, logarithmiques, trigonométriques et utilitaires.
Le fournisseur ajoute automatiquement set_join_use_nulls=1 à chaque chemin de connexion afin que le comportement des jointures corresponde aux attentes d’Entity Framework.
Si votre serveur ClickHouse ou votre profil interdit la modification de ce paramètre (par exemple, avec un profil readonly=1), désactivez cet ajout avec :
0 / "" plutôt qu’avec == null.
Insertion de données
SaveChanges utilise l’API native InsertBinaryAsync du driver : un encodage RowBinary avec un corps de requête compressé, bien plus efficace que des requêtes SQL paramétrées :
Added à l’état Unchanged après l’enregistrement, comme avec n’importe quel autre fournisseur EF Core.
La taille des lots est configurable (1 000 par défaut) :
Insertion en masse
Pour les insertions à haut débit, utilisezBulkInsertAsync plutôt que SaveChanges. Cette méthode d’extension de DbContext évite complètement le suivi des modifications, la résolution des identités et la gestion des états d’EF Core : elle appelle directement la méthode InsertBinaryAsync du driver, avec un encodage RowBinary et un corps de requête compressé.
Elle convient donc au chargement de grands jeux de données lorsque vous n’avez pas besoin de suivre les entités après leur insertion :
IEnumerable<T> : les entités sont parcourues au fil de l’eau, sans être toutes chargées en mémoire. La valeur renvoyée correspond au nombre de lignes insérées. Après l’insertion, les entités ne sont pas rattachées au DbContext : leur état ne passe donc pas de Added à Unchanged.
Énumérations
Les colonnes ClickHouseEnum8/Enum16 peuvent être associées à des propriétés de type string ou à des types enum C#. Avec les énumérations C#, le fournisseur assure automatiquement la conversion entre l’énumération et sa représentation sous forme de chaîne :
Conversions de types personnalisés
Le systèmeValueConverter d’EF Core permet de convertir vos types personnalisés en types déjà pris en charge par le fournisseur. Celui-ci n’a jamais accès à votre type personnalisé : EF Core effectue la conversion avant de lui transmettre la valeur.
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 indiquer explicitement le type ClickHouse.
À l’aide d’annotations de données (attributs) :
OnModelCreating :
Array(Nullable(Int32)) et LowCardinality(Nullable(String)) sont pris en charge — le fournisseur retire automatiquement les couches Nullable et LowCardinality à chaque niveau d’imbrication.
Colonnes Variant et Dynamic
Les colonnes ClickHouseVariant(T1, T2, ...) et Dynamic sont représentées par le type object en .NET. Comme object est trop générique pour que le type soit déduit automatiquement, vous devez déclarer explicitement le type de stockage à l’aide de .HasColumnType() :
string, ulong, ulong[]).
Colonnes JSON
Le fournisseur prend en charge le type de colonneJson de ClickHouse, avec une correspondance vers System.Text.Json.Nodes.JsonNode (type principal) ou string (au moyen d’un ValueConverter automatique) :
SaveChanges que par BulkInsertAsync :
string avec un type de colonne Json : le fournisseur applique automatiquement un ValueConverter :
- Pas de traduction des chemins JSON — L’expression
entity.Data["name"]dans LINQ n’est pas convertie en syntaxe SQLdata.namede ClickHouse. Appliquez les filtres aux colonnes non JSON et examinez les données JSON en mémoire. - Sémantique des valeurs NULL — Le type JSON de ClickHouse renvoie
{}(un objet vide) pour les valeurs NULL, et non un NULL SQL. - Précision des entiers — Le type JSON de ClickHouse stocke tous les entiers sous forme de
Int64. Lors de la lecture avecJsonNode, utilisezGetValue<long>()plutôt queGetValue<int>().
Moteurs de table
Configurez les moteurs des tables ClickHouse et les clauses propres à chaque moteur au moyen de l’API fluideToTable(name, t => ...). Si aucun moteur n’est configuré, le fournisseur utilise MergeTree par défaut, avec une clause ORDER BY définie à partir de la clé primaire de l’entité.
Clauses du moteur :
WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings. Elles s’ajoutent toutes au constructeur de moteur renvoyé par HasXxxEngine().
Fonctionnalités au niveau des colonnes : HasCodec, HasTtl, HasComment, HasDefault — elles sont toutes prises en compte dans les migrations.
Index de saut de données — avec HasIndex(...).HasSkippingIndexType(...) :
Migrations
Flux de travail standard des migrations avec EF Core :Limites des migrations
Outre ces limites de migration, le fournisseur ne prend pas encore en charge :
UPDATE/DELETE- Transactions :
BeginTransactionest une opération sans effet. Les transactions ACID ne sont pas prises en charge dans ClickHouse. - Traduction des requêtes sur les chemins JSON : l’expression LINQ
entity.Data["key"]n’est pas traduite en syntaxe SQLdata.keyde ClickHouse. Filtrez sur des colonnes non JSON et examinez les données JSON en mémoire.
Limitations
Tuples de 8 éléments ou plus avec un tuple imbriqué en dernière position
En C#, les typesValueTuple de plus de 7 éléments utilisent une imbrication générée 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>>.
Une ambiguïté apparaît lorsque la colonne ClickHouse contient 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 driver ne peut pas distinguer :
- Un tuple plat de 9 éléments (avec une imbrication de TRest générée par le compilateur)
- Un tuple de 8 éléments dont le dernier est un
Tuple(String, String)imbriqué
ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>.
Le driver interprète le 8e argument comme TRest et l’aplatit. Par conséquent, un tuple de 8 éléments dont le dernier est un tuple imbriqué sera sérialisé de manière incorrecte.
Cela concerne aussi bien System.Tuple que ValueTuple, car tous deux utilisent l’imbrication de TRest au-delà de 7 éléments. Les tuples de 7 éléments ou moins, ainsi que ceux dont le dernier élément n’est pas lui-même un tuple, ne sont pas concernés.
Solution de contournement : Placez le tuple interne dans un niveau d’imbrication supplémentaire pour que le driver puisse le distinguer de l’imbrication de TRest :
Colonnes de type AggregateFunction
Les colonnes de typeAggregateFunction(...) ne peuvent pas être interrogées directement ni recevoir de données par insertion directe.
Pour insérer des données :