الانتقال إلى المحتوى الرئيسي
عميل C# الرسمي للاتصال بـ ClickHouse. تتوفّر الشيفرة المصدرية للعميل في مستودع GitHub. طوّره في الأصل Oleg V. Kozlyuk. توفّر المكتبة واجهتَي برمجة تطبيقات رئيسيتين:
  • ClickHouseClient (موصى به): عميل عالي المستوى وآمن للاستخدام من عدة خيوط، ومصمَّم للاستخدام بنمط singleton. يوفّر واجهة برمجة تطبيقات غير متزامنة وبسيطة للاستعلامات وعمليات الإدراج المجمّع. وهو الأنسب لمعظم التطبيقات.
  • ADO.NET (ClickHouseDataSource, ClickHouseConnection, ClickHouseCommand): تجريدات قياسية لقواعد البيانات في .NET. وهي مطلوبة لتكامل ORM ‏(Dapper وLinq2db) وعندما تحتاج إلى التوافق مع ADO.NET. تُعد ClickHouseBulkCopy فئة مساعدة لإدراج البيانات بكفاءة باستخدام اتصال ADO.NET. الفئة ClickHouseBulkCopy مُهمَلة وستُزال في إصدار مستقبلي؛ استخدم ClickHouseClient.InsertBinaryAsync بدلاً منها.
تشترك كلتا الواجهتين في مجمّع اتصالات HTTP الأساسي نفسه، ويمكن استخدامهما معًا داخل التطبيق نفسه.

دليل الترحيل

  1. حدّث ملف .csproj لاستخدام اسم الحزمة الجديد ClickHouse.Driver وأحدث إصدار على NuGet.
  2. حدّث جميع مراجع ClickHouse.Client إلى ClickHouse.Driver في شيفرة مشروعك.

إصدارات ‎.NET‎ المدعومة

يدعم ClickHouse.Driver إصدارات ‎.NET‎ التالية:
  • .NET 6.0
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

التثبيت

ثبّت الحزمة من NuGet:
dotnet add package ClickHouse.Driver
أو باستخدام مدير حزم NuGet:
Install-Package ClickHouse.Driver

البدء السريع

using ClickHouse.Driver;

// Create a client (typically as a singleton)
using var client = new ClickHouseClient("Host=my.clickhouse;Protocol=https;Port=8443;Username=user");

// Execute a query
var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine(version);

التهيئة

هناك طريقتان لتهيئة اتصالك بـ ClickHouse:
  • سلسلة الاتصال: أزواج مفتاح/قيمة مفصولة بفواصل منقوطة، تحدد المضيف وبيانات اعتماد المصادقة وخيارات الاتصال الأخرى.
  • كائن ClickHouseClientSettings: كائن تهيئة مضبوط الأنواع يمكن تحميله من ملفات التهيئة أو تعيينه في الشيفرة.
فيما يلي قائمة كاملة بجميع الإعدادات وقيمها الافتراضية وتأثيراتها.

إعدادات الاتصال

الخاصيةالنوعالافتراضيمفتاح سلسلة الاتصالالوصف
المضيفstring"localhost"Hostاسم المضيف أو عنوان IP لخادم ClickHouse
المنفذushort8123 (HTTP) / 8443 (HTTPS)Portرقم المنفذ؛ تُحدَّد القيمة الافتراضية حسب البروتوكول
اسم المستخدمstring"default"Usernameاسم المستخدم للمصادقة
كلمة المرورstring""Passwordكلمة المرور للمصادقة
قاعدة البياناتstring""Databaseقاعدة البيانات الافتراضية؛ عند تركها فارغة، تُستخدَم القيمة الافتراضية للخادم أو المستخدم
البروتوكولstring"http"Protocolبروتوكول الاتصال: "http" أو "https"
المسارstringnullPathمسار URL في سيناريوهات الوكيل العكسي (مثل /clickhouse)
المهلةTimeSpanدقيقتانTimeoutمهلة العملية (تُخزَّن بالثواني في سلسلة الاتصال)

تنسيق البيانات والتسلسل

الخاصيةالنوعالافتراضيمفتاح سلسلة الاتصالالوصف
UseCompressionbooltrueCompressionتفعيل ضغط gzip لنقل البيانات
UseCustomDecimalsbooltrueUseCustomDecimalsاستخدم ClickHouseDecimal للدقة الاعتباطية؛ وإذا كانت القيمة false، فسيُستخدم decimal في .NET (بحد 128 بت)
ReadStringsAsByteArraysboolfalseReadStringsAsByteArraysاقرأ أعمدة String وFixedString كمصفوفات byte[] بدلًا من string؛ وهذا مفيد للبيانات الثنائية
UseFormDataParametersboolfalseUseFormDataParametersأرسل المعلمات كبيانات نموذج بدلًا من سلسلة استعلام URL
ParameterTypeResolverIParameterTypeResolvernullمُحلِّل مخصص لتعيين نوع المعلمات بأسلوب @؛ راجع تعيين نوع المعلمات المخصص
JsonReadModeJsonReadModeBinaryJsonReadModeكيفية إرجاع بيانات JSON: Binary (يعيد JsonObject) أو String (يعيد سلسلة JSON الخام)
JsonWriteModeJsonWriteModeStringJsonWriteModeكيفية إرسال بيانات JSON: String (يُسلسِل عبر JsonSerializer ويقبل جميع المدخلات) أو Binary (كائنات POCO المسجَّلة فقط مع تلميحات النوع)

إدارة الجلسات

PropertyTypeDefaultConnection String KeyDescription
UseSessionboolfalseUseSessionتمكين الجلسات ذات الحالة؛ يجعل الطلبات تُنفَّذ تسلسليًا
SessionIdstringnullSessionIdمعرّف الجلسة؛ يُنشئ GUID تلقائيًا إذا كانت القيمة null وكان UseSession يساوي true
تؤدي العلامة UseSession إلى الاحتفاظ بجلسة الخادم، مما يتيح استخدام عبارات SET والجداول المؤقتة. ستُعاد تهيئة الجلسات بعد 60 ثانية من عدم النشاط (المهلة الافتراضية). ويمكن تمديد مدة الجلسة عبر تعيين إعدادات الجلسة باستخدام عبارات ClickHouse أو إعدادات الخادم.تتيح الفئة ClickHouseConnection عادةً التشغيل المتوازي (أي يمكن لعدة خيوط تنفيذ الاستعلامات بالتزامن). ومع ذلك، فإن تمكين العلامة UseSession يقيّد ذلك باستعلام نشط واحد فقط لكل اتصال في أي لحظة (وهذا قيد على جهة الخادم).

الأمان

الخاصيةالنوعالافتراضيمفتاح سلسلة الاتصالالوصف
SkipServerCertificateValidationboolfalseتجاوز التحقق من شهادة HTTPS؛ غير مخصص للاستخدام في بيئة الإنتاج

تهيئة عميل HTTP

الخاصيةالنوعالافتراضيمفتاح سلسلة الاتصالالوصف
HttpClientHttpClientnullمثيل HttpClient مخصص ومُهيأ مسبقًا
HttpClientFactoryIHttpClientFactorynullمصنع مخصص لإنشاء مثيلات HttpClient
HttpClientNamestringnullاسم يستخدِمه HttpClientFactory لإنشاء عميل معيّن

التسجيل وتصحيح الأخطاء

PropertyTypeDefaultConnection String KeyDescription
LoggerFactoryILoggerFactorynullمصنع logger المستخدم للتسجيل التشخيصي
EnableDebugModeboolfalseتمكين تتبّع الشبكة في .NET (يتطلب LoggerFactory مع ضبط المستوى على Trace)؛ تأثير ملحوظ على الأداء

الإعدادات المخصصة والأدوار

الخاصيةالنوعالقيمة الافتراضيةمفتاح سلسلة الاتصالالوصف
CustomSettingsIDictionary<string, object>فارغالبادئة set_*إعدادات خادم ClickHouse، راجع الملاحظة أدناه
RolesIReadOnlyList<string>فارغRolesأدوار ClickHouse مفصولة بفواصل (مثل Roles=admin,reader)
عند استخدام سلسلة اتصال لتعيين إعدادات مخصصة، استخدم البادئة set_، مثل “set_max_threads=4”. أما عند استخدام كائن ClickHouseClientSettings، فلا تستخدم البادئة set_.للاطلاع على القائمة الكاملة بالإعدادات المتاحة، راجع هنا.

أمثلة لسلسلة الاتصال

اتصال بسيط

Host=localhost;Port=8123;Username=default;Password=secret;Database=mydb

باستخدام إعدادات ClickHouse مخصّصة

Host=localhost;set_max_threads=4;set_readonly=1;set_max_memory_usage=10000000000

QueryOptions

يتيح لك QueryOptions تجاوز الإعدادات على مستوى العميل لكل استعلام على حدة. جميع الخصائص اختيارية، ولا تستبدل القيم الافتراضية للعميل إلا عند تحديدها.
الخاصيةالنوعالوصف
QueryIdstringمعرّف استعلام مخصّص للتتبّع في system.query_log أو للإلغاء
Databasestringتجاوز قاعدة البيانات الافتراضية لهذا الاستعلام
RolesIReadOnlyList<string>تجاوز أدوار العميل لهذا الاستعلام
CustomSettingsIDictionary<string, object>إعدادات خادم ClickHouse لهذا الاستعلام (مثل max_threads)
CustomHeadersIDictionary<string, string>ترويسات HTTP إضافية لهذا الاستعلام
UseSessionbool?تجاوز سلوك الجلسة لهذا الاستعلام
SessionIdstringمعرّف الجلسة لهذا الاستعلام (يتطلب UseSession = true)
BearerTokenstringتجاوز رمز المصادقة لهذا الاستعلام
ParameterTypeResolverIParameterTypeResolverتجاوز المُحلِّل على مستوى العميل لتعيين نوع المعلَمات بنمط @؛ راجع تعيين نوع المعلَمات المخصّص
MaxExecutionTimeTimeSpan?مهلة الاستعلام من جانب الخادم (تُمرَّر كإعداد max_execution_time)؛ يُلغي الخادم الاستعلام إذا تم تجاوزها
مثال:
var options = new QueryOptions
{
    QueryId = "report-2024-001",
    Database = "analytics",
    CustomSettings = new Dictionary<string, object>
    {
        { "max_threads", 4 },
        { "max_memory_usage", 10_000_000_000 }
    },
    MaxExecutionTime = TimeSpan.FromMinutes(5)
};

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM large_table",
    parameters: null,
    options: options
);

InsertOptions

يوسّع InsertOptionsQueryOptions بإعدادات خاصة بعمليات الإدراج المجمّع عبر InsertBinaryAsync.
الخاصيةالنوعالقيمة الافتراضيةالوصف
BatchSizeint100,000عدد الصفوف في كل دفعة
MaxDegreeOfParallelismint1عدد عمليات رفع الدُفعات المتوازية
FormatRowBinaryFormatRowBinaryالتنسيق الثنائي: RowBinary أو RowBinaryWithDefaults
ColumnTypesIReadOnlyDictionary<string, string>nullاسم العمود ← سلسلة النوع في ClickHouse. يتجاوز استعلام فحص المخطط عند تعيينه.
UseSchemaCacheboolfalseيخزّن مخطط الجدول الكامل لكل زوج من (database, table) طوال مدة حياة العميل.
تتوفّر أيضًا جميع خصائص QueryOptions في InsertOptions. مثال:
var insertOptions = new InsertOptions
{
    BatchSize = 50_000,
    MaxDegreeOfParallelism = 4,
    QueryId = "bulk-import-001"
};

long rowsInserted = await client.InsertBinaryAsync(
    "my_table",
    columns,
    rows,
    insertOptions
);

تخطي استعلام فحص المخطط

بشكل افتراضي، يرسل InsertBinaryAsync استعلام SELECT ... WHERE 1=0 قبل كل عملية إدراج لاكتشاف أنواع الأعمدة. في السيناريوهات ذات معدل النقل المرتفع، يمكنك التخلص من هذا العبء الإضافي باستخدام خيارين: الخيار 1: تحديد أنواع الأعمدة صراحةً عندما تكون على دراية بمخطط الجدول وقت التجميع، مرّره مباشرةً عبر ColumnTypes. عندها لن يُرسل أي استعلام للمخطط على الإطلاق:
var options = new InsertOptions
{
    ColumnTypes = new Dictionary<string, string>
    {
        ["id"] = "UInt64",
        ["name"] = "Nullable(String)",
        ["score"] = "Float32",
    },
};

await client.InsertBinaryAsync("my_table", ["id", "name", "score"], rows, options);
الخيار 2: تخزين المخطط مؤقتًا عند الإدراج في الجدول نفسه بشكل متكرر، اضبط UseSchemaCache = true للاستعلام عن المخطط مرة واحدة ثم إعادة استخدامه في عمليات الإدراج اللاحقة ضمن مثيل ClickHouseClient نفسه:
var options = new InsertOptions { UseSchemaCache = true };

// First call fetches schema from the server
await client.InsertBinaryAsync("my_table", columns, batch1, options);

// Second call reuses cached schema — no extra round-trip
await client.InsertBinaryAsync("my_table", columns, batch2, options);
  • يحظى ColumnTypes بالأولوية على UseSchemaCache. وإذا تم تعيينهما معًا، فستُستخدم الأنواع المحددة صراحةً.
  • لا تكتشف ذاكرة التخزين المؤقت للمخطط تغييرات ALTER TABLE. إذا عدّلت مخطط الجدول، فأنشئ ClickHouseClient جديدًا أو تجنّب استخدام UseSchemaCache لهذا الجدول.
  • يقتصر نطاق ذاكرة التخزين المؤقت على instance الخاصة بـ ClickHouseClient، ويُعرَّف مفتاحها بواسطة (database, table). وتشترك المجموعات الفرعية المختلفة من الأعمدة في الجدول نفسه في مخطط واحد مخزَّن مؤقتًا.

ClickHouseClient

تُعد ClickHouseClient واجهة برمجة التطبيقات الموصى بها للتفاعل مع ClickHouse. وهي آمنة للاستخدام من عدة خيوط، ومصممة للاستخدام كـ singleton، وتدير داخليًا تجمّع اتصالات HTTP.

إنشاء عميل

أنشئ ClickHouseClient باستخدام سلسلة اتصال أو كائن ClickHouseClientSettings. راجع قسم التهيئة للاطّلاع على الخيارات المتاحة. تتوفّر تفاصيل خدمة ClickHouse Cloud الخاصة بك في وحدة تحكم ClickHouse Cloud. حدّد خدمة وانقر على Connect: اختر C#. ستُعرض تفاصيل الاتصال أدناه. إذا كنت تستخدم ClickHouse مُدارًا ذاتيًا، فسيُحدِّد مسؤول ClickHouse لديك تفاصيل الاتصال. باستخدام سلسلة اتصال:
using ClickHouse.Driver;

using var client = new ClickHouseClient("Host=localhost;Username=default;Password=secret");
أو باستخدام ClickHouseClientSettings:
using ClickHouse.Driver;

var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    Username = "default",
    Password = "secret"
};
using var client = new ClickHouseClient(settings);
في حالات استخدام حقن التبعية، استخدم IHttpClientFactory:
// In your DI configuration
services.AddHttpClient("ClickHouse", client =>
{
    client.Timeout = TimeSpan.FromMinutes(5);
}).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});

// Create client with factory
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = new ClickHouseClient("Host=localhost", factory, "ClickHouse");
صُمِّم ClickHouseClient ليكون طويل الأمد ويُستخدم بشكل مشترك عبر تطبيقك. أنشِئه مرة واحدة فقط (عادةً بصفته singleton) وأعِد استخدامه في جميع عمليات قاعدة البيانات. يتولى العميل إدارة تجميع اتصالات HTTP داخليًا.

تنفيذ الاستعلامات

استخدم ExecuteNonQueryAsync مع التعليمات التي لا تُرجع نتائج:
// Create a table
await client.ExecuteNonQueryAsync(
    "CREATE TABLE IF NOT EXISTS default.my_table (id Int64, name String) ENGINE = Memory"
);

// Drop a table
await client.ExecuteNonQueryAsync("DROP TABLE IF EXISTS default.my_table");
استخدم ExecuteScalarAsync لاسترجاع قيمة واحدة:
var count = await client.ExecuteScalarAsync("SELECT count() FROM default.my_table");
Console.WriteLine($"Row count: {count}");

var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine($"Server version: {version}");

إدخال البيانات

عمليات الإدراج باستخدام المعلمات

أدرِج البيانات باستخدام استعلامات ذات معلمات عبر ExecuteNonQueryAsync. يجب تحديد أنواع المعلمات في SQL باستخدام الصياغة {name:Type}:
using ClickHouse.Driver;
using ClickHouse.Driver.ADO.Parameters;

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("id", 1L);
parameters.AddParameter("name", "Alice");

await client.ExecuteNonQueryAsync(
    "INSERT INTO default.my_table (id, name) VALUES ({id:Int64}, {name:String})",
    parameters
);

عمليات الإدراج المجمّعة

استخدم InsertBinaryAsync لإدراج أعداد كبيرة من الصفوف بكفاءة. فهو يمرّر البيانات بتنسيق الصفوف الثنائي الأصلي في ClickHouse، ويدعم تحميل الدُفعات بالتوازي، ويتجنب أخطاء “URL طويل جدًا” التي قد تحدث مع الاستعلامات ذات المعلمات.
// Prepare data as IEnumerable<object[]>
var rows = Enumerable.Range(0, 1_000_000)
    .Select(i => new object[] { (long)i, $"value{i}" });

var columns = new[] { "id", "name" };

// Basic insert
long rowsInserted = await client.InsertBinaryAsync("default.my_table", columns, rows);
Console.WriteLine($"Rows inserted: {rowsInserted}");
لمجموعات البيانات الكبيرة، اضبط التجميع على دفعات ومستوى التوازي باستخدام InsertOptions:
var options = new InsertOptions
{
    BatchSize = 100_000,           // Rows per batch (default: 100,000)
    MaxDegreeOfParallelism = 4     // Parallel batch uploads (default: 1)
};
  • يجلب العميل تلقائيًا بنية الجدول عبر SELECT * FROM <table> WHERE 1=0 قبل الإدراج. يجب أن تتوافق القيم المُمرَّرة مع أنواع الأعمدة المستهدفة. لتجاوز هذا الاستعلام، استخدم InsertOptions.ColumnTypes أو InsertOptions.UseSchemaCache.
  • عندما تكون قيمة MaxDegreeOfParallelism > 1، تُحمَّل الدفعات بالتوازي. لا تتوافق الجلسات مع الإدراج المتوازي؛ لذا عطّل الجلسات أو عيّن MaxDegreeOfParallelism = 1.
  • استخدم RowBinaryFormat.RowBinaryWithDefaults في InsertOptions.Format إذا كنت تريد أن يطبّق الخادم قيم DEFAULT على الأعمدة غير المُمرَّرة.

عمليات إدراج POCO

بدلاً من إنشاء مصفوفات object[]، يمكنك إدراج كائنات POCO محددة النوع مباشرةً. سجّل النوع مرة واحدة، ثم مرّر IEnumerable<T>:
// Define a POCO matching your table columns
public class SensorReading
{
    public ulong Id { get; set; }
    public string SensorName { get; set; }
    public double Value { get; set; }
    public DateTime Timestamp { get; set; }
}

// Register the type (once per client lifetime)
client.RegisterBinaryInsertType<SensorReading>();

// Insert directly — column names are derived from property names
var readings = Enumerable.Range(0, 100_000)
    .Select(i => new SensorReading
    {
        Id = (ulong)i,
        SensorName = $"sensor_{i % 10}",
        Value = Random.Shared.NextDouble() * 100,
        Timestamp = DateTime.UtcNow,
    });

long rowsInserted = await client.InsertBinaryAsync("sensors", readings);
افتراضيًا، تُعيَّن جميع الخصائص العامة القابلة للقراءة إلى أعمدة باستخدام مطابقة صارمة للأسماء تراعي حالة الأحرف. يمكنك تخصيص هذا التعيين باستخدام السمات:
public class Event
{
    [ClickHouseColumn(Name = "event_id")]     // Map to a differently-named column
    public ulong Id { get; set; }

    [ClickHouseColumn(Type = "LowCardinality(String)")]  // Explicit ClickHouse type
    public string Category { get; set; }

    public string Payload { get; set; }

    [ClickHouseNotMapped]                     // Exclude from insert
    public string InternalTag { get; set; }
}
السمةالغرض
[ClickHouseColumn(Name = "...")]تجاوز اسم العمود الهدف
[ClickHouseColumn(Type = "...")]التصريح بنوع ClickHouse صراحةً
[ClickHouseNotMapped]استبعاد الخاصية من عملية الإدراج
عندما تحدد جميع الخصائص المعينة Type صريحًا، يُتخطّى استعلام فحص المخطط بالكامل. وعندما تكون بعض الخصائص فقط ذات أنواع صريحة، يعود برنامج التشغيل إلى فحص المخطط لمجموعة الأعمدة الكاملة. يدعم InsertBinaryAsync<T> خيارات InsertOptions نفسها (التجميع على دفعات، التوازي، التخزين المؤقت للمخطط) مثل التحميل الزائد object[].
بخلاف التحميل الزائد object[]، لا يقبل InsertBinaryAsync<T> قائمة أعمدة صريحة. تُحدَّد الأعمدة بواسطة الخصائص المعينة للنوع المسجل. للتحكم في الأعمدة التي تُدرج، استخدم [ClickHouseNotMapped] لاستبعاد الخصائص أو [ClickHouseColumn(Name = "...")] لإعادة تسميتها.إذا تم تعيين ColumnTypes في InsertOptions، فستتجاوز سمات POCO.

تطور المخطط

تعمل عمليات الإدراج الخاصة بـ POCO بسلاسة عند إضافة أعمدة إلى الجدول المستهدف بعد تسجيل النوع. ونظرًا إلى أن برنامج التشغيل لا يُدرج سوى الأعمدة المرتبطة بـ POCO، فإن أي أعمدة جديدة تحتوي على DEFAULT (أو تعبيرات افتراضية أخرى) يملؤها الخادم تلقائيًا. ولا حاجة إلى أي تغييرات في الشيفرة أو إلى إعادة التسجيل.

قراءة البيانات

استخدم ExecuteReaderAsync لتنفيذ استعلامات SELECT. يوفّر ClickHouseDataReader المُعاد وصولًا مُحدَّد النوع إلى أعمدة النتائج عبر طرق مثل GetInt64() وGetString() وGetFieldValue<T>(). استدعِ Read() للانتقال إلى الصف التالي. وتُرجع false عند عدم وجود المزيد من الصفوف. ويمكنك الوصول إلى الأعمدة حسب الفهرس (ابتداءً من 0) أو حسب اسم العمود.
using ClickHouse.Driver.ADO.Parameters;

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("max_id", 100L);

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM default.my_table WHERE id < {max_id:Int64}",
    parameters
);

while (reader.Read())
{
    Console.WriteLine($"Id: {reader.GetInt64(0)}, Name: {reader.GetString(1)}");
}

معلمات SQL

في ClickHouse، الصيغة القياسية لمعلمات الاستعلام في استعلامات SQL هي {parameter_name:DataType}. أمثلة:
SELECT {value:Array(UInt16)} as a
SELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}
INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})
تُمرَّر معلمات SQL ‏‘bind’ كمعلمات استعلام في HTTP URI، لذا فإن استخدام عدد كبير جدًا منها قد يؤدي إلى ظهور استثناء “URL طويل جدًا”. استخدم InsertBinaryAsync لإدراج كميات كبيرة من البيانات لتجنّب هذا القيد.

معرّف الاستعلام

يُخصَّص لكل استعلام معرّف فريد query_id يمكن استخدامه لجلب البيانات من جدول system.query_log أو لإلغاء الاستعلامات طويلة التشغيل. يمكنك تحديد معرّف استعلام مخصّص عبر QueryOptions:
var options = new QueryOptions
{
    QueryId = $"report-{Guid.NewGuid()}"
};

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM large_table",
    parameters: null,
    options: options
);
إذا كنت تحدد QueryId مخصصًا، فتأكد من أنه فريد في كل استدعاء. ويُعد GUID عشوائيًا خيارًا جيدًا.

تعيين نوع المعلمة المخصّص

عند استخدام المعلمات بأسلوب @ (مثل WHERE id = @id)، يستنتج برنامج التشغيل تلقائيًا نوع ClickHouse من نوع القيمة في ‎.NET. على سبيل المثال، يُعيَّن int إلى Int32، ويُعيَّن DateTime إلى DateTime. لتجاوز هذه الإعدادات الافتراضية، عيّن ParameterTypeResolver في ClickHouseClientSettings. ويكون ذلك مفيدًا عندما تريد استخدام DateTime64(3) لجميع معلمات DateTime بدقة الملّي ثانية، أو استخدام قيمة scale محددة لجميع قيم Decimal، من دون تعيين ClickHouseType لكل معلمة على حدة. استخدام DictionaryParameterTypeResolver لتعيينات الأنواع البسيطة:
using ClickHouse.Driver.ADO.Parameters;

var settings = new ClickHouseClientSettings("Host=localhost")
{
    ParameterTypeResolver = new DictionaryParameterTypeResolver(new Dictionary<Type, string>
    {
        [typeof(DateTime)] = "DateTime64(3)",
        [typeof(decimal)] = "Decimal64(4)",
    }),
};
using var client = new ClickHouseClient(settings);

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("dt", DateTime.UtcNow);     // Mapped to DateTime64(3)
parameters.AddParameter("amount", 99.1234m);         // Mapped to Decimal64(4)

await client.ExecuteReaderAsync("SELECT @dt, @amount", parameters);
IParameterTypeResolver مخصّص للحالات المتقدمة: للاستدلال المعتمد على القيمة أو المستند إلى الاسم، نفِّذ الواجهة IParameterTypeResolver مباشرةً. أرجِع null للانتقال إلى الاستدلال الافتراضي:
public class SmartDecimalResolver : IParameterTypeResolver
{
    public string ResolveType(Type clrType, object value, string parameterName)
    {
        if (clrType != typeof(decimal))
            return null; // Fall through to default

        var scale = (decimal.GetBits((decimal)value)[3] >> 16) & 0x7F;
        return scale <= 4 ? $"Decimal64({scale})" : $"Decimal128({scale})";
    }
}
يمكنك أيضًا تعيين محلِّل لاستعلام واحد عبر QueryOptions.ParameterTypeResolver. وعند تعيينه، تكون له أولوية أعلى من المحلِّل على مستوى العميل. ترتيب أولوية تحديد النوع: المحلِّل ليس سوى خطوة ضمن سلسلة ترتيب الأولوية. من الأعلى إلى الأدنى أولوية:
  1. تعيين ClickHouseType صراحةً على المعلَمة
  2. تلميح نوع SQL من الصيغة {name:Type} في الاستعلام
  3. IParameterTypeResolver (من QueryOptions.ParameterTypeResolver، مع الرجوع إلى ClickHouseClientSettings.ParameterTypeResolver)
  4. استنتاج النوع المضمَّن (TypeConverter.ToClickHouseType)
يعمل المحلِّل أيضًا مع مسار ADO.NET ClickHouseConnection — إذ ترث الاتصالات المُنشأة من العميل هذه الإعدادات.

البث الخام

استخدم ExecuteRawResultAsync لبث نتائج الاستعلام مباشرةً بتنسيق محدد، متجاوزًا قارئ البيانات. يفيد ذلك عند تصدير البيانات إلى ملفات أو تمريرها إلى أنظمة أخرى:
using var result = await client.ExecuteRawResultAsync(
    "SELECT * FROM default.my_table LIMIT 100 FORMAT JSONEachRow"
);

await using var stream = await result.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
var json = await reader.ReadToEndAsync();
التنسيقات الشائعة: JSONEachRow، CSV، TSV، Parquet، Native. راجع توثيق التنسيقات للتعرّف على جميع الخيارات.

إدراج تدفق خام

استخدم InsertRawStreamAsync لإدراج البيانات مباشرةً من تدفقات الملفات أو الذاكرة بتنسيقات مثل CSV وJSON وParquet، أو بأي تنسيق ClickHouse مدعوم. إدراج من ملف CSV:
await using var fileStream = File.OpenRead("data.csv");

using var response = await client.InsertRawStreamAsync(
    table: "my_table",
    stream: fileStream,
    format: "CSV",
    columns: ["id", "product", "price"] // Optional: specify columns
);
راجع وثائق إعدادات الصيغ للاطلاع على خيارات التحكّم في سلوك إدخال البيانات.

المزيد من الأمثلة

للاطلاع على المزيد من أمثلة الاستخدام العملية، راجع دليل الأمثلة في مستودع GitHub.

ADO.NET

توفّر المكتبة دعمًا كاملًا لـ ADO.NET من خلال ClickHouseConnection وClickHouseCommand وClickHouseDataReader. وتُعد واجهة برمجة التطبيقات هذه ضرورية للتكامل مع ORM ‏(Dapper وLinq2db)، وكذلك عند الحاجة إلى طبقات تجريد قواعد البيانات القياسية في .NET.

إدارة دورة الحياة باستخدام ClickHouseDataSource

أنشئ الاتصالات دائمًا عبر ClickHouseDataSource لضمان الإدارة السليمة لدورة الحياة وتجميع الاتصالات. يدير DataSource مثيل ClickHouseClient واحدًا داخليًا، وتشترك جميع الاتصالات في تجمّع الاتصالات HTTP الخاص به.
using ClickHouse.Driver.ADO;

// Create DataSource once (register as singleton in DI)
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default;Password=secret");

// Create lightweight connections as needed
await using var connection = await dataSource.OpenConnectionAsync();

// Use the connection
await using var command = connection.CreateCommand("SELECT version()");
var version = await command.ExecuteScalarAsync();
في حال استخدام حقن التبعيات:
// In Startup.cs or Program.cs
services.AddSingleton(sp =>
{
    var factory = sp.GetRequiredService<IHttpClientFactory>();
    return new ClickHouseDataSource("Host=localhost", factory, "ClickHouse");
});

// In your service
public class MyService
{
    private readonly ClickHouseDataSource _dataSource;

    public MyService(ClickHouseDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task DoWorkAsync()
    {
        await using var connection = await _dataSource.OpenConnectionAsync();
        // Use connection...
    }
}
لا تُنشئ ClickHouseConnection مباشرةً في شيفرة الإنتاج. فكل إنشاء مباشر له ينشئ عميل HTTP جديدًا وتجمّع اتصالات جديدًا، ما قد يؤدي إلى استنفاد المقابس عند ارتفاع الحمل:
// لا تفعل هذا — ينشئ مجمّع اتصالات جديدًا في كل مرة
using var conn = new ClickHouseConnection("Host=localhost");
await conn.OpenAsync();
بدلًا من ذلك، استخدم دائمًا ClickHouseDataSource أو شارِك مثيلًا واحدًا من ClickHouseClient.

استخدام ClickHouseCommand

أنشئ أوامر باستخدام اتصال لتنفيذ SQL:
await using var connection = await dataSource.OpenConnectionAsync();

// Create command with SQL
await using var command = connection.CreateCommand("SELECT * FROM my_table WHERE id = {id:Int64}");
command.AddParameter("id", 42L);

// Execute and read results
await using var reader = await command.ExecuteReaderAsync();
while (reader.Read())
{
    Console.WriteLine($"Name: {reader.GetString("name")}");
}
طرق الأوامر:
  • ExecuteNonQueryAsync() - لعبارات INSERT وUPDATE وDELETE وDDL
  • ExecuteScalarAsync() - يعيد أول عمود من أول صف
  • ExecuteReaderAsync() - يعيد ClickHouseDataReader للتنقّل بين النتائج

استخدام ClickHouseDataReader

يتيح ClickHouseDataReader الوصول إلى نتائج الاستعلام مع الحفاظ على أنواع البيانات:
await using var reader = await command.ExecuteReaderAsync();

while (reader.Read())
{
    // Access by column index
    var id = reader.GetInt64(0);
    var name = reader.GetString(1);

    // Access by column name
    var email = reader.GetString("email");

    // Generic access
    var timestamp = reader.GetFieldValue<DateTime>("created_at");

    // Check for null
    if (!reader.IsDBNull("optional_field"))
    {
        var value = reader.GetString("optional_field");
    }
}

أفضل الممارسات

مدة الاتصال وتجميع الاتصالات

يستخدم ClickHouse.Driver المكوّن System.Net.Http.HttpClient في الخلفية. ويحتوي HttpClient على تجمّع اتصالات لكل endpoint. ونتيجة لذلك:
  • تُمرَّر جلسات قاعدة البيانات عبر اتصالات HTTP التي يديرها تجمّع الاتصالات.
  • يُعيد التجمّع استخدام اتصالات HTTP تلقائيًا.
  • قد تظل الاتصالات مفتوحة حتى بعد التخلّص من الكائنات ClickHouseClient أو ClickHouseConnection.
الأنماط الموصى بها:
السيناريوالنهج الموصى به
الاستخدام العاماستخدم ClickHouseClient بنمط singleton
ADO.NET / ORMsاستخدم ClickHouseDataSource (ينشئ اتصالات تشترك في تجمّع الاتصالات نفسه)
بيئات DIسجّل ClickHouseClient أو ClickHouseDataSource بنمط singleton مع IHttpClientFactory
عند استخدام HttpClient أو HttpClientFactory مخصّص، تأكد من ضبط PooledConnectionIdleTimeout على قيمة أقل من keep_alive_timeout الخاص بالخادم، لتجنّب الأخطاء الناتجة عن الاتصالات نصف المغلقة. القيمة الافتراضية لـ keep_alive_timeout في Cloud deployments هي 10 ثوانٍ.
تجنّب إنشاء عدة مثيلات من ClickHouseClient أو مثيلات ClickHouseConnection مستقلة من دون HttpClient مشترك. فكل مثيل ينشئ تجمّع الاتصالات الخاص به.

التعامل مع DateTime

  1. استخدم UTC كلما أمكن. خزّن الطوابع الزمنية في أعمدة DateTime('UTC') واستخدم DateTimeKind.Utc في الشيفرة الخاصة بك. هذا يزيل أي التباس متعلق بالمنطقة الزمنية.
  2. استخدم DateTimeOffset للتعامل الصريح مع المنطقة الزمنية. فهو يمثّل دائمًا لحظة زمنية محددة ويتضمن معلومات الإزاحة.
  3. حدّد المنطقة الزمنية في تلميحات النوع في SQL. عند استخدام المعلمات مع قيم DateTime من النوع Unspecified والموجّهة إلى أعمدة غير UTC، ضمّن المنطقة الزمنية في SQL:
    var parameters = new ClickHouseParameterCollection();
    parameters.AddParameter("dt", myDateTime);
    
    await client.ExecuteNonQueryAsync(
        "INSERT INTO table (dt) VALUES ({dt:DateTime('Europe/Amsterdam')})",
        parameters
    );
    

عمليات الإدراج غير المتزامنة

تنقل عمليات الإدراج غير المتزامنة مسؤولية التجميع من العميل إلى الخادم. فبدلًا من اشتراط التجميع من جهة العميل، يخزّن الخادم البيانات الواردة مؤقتًا ثم يفرّغها إلى التخزين وفقًا لعتبات قابلة للتهيئة. ويكون هذا مفيدًا في السيناريوهات عالية التزامن، مثل أحمال عمل observability، حيث يرسل العديد من الوكلاء حمولات صغيرة. فعِّل عمليات الإدراج غير المتزامنة عبر CustomSettings أو سلسلة الاتصال:
// Using CustomSettings
var settings = new ClickHouseClientSettings("Host=localhost");
settings.CustomSettings["async_insert"] = 1;
settings.CustomSettings["wait_for_async_insert"] = 1; // Recommended: wait for flush acknowledgment

// Or via connection string
// "Host=localhost;set_async_insert=1;set_wait_for_async_insert=1"
وضعان (يحددهما wait_for_async_insert):
الوضعالسلوكحالة الاستخدام
wait_for_async_insert=1تعود عملية الإدراج بعد كتابة البيانات إلى القرص. وتُعاد الأخطاء إلى العميل.موصى به لمعظم أحمال العمل
wait_for_async_insert=0تعود عملية الإدراج فورًا عند تخزين البيانات مؤقتًا. لا يوجد ما يضمن حفظ البيانات.فقط عندما يكون فقدان البيانات مقبولًا
مع wait_for_async_insert=0، لا تظهر الأخطاء إلا أثناء التفريغ ولا يمكن ربطها بعملية الإدراج الأصلية. كما أن العميل لا يوفّر ضغطًا عكسيًا، مما يعرّض الخادم لخطر زيادة الحمل.
الإعدادات الأساسية:
الإعدادالوصف
async_insert_max_data_sizeفرّغ عند وصول المخزن المؤقت إلى هذا الحجم (بايت)
async_insert_busy_timeout_msفرّغ بعد انقضاء هذه المهلة (مللي ثانية)
async_insert_max_query_numberفرّغ بعد تراكم هذا العدد من الاستعلامات

الجلسات

لا تُفعِّل الجلسات إلا عند الحاجة إلى ميزات من جهة الخادم تحتفظ بالحالة، مثل:
  • الجداول المؤقتة (CREATE TEMPORARY TABLE)
  • الحفاظ على سياق الاستعلام عبر عدة تعليمات
  • إعدادات على مستوى الجلسة (SET max_threads = 4)
عند تفعيل الجلسات، تُعالَج الطلبات تسلسليًا لمنع الاستخدام المتزامن للجلسة نفسها. ويضيف ذلك حملًا إضافيًا إلى أحمال العمل التي لا تتطلب حالة الجلسة.
var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    UseSession = true,
    SessionId = "my-session", // Optional -- will be auto-generated if not provided
};

using var client = new ClickHouseClient(settings);

await client.ExecuteNonQueryAsync("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await client.ExecuteNonQueryAsync("INSERT INTO temp_ids VALUES (1), (2), (3)");

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)"
);
استخدام ADO.NET (للتوافق مع أطر ORM):
var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    UseSession = true,
    SessionId = "my-session",
};

var dataSource = new ClickHouseDataSource(settings);
await using var connection = await dataSource.OpenConnectionAsync();

await using var cmd1 = connection.CreateCommand("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await cmd1.ExecuteNonQueryAsync();

await using var cmd2 = connection.CreateCommand("INSERT INTO temp_ids VALUES (1), (2), (3)");
await cmd2.ExecuteNonQueryAsync();

await using var cmd3 = connection.CreateCommand("SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)");
await using var reader = await cmd3.ExecuteReaderAsync();

أنواع البيانات المدعومة

يدعم ClickHouse.Driver جميع أنواع بيانات ClickHouse. تُبيّن الجداول أدناه أوجه التوافق بين أنواع ClickHouse وأنواع ‎.NET الأصلية عند قراءة البيانات من قاعدة البيانات.

تعيين الأنواع: عند القراءة من ClickHouse

أنواع الأعداد الصحيحة

نوع ClickHouseنوع .NET
Int8sbyte
UInt8byte
Int16short
UInt16ushort
Int32int
UInt32uint
Int64long
UInt64ulong
Int128BigInteger
UInt128BigInteger
Int256BigInteger
UInt256BigInteger

أنواع الأعداد ذات الفاصلة العائمة

نوع ClickHouseنوع .NET
Float32float
Float64double
BFloat16float

الأنواع العشرية

نوع ClickHouseنوع .NET
Decimal(P, S)decimal / ClickHouseDecimal
Decimal32(S)decimal / ClickHouseDecimal
Decimal64(S)decimal / ClickHouseDecimal
Decimal128(S)decimal / ClickHouseDecimal
Decimal256(S)decimal / ClickHouseDecimal
يُتحكَّم في تحويل النوع Decimal من خلال الإعداد UseCustomDecimals.

نوع Boolean

نوع ClickHouseنوع .NET
Boolbool

أنواع السلاسل النصية

نوع ClickHouseنوع .NET
Stringstring
FixedString(N)string
افتراضيًا، يُرجَع كلٌّ من العمودين String وFixedString(N) على هيئة string. عيّن ReadStringsAsByteArrays=true في سلسلة الاتصال لقراءتهما على هيئة byte[] بدلًا من ذلك. يفيد هذا عند تخزين بيانات ثنائية قد لا تكون بتنسيق UTF-8 صالح.

أنواع التاريخ والوقت

نوع ClickHouseنوع .NET
DateDateTime
Date32DateTime
DateTimeDateTime
DateTime32DateTime
DateTime64DateTime
TimeTimeSpan
Time64TimeSpan
يخزّن ClickHouse قيم DateTime وDateTime64 داخليًا على هيئة طوابع زمنية Unix (بالثواني أو بوحدات أصغر من الثانية منذ حقبة Unix). وعلى الرغم من أن التخزين يكون دائمًا بتوقيت UTC، فقد تكون للأعمدة منطقة زمنية مرتبطة بها تؤثر في كيفية عرض القيم وتفسيرها. عند قراءة قيم DateTime، تُضبط الخاصية DateTime.Kind بناءً على المنطقة الزمنية للعمود:
تعريف العمودقيمة DateTime.Kind المُعادةملاحظات
DateTime('UTC')Utcمنطقة UTC زمنية محددة صراحةً
DateTime('Europe/Amsterdam')Unspecifiedتُطبَّق الإزاحة
DateTimeUnspecifiedيُحفَظ الوقت المحلي كما هو
بالنسبة إلى الأعمدة التي لا تستخدم UTC، تمثل قيمة DateTime المُعادة الوقت المحلي في تلك المنطقة الزمنية. استخدم ClickHouseDataReader.GetDateTimeOffset() للحصول على DateTimeOffset مع الإزاحة الصحيحة لتلك المنطقة الزمنية:
var reader = (ClickHouseDataReader)await connection.ExecuteReaderAsync(
    "SELECT toDateTime('2024-06-15 14:30:00', 'Europe/Amsterdam')");
reader.Read();

var dt = reader.GetDateTime(0);    // 2024-06-15 14:30:00, Kind=Unspecified
var dto = reader.GetDateTimeOffset(0); // 2024-06-15 14:30:00 +02:00 (CEST)
بالنسبة إلى الأعمدة التي لا تتضمن منطقة زمنية صريحة (أي DateTime بدلًا من DateTime('Europe/Amsterdam'))، يعيد برنامج التشغيل قيمة DateTime مع Kind=Unspecified. وهذا يحافظ على الوقت المحلي كما تظهره الساعة تمامًا كما هو مخزّن، من دون افتراض أي منطقة زمنية. إذا كنت بحاجة إلى سلوكٍ مدركٍ للمنطقة الزمنية للأعمدة التي لا تتضمن مناطق زمنية صريحة، فإما أن:
  1. تستخدم مناطق زمنية صريحة في تعريفات الأعمدة: DateTime('UTC') أو DateTime('Europe/Amsterdam')
  2. تطبّق المنطقة الزمنية بنفسك بعد القراءة.

نوع JSON

نوع ClickHouseنوع .NETملاحظات
JsonJsonObjectالافتراضي (JsonReadMode=Binary)
Jsonstringعند استخدام JsonReadMode=String
يُحدَّد نوع الإرجاع لأعمدة JSON من خلال الإعداد JsonReadMode:
  • Binary (الافتراضي): يعيد System.Text.Json.Nodes.JsonObject. يوفّر وصولًا منظّمًا إلى بيانات JSON، لكن أنواع ClickHouse المتخصصة (مثل عناوين IP وUUIDs والقيم العشرية الكبيرة) تُحوَّل إلى تمثيلاتها النصية داخل بنية JSON.
  • String: يعيد JSON الخام كسلسلة string. ويحافظ على تمثيل JSON كما هو تمامًا من ClickHouse، وهو ما يكون مفيدًا عندما تحتاج إلى تمرير JSON كما هو دون تحليله، أو عندما تريد التعامل مع فك التسلسل بنفسك.
// Configure string mode via settings
var settings = new ClickHouseClientSettings("Host=localhost")
{
    JsonReadMode = JsonReadMode.String
};

// Or via connection string
// "Host=localhost;JsonReadMode=String"

أنواع أخرى

نوع ClickHouseنوع .NET
UUIDGuid
IPv4IPAddress
IPv6IPAddress
NothingDBNull
Dynamicانظر الملاحظة
Array(T)T[]
Tuple(T1, T2, …)Tuple<T1, T2, ...> / LargeTuple
Map(K, V)Dictionary<K, V>
Nullable(T)T?
Enum8string
Enum16string
LowCardinality(T)نفس T
SimpleAggregateFunctionنفس النوع الأساسي
Nested(…)Tuple[]
Variant(T1, T2, …)انظر الملاحظة
QBit(T, dimension)T[]
سيُحوَّل النوعان Dynamic وVariant إلى النوع المقابل للنوع الأساسي الفعلي في كل صف.

أنواع Geometry

نوع ClickHouseنوع .NET
PointTuple<double, double>
RingTuple<double, double>[]
LineStringTuple<double, double>[]
PolygonRing[]
MultiLineStringLineString[]
MultiPolygonPolygon[]
Geometryراجع الملاحظة
نوع Geometry هو نوع Variant يمكن أن يحتوي على أيٍّ من أنواع Geometry. وسيُحوَّل إلى النوع المقابل.

تعيين الأنواع: الكتابة إلى ClickHouse

عند إدراج البيانات، يحوّل برنامج التشغيل أنواع .NET إلى أنواع ClickHouse المقابلة لها. وتبيّن الجداول أدناه أنواع .NET المقبولة لكل نوع عمود في ClickHouse.

أنواع الأعداد الصحيحة

نوع ClickHouseأنواع .NET المقبولةملاحظات
Int8sbyte، وأي نوع متوافق مع Convert.ToSByte()
UInt8byte، وأي نوع متوافق مع Convert.ToByte()
Int16short، وأي نوع متوافق مع Convert.ToInt16()
UInt16ushort، وأي نوع متوافق مع Convert.ToUInt16()
Int32int، وأي نوع متوافق مع Convert.ToInt32()
UInt32uint، وأي نوع متوافق مع Convert.ToUInt32()
Int64long، وأي نوع متوافق مع Convert.ToInt64()
UInt64ulong، وأي نوع متوافق مع Convert.ToUInt64()
Int128BigInteger، decimal، double، float، int، uint، long، ulong، وأي نوع متوافق مع Convert.ToInt64()
UInt128BigInteger، decimal، double، float، int، uint، long، ulong، وأي نوع متوافق مع Convert.ToInt64()
Int256BigInteger، decimal، double، float، int، uint، long، ulong، وأي نوع متوافق مع Convert.ToInt64()
UInt256BigInteger، decimal، double، float، int، uint، long، ulong، وأي نوع متوافق مع Convert.ToInt64()

أنواع الأعداد ذات الفاصلة العائمة

نوع ClickHouseأنواع .NET المقبولةملاحظات
Float32float، وأي نوع متوافق مع Convert.ToSingle()
Float64double، وأي نوع متوافق مع Convert.ToDouble()
BFloat16float، وأي نوع متوافق مع Convert.ToSingle()يُقتطع إلى تنسيق bfloat16 ‏(brain float)‏ ذي 16 بت

النوع المنطقي

نوع ClickHouseأنواع .NET المقبولةملاحظات
Boolbool

أنواع String

نوع ClickHouseأنواع .NET المقبولةملاحظات
Stringstring, byte[], ReadOnlyMemory<byte>, Streamتُكتب الأنواع الثنائية مباشرةً؛ ويمكن أن تكون التدفقات قابلةً لـ seek أو غير قابلة له
FixedString(N)string, byte[], ReadOnlyMemory<byte>, Streamيُرمَّز String بترميز UTF-8 ويُحشّى؛ ويجب أن تكون الأنواع الثنائية بطول N بايتات بالضبط

أنواع التاريخ والوقت

نوع ClickHouseأنواع .NET المقبولةملاحظات
DateDateTime, DateTimeOffset, DateOnly, أنواع NodaTimeتُحوَّل إلى أيام Unix بصيغة UInt16
Date32DateTime, DateTimeOffset, DateOnly, أنواع NodaTimeتُحوَّل إلى أيام Unix بصيغة Int32
DateTimeDateTime, DateTimeOffset, DateOnly, أنواع NodaTimeانظر أدناه للاطّلاع على التفاصيل
DateTime32DateTime, DateTimeOffset, DateOnly, أنواع NodaTimeمثل DateTime
DateTime64DateTime, DateTimeOffset, DateOnly, أنواع NodaTimeتعتمد الدقة على معلمة Scale
TimeTimeSpan, intتُقيَّد إلى ±999:59:59؛ ويُتعامل مع int على أنه ثوانٍ
Time64TimeSpan, decimal, double, float, int, long, stringتُحلَّل السلسلة النصية بالصيغة [-]HHH:MM:SS[.fraction]؛ وتُقيَّد إلى ±999:59:59.999999999
يراعي برنامج التشغيل DateTime.Kind عند كتابة القيم:
DateTime.Kindمعلمات HTTPBulk
Utcتُحفَظ اللحظة الزمنية كما هيتُحفَظ اللحظة الزمنية كما هي
Localتُحفَظ اللحظة الزمنية كما هيتُحفَظ اللحظة الزمنية كما هي
Unspecifiedيُتعامل معه كوقت محلي في المنطقة الزمنية لنوع المعلمة (UTC افتراضيًا)يُتعامل معه كوقت محلي في المنطقة الزمنية للعمود
تحافظ قيم DateTimeOffset دائمًا على اللحظة الزمنية الدقيقة. مثال: DateTime بتوقيت UTC (تُحفَظ اللحظة الزمنية كما هي)
var utcTime = new DateTime(2024, 1, 15, 12, 0, 0, DateTimeKind.Utc);
// Stored as 12:00 UTC
// Read from DateTime('Europe/Amsterdam') column: 13:00 (UTC+1)
// Read from DateTime('UTC') column: 12:00 UTC
مثال: DateTime غير محدد (التوقيت المحلي)
var wallClock = new DateTime(2024, 1, 15, 14, 30, 0, DateTimeKind.Unspecified);
// Written to DateTime('Europe/Amsterdam') column: stored as 14:30 Amsterdam time
// Read back from DateTime('Europe/Amsterdam') column: 14:30
التوصية: للحصول على أبسط سلوك وأكثره قابلية للتنبؤ، استخدم DateTimeKind.Utc أو DateTimeOffset في جميع عمليات DateTime. يضمن ذلك أن تعمل شيفرتك بشكل متسق بغض النظر عن المنطقة الزمنية للخادم أو العميل أو العمود.

معلمات HTTP مقابل Bulk Copy

يوجد فرق مهم بين ربط معلمات HTTP وBulk Copy عند كتابة قيم DateTime من النوع Unspecified: Bulk Copy يعرف المنطقة الزمنية للعمود المستهدف ويفسّر قيم Unspecified بشكل صحيح وفقًا لتلك المنطقة الزمنية. HTTP Parameters لا تعرف تلقائيًا المنطقة الزمنية للعمود. يجب تحديدها في تلميح نوع SQL:
// CORRECT: Timezone in SQL type hint - type is extracted automatically
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime('Europe/Amsterdam')})";
command.AddParameter("dt", myDateTime);

// INCORRECT: Without timezone hint, interpreted as UTC
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime})";
command.AddParameter("dt", myDateTime);
// String value "2024-01-15 14:30:00" interpreted as UTC, not Amsterdam time!
DateTime.Kindالعمود المستهدفمعلمة HTTP (مع تلميح المنطقة الزمنية)معلمة HTTP (من دون تلميح المنطقة الزمنية)النسخ المجمّع
UtcUTCتبقى اللحظة الزمنية كما هيتبقى اللحظة الزمنية كما هيتبقى اللحظة الزمنية كما هي
UtcEurope/Amsterdamتبقى اللحظة الزمنية كما هيتبقى اللحظة الزمنية كما هيتبقى اللحظة الزمنية كما هي
Localأيّ قيمةتبقى اللحظة الزمنية كما هيتبقى اللحظة الزمنية كما هيتبقى اللحظة الزمنية كما هي
UnspecifiedUTCيُعامَل على أنه UTCيُعامَل على أنه UTCيُعامَل على أنه UTC
UnspecifiedEurope/Amsterdamيُعامَل على أنه بتوقيت Amsterdamيُعامَل على أنه UTCيُعامَل على أنه بتوقيت Amsterdam

أنواع Decimal

نوع ClickHouseأنواع .NET المقبولةملاحظات
Decimal(P,S)decimal، ClickHouseDecimal، وأي نوع متوافق مع Convert.ToDecimal()يتم طرح OverflowException إذا تجاوزت القيمة الدقة
Decimal32decimal، ClickHouseDecimal، وأي نوع متوافق مع Convert.ToDecimal()الحد الأقصى للدقة هو 9
Decimal64decimal، ClickHouseDecimal، وأي نوع متوافق مع Convert.ToDecimal()الحد الأقصى للدقة هو 18
Decimal128decimal، ClickHouseDecimal، وأي نوع متوافق مع Convert.ToDecimal()الحد الأقصى للدقة هو 38
Decimal256decimal، ClickHouseDecimal، وأي نوع متوافق مع Convert.ToDecimal()الحد الأقصى للدقة هو 76

نوع JSON

ClickHouse Typeأنواع .NET المقبولةملاحظات
Jsonstring, JsonObject, JsonNode, أي كائنيعتمد السلوك على إعداد JsonWriteMode
يتحكم إعداد JsonWriteMode في السلوك عند كتابة JSON:
نوع الإدخالJsonWriteMode.String (الافتراضي)JsonWriteMode.Binary
stringيُمرَّر كما هويطرح ArgumentException
JsonObjectيُسلسَل عبر ToJsonString()يطرح ArgumentException
JsonNodeيُسلسَل عبر ToJsonString()يطرح ArgumentException
POCO مسجّليُسلسَل عبر JsonSerializer.Serialize()ترميز ثنائي مع دعم تلميحات النوع وسمات المسار المخصصة
POCO غير مسجّل / كائن مجهوليُسلسَل عبر JsonSerializer.Serialize()يطرح ClickHouseJsonSerializationException
  • String (الافتراضي): يقبل string وJsonObject وJsonNode أو أي كائن. تُسلسَل جميع المدخلات عبر System.Text.Json.JsonSerializer وتُرسل كسلاسل JSON لتحليلها على جهة الخادم. هذا هو الوضع الأكثر مرونة ويعمل من دون تسجيل النوع.
  • Binary: لا يقبل إلا أنواع POCO المسجّلة. تُحوَّل البيانات إلى تنسيق JSON الثنائي الخاص بـ ClickHouse على جهة العميل مع دعم كامل لتلميحات النوع. ويتطلب استدعاء connection.RegisterJsonSerializationType<T>() قبل الاستخدام. وتؤدي كتابة قيم string أو JsonNode في هذا الوضع إلى طرح ArgumentException.
// Default String mode works with any input
await client.InsertBinaryAsync(
    "my_table",
    new[] { "id", "data" },
    new[] { new object[] { 1u, new { name = "test", value = 42 } } }
);

// Binary mode requires explicit opt-in and type registration
var settings = new ClickHouseClientSettings("Host=localhost")
{
    JsonWriteMode = JsonWriteMode.Binary
};
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<MyPocoType>();
أعمدة JSON محددة الأنواع
عندما يحتوي عمود JSON على تلميحات للأنواع (مثل JSON(id UInt64, price Decimal128(2)))، يستخدم برنامج التشغيل هذه التلميحات لتسلسل القيم مع الحفاظ الكامل على سلامة الأنواع. وهذا يحافظ على الدقة في أنواع مثل UInt64 وDecimal وUUID وDateTime64، والتي قد تفقد دقتها لولا ذلك عند تسلسلها بصيغة JSON عامة.
تسلسل كائنات POCO
يمكن كتابة كائنات POCO في أعمدة JSON بطريقتين وفقًا لـ JsonWriteMode: String mode (الافتراضي): تُسلسَل كائنات POCO عبر System.Text.Json.JsonSerializer. لا يتطلب ذلك تسجيل الأنواع. هذا هو النهج الأبسط، كما أنه يعمل مع الكائنات المجهولة. Binary mode: تُسلسَل كائنات POCO باستخدام تنسيق JSON الثنائي الخاص ببرنامج التشغيل مع دعم كامل لتلميحات النوع. يجب تسجيل الأنواع باستخدام connection.RegisterJsonSerializationType<T>() قبل الاستخدام. يدعم هذا الوضع تعيينات مسارات مخصّصة عبر السمات:
  • [ClickHouseJsonPath("path")]: يربط خاصيةً بمسار JSON مخصّص. ويكون ذلك مفيدًا مع البُنى المتداخلة أو عندما يختلف اسم الخاصية عن مفتاح JSON المطلوب. يعمل فقط في Binary mode.
  • [ClickHouseJsonIgnore]: يستبعد خاصيةً من التسلسل. يعمل فقط في Binary mode.
CREATE TABLE events (
    id UInt32,
    data JSON(`user.id` Int64, `user.name` String, Timestamp DateTime64(3))
) ENGINE = MergeTree() ORDER BY id
using ClickHouse.Driver.Json;

public class UserEvent
{
    [ClickHouseJsonPath("user.id")]
    public long UserId { get; set; }

    [ClickHouseJsonPath("user.name")]
    public string UserName { get; set; }

    public DateTime Timestamp { get; set; }

    [ClickHouseJsonIgnore]
    public string InternalData { get; set; }  // Not serialized
}

// For Binary mode: Register the type and enable Binary mode
var settings = new ClickHouseClientSettings("Host=localhost") { JsonWriteMode = JsonWriteMode.Binary };
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<UserEvent>();

// Insert POCO - serialized to JSON with nested structure via custom path attributes
await client.InsertBinaryAsync(
    "events",
    new[] { "id", "data" },
    new[] { new object[] { 1u, new UserEvent { UserId = 123, UserName = "Alice", Timestamp = DateTime.UtcNow } } }
);
// Resulting JSON: {"user": {"id": 123, "name": "Alice"}, "Timestamp": "2024-01-15T..."}
مطابقة أسماء الخصائص مع تلميحات نوع العمود حسّاسة لحالة الأحرف. فالخاصية UserId لن تتطابق إلا مع تلميح مُعرَّف باسم UserId، وليس userid. وهذا يتوافق مع سلوك ClickHouse الذي يسمح بوجود مسارات مثل userName وUserName معًا كحقول منفصلة. القيود (في Binary mode فقط):
  • يجب تسجيل أنواع POCO على الاتصال باستخدام connection.RegisterJsonSerializationType<T>() قبل إجراء التسلسل. وستؤدي محاولة تسلسل نوع غير مسجّل إلى ظهور الاستثناء ClickHouseJsonSerializationException.
  • تتطلب خصائص القاموس والمصفوفة/القائمة تلميحات نوع في تعريف العمود لكي تُسلسَل بشكل صحيح. ومن دون هذه التلميحات، استخدم String mode بدلًا من ذلك.
  • لا تُكتَب القيم الخالية في خصائص POCO إلا إذا كان للمسار تلميح نوع Nullable(T) في تعريف العمود. ولا يسمح ClickHouse بأنواع Nullable داخل مسارات JSON الديناميكية، لذلك يتم تخطي الخصائص الخالية غير المزوّدة بتلميحات.
  • يتم تجاهل السمات ClickHouseJsonPath وClickHouseJsonIgnore في String mode (إذ لا تعمل إلا في Binary mode).

أنواع أخرى

نوع ClickHouseأنواع .NET المقبولةملاحظات
UUIDGuid, stringتُحلَّل السلسلة النصية باعتبارها Guid
IPv4IPAddress, stringيجب أن يكون IPv4؛ وتُحلَّل السلسلة النصية باستخدام IPAddress.Parse()
IPv6IPAddress, stringيجب أن يكون IPv6؛ وتُحلَّل السلسلة النصية باستخدام IPAddress.Parse()
Nothingأيّ نوعلا يكتب شيئًا (no-op)
Dynamicغير مدعوم (throws NotImplementedException)
Array(T)IList, nullتؤدي القيمة null إلى كتابة مصفوفة فارغة
Tuple(T1, T2, …)ITuple, IListيجب أن يتطابق عدد العناصر مع رتبة Tuple
Map(K, V)IDictionary
Nullable(T)null, DBNull, أو الأنواع المقبولة لـ Tيكتب بايت علامة null قبل القيمة
Enum8string, sbyte, أنواع رقميةيُبحث عن السلسلة النصية في قاموس enum
Enum16string, short, أنواع رقميةيُبحث عن السلسلة النصية في قاموس enum
LowCardinality(T)الأنواع المقبولة لـ Tيُفوَّض إلى النوع الأساسي
SimpleAggregateFunctionالأنواع المقبولة للنوع الأساسييُفوَّض إلى النوع الأساسي
Nested(…)IList من tuplesيجب أن يتطابق عدد العناصر مع عدد الحقول
Variant(T1, T2, …)قيمة تطابق أحد T1 أو T2 أو …Throws ArgumentException إذا لم يطابق أي نوع
QBit(T, dim)IListيُفوَّض إلى Array؛ والبُعد بيانات وصفية فقط

أنواع Geometry

نوع ClickHouseأنواع .NET المقبولةملاحظات
PointSystem.Drawing.Point, ITuple, IList (عنصران)
RingIList من عناصر Point
LineStringIList من عناصر Point
PolygonIList من عناصر Ring
MultiLineStringIList من عناصر LineString
MultiPolygonIList من عناصر Polygon
Geometryأي نوع من أنواع Geometry أعلاهVariant لجميع أنواع Geometry

غير مدعوم عند الكتابة

نوع ClickHouseملاحظات
Dynamicيؤدي إلى إطلاق NotImplementedException
AggregateFunctionيؤدي إلى إطلاق AggregateFunctionException

التعامل مع النوع Nested

يمكن قراءة النوع Nested في ClickHouse (Nested(...)) وكتابته باستخدام دلالات المصفوفات.
CREATE TABLE test.nested (
    id UInt32,
    params Nested (param_id UInt8, param_val String)
) ENGINE = Memory
var row1 = new object[] { 1, new[] { 1, 2, 3 }, new[] { "v1", "v2", "v3" } };
var row2 = new object[] { 2, new[] { 4, 5, 6 }, new[] { "v4", "v5", "v6" } };

await client.InsertBinaryAsync(
    "test.nested",
    new[] { "id", "params.param_id", "params.param_val" },
    new[] { row1, row2 }
);

التسجيل والتشخيص

يتكامل عميل ClickHouse لـ .NET مع تجريدات Microsoft.Extensions.Logging لتوفير تسجيل خفيف الوزن يُفعَّل عند الحاجة. وعند تمكينه، يُصدر برنامج التشغيل رسائل منظَّمة لأحداث دورة حياة الاتصال، وتنفيذ الأوامر، وعمليات النقل، وعمليات الإدراج المجمّع. ويظل التسجيل اختياريًا بالكامل—فالتطبيقات التي لا تُعدّ مُسجِّلًا تواصل العمل دون أي عبء إضافي.

البدء السريع

using ClickHouse.Driver;
using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Information);
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);

استخدام appsettings.json

يمكنك ضبط مستويات التسجيل باستخدام إعدادات .NET القياسية:
using ClickHouse.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(configuration.GetSection("Logging"))
        .AddConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);

استخدام تهيئة داخل الذاكرة

يمكنك أيضًا ضبط مستوى تفصيل التسجيل لكل فئة في الشيفرة:
using ClickHouse.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var categoriesConfiguration = new Dictionary<string, string>
{
    { "LogLevel:Default", "Warning" },
    { "LogLevel:ClickHouse.Driver.Connection", "Information" },
    { "LogLevel:ClickHouse.Driver.Command", "Debug" }
};

var config = new ConfigurationBuilder()
    .AddInMemoryCollection(categoriesConfiguration)
    .Build();

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(config)
        .AddSimpleConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);

الفئات والبواعث

يستخدم برنامج التشغيل فئات مخصّصة بحيث يمكنك ضبط مستويات السجل بدقة لكل مكوّن:
الفئةالمصدرأبرز الجوانب
ClickHouse.Driver.ConnectionClickHouseConnectionدورة حياة الاتصال، واختيار HTTP client factory، وفتح الاتصال وإغلاقه، وإدارة الجلسات.
ClickHouse.Driver.CommandClickHouseCommandبدء تنفيذ الاستعلام واكتماله، والتوقيت، ومعرّفات الاستعلام، وإحصاءات الخادم، وتفاصيل الخطأ.
ClickHouse.Driver.TransportClickHouseConnectionطلبات HTTP streaming منخفضة المستوى، وعلامات الضغط، ورموز حالة الاستجابة، وإخفاقات النقل.
ClickHouse.Driver.ClientClickHouseClientعمليات insert الثنائية، والاستعلامات، والعمليات الأخرى
ClickHouse.Driver.NetTraceTraceHelperتتبّع الشبكة، فقط عند تمكين وضع التصحيح

مثال: تشخيص مشكلات الاتصال

{
    "Logging": {
        "LogLevel": {
            "ClickHouse.Driver.Connection": "Trace",
            "ClickHouse.Driver.Transport": "Trace"
        }
    }
}
سيُسجِّل هذا ما يلي:
  • اختيار HTTP client factory ‏(التجمّع default مقابل اتصال واحد)
  • تهيئة HTTP handler ‏(SocketsHttpHandler أو HttpClientHandler)
  • إعدادات connection pool ‏(MaxConnectionsPerServer وPooledConnectionLifetime وما إلى ذلك)
  • إعدادات timeout ‏(ConnectTimeout وExpect100ContinueTimeout وما إلى ذلك)
  • تهيئة SSL/TLS
  • أحداث فتح الاتصال وإغلاقه
  • تتبّع معرّف الجلسة

وضع Debug: تتبّع الشبكة والتشخيص

للمساعدة في تشخيص مشكلات الشبكة، تتضمن مكتبة برنامج التشغيل أداة مساعدة تُمكّن التتبّع منخفض المستوى للمكوّنات الداخلية للشبكات في .NET. ولتمكينه، يجب تمرير LoggerFactory مع تعيين المستوى إلى Trace، وضبط EnableDebugMode على true (أو تمكينه يدويًا عبر الصنف ClickHouse.Driver.Diagnostic.TraceHelper). ستُسجَّل الأحداث ضمن الفئة ClickHouse.Driver.NetTrace. تحذير: سيؤدي ذلك إلى إنشاء سجلات شديدة التفصيل، وسيؤثر في الأداء. لا يُنصح بتمكين وضع Debug في بيئة الإنتاج.
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace); // Must be Trace level to see network events
});

var settings = new ClickHouseClientSettings()
{
    LoggerFactory = loggerFactory,
    EnableDebugMode = true,  // Enable low-level network tracing
};

OpenTelemetry

يوفّر برنامج التشغيل دعمًا مدمجًا للتتبّع الموزّع في OpenTelemetry عبر واجهة برمجة تطبيقات .NET System.Diagnostics.Activity. وعند تمكينه، يُنشئ برنامج التشغيل spans لعمليات قاعدة البيانات يمكن تصديرها إلى أنظمة observability الخلفية مثل Jaeger أو ClickHouse نفسه (عبر OpenTelemetry Collector).

تمكين التتبّع

في تطبيقات ASP.NET Core، أضِف ActivitySource الخاص ببرنامج تشغيل ClickHouse إلى تهيئة OpenTelemetry:
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)  // Subscribe to ClickHouse driver spans
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());             // Or AddJaegerExporter(), etc.
لتطبيقات سطر الأوامر، أو للاختبار، أو للإعداد اليدوي:
using OpenTelemetry;
using OpenTelemetry.Trace;

var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)
    .AddConsoleExporter()
    .Build();

سمات span

يتضمن كل span سمات قاعدة البيانات القياسية في OpenTelemetry، بالإضافة إلى إحصاءات query الخاصة بـ ClickHouse التي يمكن استخدامها في debugging.
السمةالوصف
db.systemتكون دائمًا "clickhouse"
db.nameاسم قاعدة البيانات
db.userاسم المستخدم
db.statementSQL query (إذا كان enabled)
db.clickhouse.read_rowsالصفوف التي قرأها الـ query
db.clickhouse.read_bytesالبايتات التي قرأها الـ query
db.clickhouse.written_rowsالصفوف التي كتبها الـ query
db.clickhouse.written_bytesالبايتات التي كتبها الـ query
db.clickhouse.elapsed_nsexecution time على جهة الخادم بالنانوثانية

خيارات التكوين

تحكَّم في سلوك التتبّع باستخدام ClickHouseDiagnosticsOptions:
using ClickHouse.Driver.Diagnostic;

// Include SQL statements in spans (default: false for security)
ClickHouseDiagnosticsOptions.IncludeSqlInActivityTags = true;

// Truncate long SQL statements (default: 1000 characters)
ClickHouseDiagnosticsOptions.StatementMaxLength = 500;
قد يؤدي تفعيل IncludeSqlInActivityTags إلى كشف بيانات حساسة ضمن التتبعات الخاصة بك. استخدمه بحذر في بيئات الإنتاج.

إعدادات TLS

عند الاتصال بـ ClickHouse عبر HTTPS، يمكنك تهيئة سلوك TLS/SSL بعدة طرق.

التحقق المخصص من الشهادات

بالنسبة إلى بيئات الإنتاج التي تتطلب منطقًا مخصصًا للتحقق من الشهادات، وفّر HttpClient خاصًا بك مع معالج ServerCertificateCustomValidationCallback مُعدّ:
using System.Net;
using System.Net.Security;
using ClickHouse.Driver;

var handler = new HttpClientHandler
{
    // Required when compression is enabled (default)
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,

    ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
    {
        // Example: Accept a specific certificate thumbprint
        if (cert?.Thumbprint == "YOUR_EXPECTED_THUMBPRINT")
            return true;

        // Example: Accept certificates from a specific issuer
        if (cert?.Issuer.Contains("YourOrganization") == true)
            return true;

        // Default: Use standard validation
        return sslPolicyErrors == SslPolicyErrors.None;
    },
};

var httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(5) };

var settings = new ClickHouseClientSettings
{
    Host = "my.clickhouse.server",
    Protocol = "https",
    HttpClient = httpClient,
};

using var client = new ClickHouseClient(settings);
اعتبارات مهمة عند استخدام HttpClient مخصّص
  • فك الضغط التلقائي: يجب تمكين AutomaticDecompression إذا لم يكن الضغط معطّلًا (فالضغط مفعّل افتراضيًا).
  • مهلة الخمول: اضبط PooledConnectionIdleTimeout على قيمة أقل من keep_alive_timeout الخاص بالخادم (10 ثوانٍ في ClickHouse Cloud) لتجنّب أخطاء الاتصال الناتجة عن الاتصالات شبه المفتوحة.

دعم ORM

تتطلب أطر ORM واجهة ADO.NET (ClickHouseConnection). ولإدارة دورة حياة الاتصالات على النحو الصحيح، أنشئ الاتصالات باستخدام ClickHouseDataSource:
// Register DataSource as singleton
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default");

// Create connections for ORM use
await using var connection = await dataSource.OpenConnectionAsync();
// Pass connection to your ORM...

Dapper

يعمل ClickHouse.Driver مع Dapper. ويحوّل برنامج التشغيل تلقائيًا صياغة @parameter الخاصة بـ Dapper إلى الصياغة الأصلية في ClickHouse {parameter:Type}، مع استنتاج الأنواع من قيم .NET. استخدم ClickHouseDataSource لإدارة مدة بقاء الاتصال على نحو صحيح:
var dataSource = new ClickHouseDataSource("Host=localhost");
services.AddSingleton(dataSource); // Register as singleton in DI

using var connection = dataSource.CreateConnection();

أنماط تمرير المَعلمات

جميع أنماط مَعلمات Dapper القياسية مدعومة: الكائنات المجهولة:
await connection.ExecuteAsync(
    "INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)",
    new { Id = 1, Name = "alice", Balance = 3.14 });
فئات POCO:
class InsertParams
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Balance { get; set; }
}

var param = new InsertParams { Id = 42, Name = "bob", Balance = 99.9 };
await connection.ExecuteAsync(
    "INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)", param);
القاموس:
var parameters = new Dictionary<string, object> { { "Id", 2 } };
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id = @Id", parameters);
DynamicParameters (من قاموس أو كائن مجهول الاسم):
var dynParams = new DynamicParameters(new { Id = 1 });
// or: new DynamicParameters(new Dictionary<string, object> { { "Id", 1 } });

var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id = @Id", dynParams);

الاستعلام عن POCOs

يربط Dapper الأعمدة بالخصائص حسب الاسم (من دون حساسية لحالة الأحرف):
class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Balance { get; set; }
}

// From a table
var users = (await connection.QueryAsync<User>("SELECT id, name, balance FROM users")).ToList();

// From a literal
var row = (await connection.QueryAsync<User>("SELECT 1 as id, 'hello' as name, 2.5 as balance")).Single();

صياغة المعلمات الأصلية في ClickHouse

عندما تحتاج إلى تحكم صريح في النوع، استخدم مباشرةً في SQL صياغة {param:Type} الخاصة بـ ClickHouse مع Dictionary<string, object> لقيم المعلمات. لا تجمع بين صياغة @param وصياغة {param:Type} للمعلمة نفسها.
var parameters = new Dictionary<string, object> { { "value", 42 } };
var result = await connection.QueryAsync<int>("SELECT {value:Int32}", parameters);

WHERE IN

تعمل ميزة توسيع IN المدمجة في Dapper:
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id IN @Ids ORDER BY id",
    new { Ids = new[] { 1, 3, 5 } });
يعيد Dapper صياغة ذلك إلى WHERE id IN (@Ids1, @Ids2, @Ids3)، ويحوّل برنامج التشغيل كل معلمة موسَّعة. كما تعمل الدالة has() في ClickHouse أيضًا مع معلمة من النوع Array:
var parameters = new Dictionary<string, object> { { "ids", new[] { 1, 3, 5 } } };
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE has({ids:Array(Int32)}, id) ORDER BY id",
    parameters);

معالِجات الأنواع المخصّصة

تتطلّب بعض أنواع ClickHouse، مثل ITuple وBigInteger وClickHouseDecimal، تسجيل معالِجات عند بدء التشغيل:
// ClickHouseDecimal (for Decimal64/128/256 columns)
SqlMapper.AddTypeHandler(new ClickHouseDecimalHandler());

// BigInteger (for Int128/Int256/UInt128/UInt256 columns)
SqlMapper.AddTypeHandler(new BigIntegerHandler());

// IPAddress (for IPv4/IPv6 columns)
SqlMapper.AddTypeHandler(new IpAddressHandler());
راجع مثال Dapper للاطلاع على مثال لتنفيذ معالج أنواع.

Dapper.Contrib

يعمل كلٌّ من GetAll<T>() وGet<T>(id). أما Insert<T>() فلا يعمل، إذ يولّد صياغة SQL Server (SCOPE_IDENTITY, []). ويُوصى باستخدام الطريقة الأصلية InsertBinaryAsync في ClickHouseClient بدلًا من ذلك.
[Table("test.users")]
record class UserRecord(int Id, string Name, DateTime Timestamp);

var all = await connection.GetAllAsync<UserRecord>();
var one = await connection.GetAsync<UserRecord>(1);
يجب أن تتطابق أسماء الخصائص تمامًا مع أسماء أعمدة ClickHouse (تراعي حالة الأحرف).

القيود

العنصرالحالةالتفاصيل
Tuple كـ نتيجةيعمليتطلب تسجيل SqlMapper.TypeHandler<ITuple>
Tuple كـ معلمةغير مدعوملا يستطيع Dapper إجراء تسلسل لـ ITuple/Tuple<> كقيمة DbParameter
أنواع Nested كمعلمةغير مدعومللسبب نفسه — يرفض Dapper الأنواع المعقدة كقيم للمعلمات
أنواع Geo كمعلمةغير مدعومPoint, Ring, Polygon, LineString, MultiLineString, MultiPolygon
Dapper.Contrib.Insert<T>()غير مدعوميُنشئ صياغة خاصة بـ SQL Server
النوع Nothingغير مدعوملا يوجد له تمثيل ذو معنى في .NET

Linq2db

يتوافق برنامج التشغيل هذا مع linq2db، وهو ORM خفيف الوزن وموفّر LINQ لـ .NET. راجع موقع المشروع للاطلاع على وثائق تفصيلية. مثال على الاستخدام: أنشئ DataConnection باستخدام موفّر ClickHouse:
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider.ClickHouse;

var connectionString = "Host=localhost;Port=8123;Database=default";
var options = new DataOptions()
    .UseClickHouse(connectionString, ClickHouseProvider.ClickHouseDriver);

await using var db = new DataConnection(options);
يمكن تحديد تعيينات الجداول باستخدام السمات أو التهيئة بأسلوب Fluent. إذا كانت أسماء الفئة والخاصية لديك تطابق تمامًا أسماء الجدول والعمود، فلا حاجة إلى أي تهيئة:
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
الاستعلام عن:
await using var db = new DataConnection(options);

var products = await db.GetTable<Product>()
    .Where(p => p.Price > 100)
    .OrderByDescending(p => p.Name)
    .ToListAsync();
النسخ المجمّع: استخدم BulkCopyAsync لإجراء عمليات إدراج مجمّعة بكفاءة.
await using var db = new DataConnection(options);
var table = db.GetTable<Product>();

var options = new BulkCopyOptions
{
    MaxBatchSize = 100000,
    MaxDegreeOfParallelism = 1,
    WithoutSession = true
};

await table.BulkCopyAsync(options, products);

Entity Framework Core

موفّر Entity Framework Core الرسمي لـ ClickHouse. اربط فئات C# بجداول ClickHouse، ونفّذ الاستعلامات باستخدام LINQ، وأدرِج البيانات عبر SaveChanges — وكل ذلك باستخدام أنماط EF Core المألوفة.
هذا الموفّر قيد التطوير النشط. يدعم الإصدار الحالي استعلامات LINQ (بما في ذلك عمليات JOIN، والاستعلامات الفرعية، وعمليات المجموعات)، وINSERT عبر SaveChanges / BulkInsertAsync، وعمليات الترحيل مع دعم DDL الكامل (CREATE / ALTER / DROP)، وتهيئة محرك الجدول الخاصة بـ ClickHouse. لا يدعم UPDATE / DELETE.

التثبيت

dotnet add package ClickHouse.EntityFrameworkCore
يتطلب .NET 10.0 وEF Core 10.

البدء السريع

عرّف الكيان وDbContext، ثم أجرِ استعلامًا باستخدام LINQ:
using Microsoft.EntityFrameworkCore;

public class PageView
{
    public long Id { get; set; }
    public string Path { get; set; }
    public DateOnly Date { get; set; }
    public string UserAgent { get; set; }
}

public class AnalyticsContext : DbContext
{
    public DbSet<PageView> PageViews { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseClickHouse("Host=localhost;Database=analytics");
}

// Query
await using var ctx = new AnalyticsContext();

var topPages = await ctx.PageViews
    .Where(v => v.Date >= new DateOnly(2024, 1, 1))
    .GroupBy(v => v.Path)
    .Select(g => new { Path = g.Key, Views = g.Count() })
    .OrderByDescending(x => x.Views)
    .Take(10)
    .ToListAsync();

الأنواع المدعومة

الفئةأنواع ClickHouseأنواع CLR
الأعداد الصحيحةInt8Int64, UInt8UInt64sbyte, short, int, long, byte, ushort, uint, ulong
الأعداد الصحيحة الكبيرةInt128, Int256, UInt128, UInt256BigInteger
الأعداد ذات الفاصلة العائمةFloat32, Float64, BFloat16float, double
الأعداد العشريةDecimal(P,S), Decimal32(S), Decimal64(S), Decimal128(S)decimal أو ClickHouseDecimal
BoolBoolbool
السلاسل النصيةString, FixedString(N)string
التعداداتEnum8(...), Enum16(...)string أو enum في C#
التاريخ/الوقتDate, Date32, DateTime, DateTime64(P, 'TZ')DateOnly, DateTime
الوقتTime, Time64(N)TimeSpan
UUIDUUIDGuid
الشبكةIPv4, IPv6IPAddress
المصفوفاتArray(T)T[], List<T>, IList<T>, ICollection<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>
الخرائطMap(K, V)Dictionary<K,V>
TuplesTuple(T1, ...)Tuple<...> أو ValueTuple<...>
VariantVariant(T1, T2, ...)object
DynamicDynamicobject
JSONJsonJsonNode أو string
الأنواع الجغرافيةPoint, Ring, LineString, Polygon, MultiLineString, MultiPolygon, GeometryTuple<double,double> ومصفوفات منها؛ وobject لـ Geometry
المغلِّفاتNullable(T), LowCardinality(T)تُفك تلقائيًا
استخدم ClickHouseDecimal (من ClickHouse.Driver.Numerics) بدلًا من decimal عندما تحتاج إلى الدقة الكاملة لأعمدة Decimal128/Decimal256، لأن decimal في .NET يقتصر على 28–29 رقمًا معنويًا.

عمليات LINQ المدعومة

الاستعلامات: Where, OrderBy, Take, Skip, Select, First, Single, Any, All, Count, Distinct, AsNoTracking GROUP BY والتجميع: GroupBy مع Count, LongCount, Sum, Average, Min, Max — بما في ذلك HAVING (استخدام .Where() بعد .GroupBy())، وإجراء عدة عمليات تجميع ضمن إسقاط واحد، واستخدام OrderBy على نتائج التجميع. عمليات JOIN: Join (INNER)، وأنماط GroupJoin/SelectMany‏ (LEFT وCROSS). تُرجِع LEFT JOIN قيمة null فعلية للصفوف غير المتطابقة (راجع دلالات null في LEFT JOIN أدناه). الاستعلامات الفرعية: Contains / IN المترابطة، وAny / EXISTS، وAll، والاستعلامات الفرعية scalar ضمن الإسقاطات. عمليات المجموعات: Concat (→ UNION ALL)، وUnion (→ UNION DISTINCT)، وIntersect، وExcept. المجموعات المحلية المضمنة: تُترجَم عمليات join وContains على المجموعات الموجودة في الذاكرة (int[], List<T>, إلخ) إلى سلسلة من عمليات UNION. طرق String: Contains, StartsWith, EndsWith, IndexOf, Replace, Substring, Trim/TrimStart/TrimEnd, ToLower, ToUpper, Length, IsNullOrEmpty, Concat (والمعامل +). الدوال الرياضية: تُترجَم طرق Math وMathF القياسية إلى ما يقابلها في ClickHouse — بما يشمل الدوال الحسابية واللوغاريتمية والمثلثية ودوال الأدوات المساعدة.
دلالات NULL في LEFT JOIN
يحقن الموفّر set_join_use_nulls=1 تلقائيًا في كل مسار اتصال لمواءمة توقّعات Entity Framework بشأن سلوك JOIN. إذا كان خادم ClickHouse أو ملف التعريف لديك يمنع تغيير هذا الإعداد (على سبيل المثال، ملف تعريف بقيمة readonly=1)، فأوقِف هذا السلوك باستخدام:
optionsBuilder.UseClickHouse(connectionString, o => o.DisableJoinNullSemantics());
عند تفعيل خيار إلغاء الاشتراك، يعيد LEFT JOIN القيم الافتراضية لأعمدة ClickHouse، ولا يعود اكتشاف التنقل في EF المعتمد على null يعمل كما هو متوقع. استخدم مقارنات صريحة مع 0 / "" بدلًا من == null.

إدراج البيانات

يستخدم SaveChanges واجهة برمجة تطبيقات InsertBinaryAsync الأصلية لبرنامج التشغيل — بترميز RowBinary مع ضغط GZip، وهو أكثر كفاءة بكثير من عبارات SQL ذات المعلمات:
await using var ctx = new AnalyticsContext();

ctx.PageViews.Add(new PageView
{
    Id = 1,
    Path = "/home",
    Date = new DateOnly(2024, 6, 15),
    UserAgent = "Mozilla/5.0"
});

await ctx.SaveChangesAsync();
تنتقل الكيانات من Added إلى Unchanged بعد الحفظ، تمامًا كما يحدث مع أي موفّر آخر لـ EF Core. يمكن ضبط حجم الدفعة (الافتراضي 1000):
optionsBuilder.UseClickHouse("Host=localhost", o => o.MaxBatchSize(5000));

الإدراج المجمّع

لعمليات التحميل عالية الإنتاجية، استخدم BulkInsertAsync بدلًا من SaveChanges. هذه طريقة امتداد لـ DbContext تتجاوز بالكامل متتبّع التغييرات في EF Core، وآلية حلّ الهوية، وإدارة الحالة — إذ تستدعي مباشرةً InsertBinaryAsync الخاصة ببرنامج التشغيل باستخدام ترميز RowBinary وضغط GZip. وهذا يجعلها مناسبة لتحميل مجموعات بيانات كبيرة عندما لا تحتاج إلى تتبّع الكيانات بعد الإدراج:
var events = Enumerable.Range(0, 100_000)
    .Select(i => new PageView
    {
        Id = i,
        Path = $"/page/{i}",
        Date = DateOnly.FromDateTime(DateTime.Today)
    });

long rowsInserted = await ctx.BulkInsertAsync(events);
يمكن أن يكون الإدخال أي IEnumerable<T> — إذ يمر عبر الكيانات دون تحميلها جميعًا إلى الذاكرة. قيمة الإرجاع هي عدد الصفوف المُدرجة. لا تُرفَق الكيانات بـ DbContext بعد الإدراج، لذلك لا يحدث انتقال في الحالة من Added إلى Unchanged.

التعدادات

يمكن تعيين أعمدة Enum8/Enum16 في ClickHouse كخصائص string أو كأنواع enum في C#. وعند استخدام تعدادات C#، يُجري الموفّر التحويل تلقائيًا بين التعداد وتمثيله النصي:
public enum Status { Active, Inactive, Pending }

public class User
{
    public long Id { get; set; }
    public Status Status { get; set; }
}

// Query with enum values
var active = await ctx.Users
    .Where(u => u.Status == Status.Active)
    .ToListAsync();

تحويلات الأنواع المخصّصة

يتيح لك نظام ValueConverter في EF Core تعيين الأنواع المخصّصة إلى أنواع يدعمها الموفّر بالفعل. ولا يرى الموفّر نوعك المخصّص مطلقًا، إذ يتولّى EF Core التحويل عند نقطة الفصل. التحويل على مستوى كل خاصية:
public class Money
{
    public decimal Amount { get; set; }
    public string Currency { get; set; }
}

public class Order
{
    public long Id { get; set; }
    public Money Price { get; set; }
}

// In OnModelCreating:
modelBuilder.Entity<Order>()
    .Property(o => o.Price)
    .HasConversion(
        m => $"{m.Amount}|{m.Currency}",
        s => new Money
        {
            Amount = decimal.Parse(s.Split('|')[0]),
            Currency = s.Split('|')[1]
        })
    .HasColumnType("String");
فئة مُحوِّل قابلة لإعادة الاستخدام:
public class MoneyConverter : ValueConverter<Money, string>
{
    public MoneyConverter() : base(
        m => $"{m.Amount}|{m.Currency}",
        s => Parse(s)) { }

    private static Money Parse(string s)
    {
        var parts = s.Split('|');
        return new Money { Amount = decimal.Parse(parts[0]), Currency = parts[1] };
    }
}

// Apply to a single property:
.HasConversion<MoneyConverter>()

// Or apply to all properties of a type via conventions:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<Money>()
        .HaveConversion<MoneyConverter>();
}

تعليقات توضيحية لنوع العمود

بالنسبة إلى الأنواع القياسية مثل string وint وDateTime وغيرها، يستنتج الموفّر نوع ClickHouse تلقائيًا. أمّا الأنواع ذات المعلمات والأغلفة، فيلزم تحديد نوع ClickHouse صراحةً. استخدام التعليقات التوضيحية للبيانات (السمات):
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

[Table("sensor_readings")]
public class SensorReading
{
    public long Id { get; set; }

    [Column(TypeName = "Array(String)")]
    public string[] Tags { get; set; }

    [Column(TypeName = "Map(String, String)")]
    public Dictionary<string, string> Metadata { get; set; }

    [Column(TypeName = "Nullable(Float64)")]
    public double? Value { get; set; }

    [Column(TypeName = "Decimal128(18)")]
    public decimal HighPrecision { get; set; }
}
استخدام أسلوب fluent API في OnModelCreating:
modelBuilder.Entity<SensorReading>(e =>
{
    e.ToTable("sensor_readings");
    e.Property(x => x.Tags).HasColumnType("Array(String)");
    e.Property(x => x.Metadata).HasColumnType("Map(String, String)");
    e.Property(x => x.Value).HasColumnType("Nullable(Float64)");
    e.Property(x => x.Category).HasColumnType("LowCardinality(String)");
    e.Property(x => x.HighPrecision).HasColumnType("Decimal128(18)");
});
الأغلفة المتداخلة مثل Array(Nullable(Int32)) وLowCardinality(Nullable(String)) مدعومة — إذ يفكّ الموفّر تغليف Nullable وLowCardinality تلقائيًا عند كل مستوى من مستويات التداخل.

أعمدة Variant وDynamic

تُقابِل أعمدة ClickHouse Variant(T1, T2, ...) وDynamic النوع object في .NET. وبما أن object عام جدًا بحيث لا يتيح استنتاج النوع تلقائيًا، يجب التصريح بنوع التخزين صراحةً باستخدام .HasColumnType():
public class Event
{
    public long Id { get; set; }
    public object? Payload { get; set; }
}

// In OnModelCreating:
entity.Property(e => e.Payload).HasColumnType("Variant(String, UInt64, Array(UInt64))");
// or:
entity.Property(e => e.Payload).HasColumnType("Dynamic");
عند القراءة، يُفك تسلسل القيمة تلقائيًا إلى نوع .NET الموافق للمميِّز المخزَّن (مثل string وulong وulong[]).

أعمدة JSON

يدعم المزوّد نوع العمود Json في ClickHouse، ويعيّنه إلى System.Text.Json.Nodes.JsonNode (بشكل أساسي) أو string (عبر ValueConverter تلقائي):
using System.Text.Json.Nodes;

public class Event
{
    public long Id { get; set; }
    public JsonNode? Data { get; set; }
}

// In OnModelCreating:
entity.Property(e => e.Data).HasColumnType("Json");
تتم قراءة JSON وكتابته من خلال كلٍّ من SaveChanges وBulkInsertAsync:
ctx.Events.Add(new Event
{
    Id = 1,
    Data = JsonNode.Parse("""{"action": "click", "x": 100, "y": 200}""")
});
await ctx.SaveChangesAsync();

var ev = await ctx.Events.Where(e => e.Id == 1).SingleAsync();
string action = ev.Data!["action"]!.GetValue<string>(); // "click"
إذا كنت تفضّل سلاسل JSON الخام، فاضبط الخاصية لتكون string مع نوع عمود Json — يطبّق الموفّر ValueConverter تلقائيًا:
public class Event
{
    public long Id { get; set; }
    public string? Data { get; set; }  // raw JSON string
}

entity.Property(e => e.Data).HasColumnType("Json");
  • عدم ترجمة JSON path — لا يُترجَم entity.Data["name"] في LINQ إلى صياغة SQL data.name الخاصة بـ ClickHouse. طبّق عامل التصفية على الأعمدة غير JSON وافحص JSON في الذاكرة.
  • دلالات NULL — يعيد JSON type في ClickHouse القيمة {} (كائنًا فارغًا) لقيم NULL بدلًا من SQL NULL.
  • دقة الأعداد الصحيحة — يخزّن JSON في ClickHouse جميع الأعداد الصحيحة بصيغة Int64. عند القراءة عبر JsonNode، استخدم GetValue<long>() بدلًا من GetValue<int>().

محركات الجداول

اضبط محركات جداول ClickHouse والعبارات الخاصة بكل محرك عبر واجهة ToTable(name, t => ...) بأسلوب الاستدعاءات المتسلسلة. عند عدم ضبط أي محرك، يستخدم الموفّر MergeTree افتراضيًا، مع استمداد ORDER BY من المفتاح الأساسي للكيان.
modelBuilder.Entity<Event>(e =>
{
    e.ToTable("events", t => t
        .HasMergeTreeEngine()
        .WithOrderBy("UserId", "Timestamp")
        .WithPartitionBy("toYYYYMM(Timestamp)")
        .WithPrimaryKey("UserId")
        .WithSettings("index_granularity = 8192"));
});
عائلات المحركات المدعومة:
المحرّكالأسلوب المتسلسلملاحظات
MergeTreeHasMergeTreeEngine()الافتراضي عند عدم تكوين أي شيء
ReplacingMergeTreeHasReplacingMergeTreeEngine("Version", "IsDeleted") or HasReplacingMergeTreeEngine<T>(e => e.Version)عمودا Version وIsDeleted اختياريان
SummingMergeTreeHasSummingMergeTreeEngine(…) or HasSummingMergeTreeEngine<T>(e => new { … })أعمدة الجمع اختيارية
AggregatingMergeTreeHasAggregatingMergeTreeEngine()
CollapsingMergeTreeHasCollapsingMergeTreeEngine("Sign") or HasCollapsingMergeTreeEngine<T>(e => e.Sign)يجب أن يكون العمود Sign من النوع Int8
VersionedCollapsingMergeTreeHasVersionedCollapsingMergeTreeEngine("Sign", "Version") or <T>(e => e.Sign, e => e.Version)
GraphiteMergeTreeHasGraphiteMergeTreeEngine("config_section")
Log, TinyLog, StripeLog, MemoryHasLogEngine(), HasTinyLogEngine(), HasStripeLogEngine(), HasMemoryEngine()بدون ORDER BY / PARTITION BY
بنود المحرّك: WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings. ترتبط جميعها بمنشئ المحرّك المُعاد من HasXxxEngine(). ميزات على مستوى العمود: HasCodec, HasTtl, HasComment, HasDefault — جميعها تدخل في عمليات الترحيل. فهارس تخطي البيانات — عبر HasIndex(...).HasSkippingIndexType(...):
modelBuilder.Entity<Event>()
    .HasIndex(e => e.UserId)
    .HasSkippingIndexType("minmax")
    .HasGranularity(4);

// Index with parameters (e.g. bloom_filter, tokenbf_v1):
modelBuilder.Entity<Event>()
    .HasIndex(e => e.Tag)
    .HasSkippingIndexType("bloom_filter")
    .HasSkippingIndexParams("0.01")
    .HasGranularity(1);
يُتجاهَل الفهرس القياسي (غير القائم على تخطي البيانات) بصمت، إذ لا يوجد في ClickHouse ما يقابله. أما الفهارس الفريدة فتؤدي إلى ظهور استثناء، لأن ClickHouse لا يفرض التفرد.

عمليات الترحيل

سير العمل القياسي لعمليات الترحيل في EF Core:
dotnet ef migrations add InitialCreate
dotnet ef database update
العمليات المدعومة:
العمليةالناتج
CREATE TABLEيتضمن عبارة engine، وORDER BY، وPARTITION BY، وSETTINGS، ومرمّزات الأعمدة/TTL/التعليقات/القيم الافتراضية
ALTER TABLE ADD COLUMN
ALTER TABLE DROP COLUMN
ALTER TABLE MODIFY COLUMNيعالج تغيير النوع، بالإضافة إلى إضافة/إزالة السمات (CODEC, TTL, COMMENT, DEFAULT)
ALTER TABLE RENAME COLUMN
RENAME TABLE
ALTER TABLE ADD INDEX / DROP INDEXفهارس تخطي البيانات فقط
CREATE DATABASE / DROP DATABASEعبر EnsureCreated / EnsureDeleted وعمليات الترحيل

قيود الترحيل

الميزةالسبب
المفاتيح الخارجيةلا يفرض ClickHouse المفاتيح الخارجية. ترفض عمليات الترحيل AddForeignKey، ويُصدر مدقق النموذج تحذيرًا عند بناء النموذج.
القيود الفريدة / الفهارس الفريدةلا يفرض ClickHouse التفرد. وتؤدي الفهارس الفريدة إلى إطلاق استثناء أثناء الترحيل.
القيم التي ينشئها الخادم (الزيادة التلقائية / IDENTITY)لا يوجد ما يقابلها في ClickHouse.
أعمدة Nested(…)غير مدعومة بعد كنوع CLR مُعيَّن.
الكيانات المملوكة بصيغة JSON (.ToJson())لم يُنفَّذ بعد التعيين البنيوي لـ JSON للكيانات المملوكة. استخدم JsonNode / string في عمود Json بدلًا من ذلك (راجع أعمدة JSON).
وبالإضافة إلى عمليات الترحيل، لا يزال الموفّر لا يدعم أيضًا ما يلي:
  • UPDATE / DELETE
  • المعاملات: BeginTransaction عملية no-op. لا يوجد دعم لمعاملات ACID في ClickHouse.
  • ترجمة استعلامات JSON path: ‏entity.Data["key"] في LINQ لا يُترجم إلى صياغة SQL الخاصة بـ ClickHouse، أي data.key. استخدم التصفية على أعمدة غير JSON وافحص JSON في الذاكرة.

القيود

أعمدة AggregateFunction

لا يمكن الاستعلام عن الأعمدة من النوع AggregateFunction(...) أو الإدراج فيها مباشرةً. للإدراج:
INSERT INTO t VALUES (uniqState(1));
للاختيار:
SELECT uniqMerge(c) FROM t;

آخر تعديل في ٣ يوليو ٢٠٢٦