Skip to main content

Installation

Install via NuGet:
dotnet add package Qdrant.Client
Or via Package Manager:
Install-Package Qdrant.Client

Quick Start

using Qdrant.Client;
using Qdrant.Client.Grpc;

// Connect to Qdrant
var client = new QdrantClient("localhost", 6334);

// Create collection
await client.CreateCollectionAsync(
    collectionName: "my_collection",
    vectorsConfig: new VectorParams
    {
        Size = 384,
        Distance = Distance.Cosine
    }
);

// Upsert points
await client.UpsertAsync(
    collectionName: "my_collection",
    points: new[]
    {
        new PointStruct
        {
            Id = 1,
            Vectors = new[] { 0.05f, 0.61f, 0.76f, 0.74f },
            Payload =
            {
                ["city"] = "Berlin",
                ["country"] = "Germany"
            }
        },
        new PointStruct
        {
            Id = 2,
            Vectors = new[] { 0.19f, 0.81f, 0.75f, 0.11f },
            Payload =
            {
                ["city"] = "London",
                ["country"] = "UK"
            }
        }
    }
);

// Search
var searchResults = await client.SearchAsync(
    collectionName: "my_collection",
    vector: new[] { 0.2f, 0.1f, 0.9f, 0.7f },
    limit: 3
);

foreach (var result in searchResults)
{
    Console.WriteLine($"ID: {result.Id}, Score: {result.Score}");
    Console.WriteLine($"Payload: {result.Payload}");
}

Repository

GitHub Repository

Official .NET client: qdrant/qdrant-dotnet

Client Initialization

Basic Connection

using Qdrant.Client;

// Connect via gRPC (recommended)
var client = new QdrantClient(host: "localhost", port: 6334);

// Connect to Qdrant Cloud
var cloudClient = new QdrantClient(
    host: "your-cluster.cloud.qdrant.io",
    port: 6334,
    https: true,
    apiKey: "your-api-key"
);

With Custom Options

using Grpc.Core;
using Qdrant.Client;

var channelOptions = new GrpcChannelOptions
{
    MaxReceiveMessageSize = 100 * 1024 * 1024, // 100MB
    MaxSendMessageSize = 100 * 1024 * 1024
};

var client = new QdrantClient(
    host: "localhost",
    port: 6334,
    grpcChannelOptions: channelOptions
);

Collection Management

Create Collection

using Qdrant.Client.Grpc;

await client.CreateCollectionAsync(
    collectionName: "my_collection",
    vectorsConfig: new VectorParams
    {
        Size = 768,
        Distance = Distance.Cosine,
        OnDisk = false,
        HnswConfig = new HnswConfigDiff
        {
            M = 16,
            EfConstruct = 100,
            FullScanThreshold = 10000
        }
    },
    replicationFactor: 2,
    writeConsistencyFactor: 1
);

Create with Multiple Vectors

await client.CreateCollectionAsync(
    collectionName: "multi_vector",
    vectorsConfig: new VectorParamsMap
    {
        Map =
        {
            ["text"] = new VectorParams
            {
                Size = 768,
                Distance = Distance.Cosine
            },
            ["image"] = new VectorParams
            {
                Size = 512,
                Distance = Distance.Euclid
            }
        }
    }
);

Get Collection Info

var info = await client.GetCollectionInfoAsync("my_collection");

Console.WriteLine($"Status: {info.Status}");
Console.WriteLine($"Vectors count: {info.VectorsCount}");
Console.WriteLine($"Points count: {info.PointsCount}");
Console.WriteLine($"Segments: {info.SegmentsCount}");

List Collections

var collections = await client.ListCollectionsAsync();

foreach (var collection in collections)
{
    Console.WriteLine(collection.Name);
}

Update Collection

await client.UpdateCollectionAsync(
    collectionName: "my_collection",
    optimizersConfig: new OptimizersConfigDiff
    {
        IndexingThreshold = 50000,
        MemmapThreshold = 100000
    }
);

Delete Collection

await client.DeleteCollectionAsync("my_collection");

Point Operations

Upsert Points

using Qdrant.Client.Grpc;

await client.UpsertAsync(
    collectionName: "my_collection",
    points: new[]
    {
        new PointStruct
        {
            Id = new PointId { Num = 1 },
            Vectors = new[] { 0.05f, 0.61f, 0.76f, 0.74f },
            Payload =
            {
                ["title"] = "Document 1",
                ["category"] = "technology",
                ["views"] = 1500,
                ["tags"] = new[] { "ai", "ml", "vector-db" }
            }
        },
        new PointStruct
        {
            Id = new PointId { Num = 2 },
            Vectors = new[] { 0.19f, 0.81f, 0.75f, 0.11f },
            Payload = { ["title"] = "Document 2" }
        }
    },
    wait: true
);

Upsert with GUID

var id = Guid.NewGuid();

await client.UpsertAsync(
    collectionName: "my_collection",
    points: new[]
    {
        new PointStruct
        {
            Id = new PointId { Uuid = id.ToString() },
            Vectors = new[] { 0.1f, 0.2f, 0.3f },
            Payload = { ["key"] = "value" }
        }
    }
);

Batch Upsert

const int batchSize = 100;
const int totalPoints = 10000;

for (int i = 0; i < totalPoints; i += batchSize)
{
    var points = Enumerable.Range(i, batchSize)
        .Select(j => new PointStruct
        {
            Id = new PointId { Num = (ulong)j },
            Vectors = Enumerable.Range(0, 384).Select(_ => (float)Random.Shared.NextDouble()).ToArray(),
            Payload = { ["index"] = j }
        })
        .ToArray();

    await client.UpsertAsync(
        collectionName: "my_collection",
        points: points,
        wait: false // Don't wait for faster throughput
    );
}

Get Points

var points = await client.RetrieveAsync(
    collectionName: "my_collection",
    ids: new[] { 1ul, 2ul, 3ul },
    withPayload: true,
    withVectors: true
);

foreach (var point in points)
{
    Console.WriteLine($"ID: {point.Id}");
    Console.WriteLine($"Vectors: {string.Join(", ", point.Vectors.Vector.Data)}");
    Console.WriteLine($"Payload: {point.Payload}");
}

Delete Points

using Qdrant.Client.Grpc;

// Delete by IDs
await client.DeleteAsync(
    collectionName: "my_collection",
    ids: new[] { 1ul, 2ul, 3ul }
);

// Delete by filter
await client.DeleteAsync(
    collectionName: "my_collection",
    filter: new Filter
    {
        Must =
        {
            new Condition
            {
                Field = new FieldCondition
                {
                    Key = "category",
                    Match = new Match { Keyword = "outdated" }
                }
            }
        }
    }
);

Search Operations

var results = await client.SearchAsync(
    collectionName: "my_collection",
    vector: new[] { 0.2f, 0.1f, 0.9f, 0.7f },
    limit: 10,
    payloadSelector: true,
    vectorsSelector: false
);

foreach (var result in results)
{
    Console.WriteLine($"ID: {result.Id}, Score: {result.Score}");
    Console.WriteLine($"Payload: {result.Payload}");
}

Search with Filtering

using Qdrant.Client.Grpc;

var results = await client.SearchAsync(
    collectionName: "my_collection",
    vector: new[] { 0.2f, 0.1f, 0.9f, 0.7f },
    filter: new Filter
    {
        Must =
        {
            new Condition
            {
                Field = new FieldCondition
                {
                    Key = "category",
                    Match = new Match { Keyword = "technology" }
                }
            },
            new Condition
            {
                Field = new FieldCondition
                {
                    Key = "views",
                    Range = new Range
                    {
                        Gte = 1000,
                        Lt = 10000
                    }
                }
            }
        }
    },
    limit: 5,
    scoreThreshold: 0.7f
);

Search with Parameters

var results = await client.SearchAsync(
    collectionName: "my_collection",
    vector: new[] { 0.2f, 0.1f, 0.9f, 0.7f },
    limit: 20,
    searchParams: new SearchParams
    {
        HnswEf = 128,
        Exact = false,
        Quantization = new QuantizationSearchParams
        {
            Ignore = false,
            Rescore = true,
            Oversampling = 2.0
        }
    }
);
var searchRequests = new[]
{
    new SearchPoints
    {
        CollectionName = "my_collection",
        Vector = { 0.2f, 0.1f, 0.9f },
        Limit = 5
    },
    new SearchPoints
    {
        CollectionName = "my_collection",
        Vector = { 0.5f, 0.3f, 0.2f },
        Limit = 10
    }
};

var batchResults = await client.SearchBatchAsync(
    collectionName: "my_collection",
    searches: searchRequests
);

for (int i = 0; i < batchResults.Length; i++)
{
    Console.WriteLine($"Results for query {i}:");
    foreach (var point in batchResults[i])
    {
        Console.WriteLine($"  ID: {point.Id}, Score: {point.Score}");
    }
}

Recommendations

var results = await client.RecommendAsync(
    collectionName: "my_collection",
    positiveIds: new[] { 1ul, 2ul, 3ul },
    negativeIds: new[] { 10ul },
    limit: 10,
    payloadSelector: true
);

foreach (var result in results)
{
    Console.WriteLine($"ID: {result.Id}, Score: {result.Score}");
}

Query API (Universal)

using Qdrant.Client.Grpc;

var results = await client.QueryAsync(
    collectionName: "my_collection",
    query: new float[] { 0.1f, 0.2f, 0.3f, 0.4f },
    limit: 10,
    withPayload: true
);

foreach (var result in results)
{
    Console.WriteLine($"ID: {result.Id}, Score: {result.Score}");
}

Payload Operations

Set Payload

await client.SetPayloadAsync(
    collectionName: "my_collection",
    payload: new Dictionary<string, Value>
    {
        ["new_field"] = "value",
        ["updated_at"] = "2024-01-01"
    },
    ids: new[] { 1ul, 2ul, 3ul }
);

Overwrite Payload

await client.OverwritePayloadAsync(
    collectionName: "my_collection",
    payload: new Dictionary<string, Value>
    {
        ["only_field"] = "value"
    },
    ids: new[] { 1ul, 2ul }
);

Delete Payload Keys

await client.DeletePayloadAsync(
    collectionName: "my_collection",
    keys: new[] { "old_field", "deprecated" },
    ids: new[] { 1ul, 2ul, 3ul }
);

Clear Payload

await client.ClearPayloadAsync(
    collectionName: "my_collection",
    ids: new[] { 1ul, 2ul, 3ul }
);

Scroll (Pagination)

PointId? offset = null;
var allPoints = new List<RetrievedPoint>();

while (true)
{
    var (points, nextOffset) = await client.ScrollAsync(
        collectionName: "my_collection",
        limit: 100,
        offset: offset,
        payloadSelector: true
    );

    allPoints.AddRange(points);
    offset = nextOffset;

    if (offset == null)
        break;
}

Console.WriteLine($"Total points: {allPoints.Count}");

Payload Indexing

using Qdrant.Client.Grpc;

// Create keyword index
await client.CreatePayloadIndexAsync(
    collectionName: "my_collection",
    fieldName: "category",
    schemaType: PayloadSchemaType.Keyword
);

// Create text index
await client.CreatePayloadIndexAsync(
    collectionName: "my_collection",
    fieldName: "description",
    schemaType: PayloadSchemaType.Text,
    indexParams: new PayloadIndexParams
    {
        TextIndexParams = new TextIndexParams
        {
            Tokenizer = TokenizerType.Word,
            Lowercase = true,
            MinTokenLen = 2,
            MaxTokenLen = 20
        }
    }
);

// Delete index
await client.DeletePayloadIndexAsync(
    collectionName: "my_collection",
    fieldName: "category"
);

Count Points

var count = await client.CountAsync(
    collectionName: "my_collection",
    exact: true
);

Console.WriteLine($"Total points: {count}");

// Count with filter
var filteredCount = await client.CountAsync(
    collectionName: "my_collection",
    filter: new Filter
    {
        Must =
        {
            new Condition
            {
                Field = new FieldCondition
                {
                    Key = "category",
                    Match = new Match { Keyword = "technology" }
                }
            }
        }
    },
    exact: true
);

Snapshots

// Create snapshot
var snapshot = await client.CreateSnapshotAsync("my_collection");
Console.WriteLine($"Snapshot name: {snapshot.Name}");

// List snapshots
var snapshots = await client.ListSnapshotsAsync("my_collection");
foreach (var snap in snapshots)
{
    Console.WriteLine($"Snapshot: {snap.Name}");
}

Error Handling

using Grpc.Core;
using Qdrant.Client;

try
{
    var info = await client.GetCollectionInfoAsync("nonexistent");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
    Console.WriteLine("Collection not found");
}
catch (RpcException ex)
{
    Console.WriteLine($"Error: {ex.StatusCode} - {ex.Status.Detail}");
}
catch (Exception ex)
{
    Console.WriteLine($"Unexpected error: {ex.Message}");
}

Strongly-Typed Payloads

using System.Text.Json;

public class DocumentPayload
{
    public string Title { get; set; }
    public string Category { get; set; }
    public int Views { get; set; }
    public List<string> Tags { get; set; }
}

// Serialize to payload
var document = new DocumentPayload
{
    Title = "Document 1",
    Category = "technology",
    Views = 1500,
    Tags = new List<string> { "ai", "ml" }
};

var payload = JsonSerializer.SerializeToDocument(document);

await client.UpsertAsync(
    collectionName: "my_collection",
    points: new[]
    {
        new PointStruct
        {
            Id = 1,
            Vectors = new[] { 0.1f, 0.2f, 0.3f },
            Payload = { /* Convert from JsonDocument */ }
        }
    }
);

// Deserialize from payload
var results = await client.SearchAsync(
    collectionName: "my_collection",
    vector: new[] { 0.2f, 0.1f, 0.9f },
    limit: 10
);

foreach (var result in results)
{
    var doc = JsonSerializer.Deserialize<DocumentPayload>(
        result.Payload.ToString()
    );
    Console.WriteLine($"Title: {doc.Title}, Views: {doc.Views}");
}

LINQ Integration

using System.Linq;

var results = await client.SearchAsync(
    collectionName: "my_collection",
    vector: new[] { 0.2f, 0.1f, 0.9f },
    limit: 100
);

// Use LINQ for post-processing
var topTech = results
    .Where(r => r.Payload.ContainsKey("category") && 
                r.Payload["category"].StringValue == "technology")
    .OrderByDescending(r => r.Score)
    .Take(10)
    .Select(r => new
    {
        Id = r.Id,
        Score = r.Score,
        Title = r.Payload["title"].StringValue
    });

foreach (var item in topTech)
{
    Console.WriteLine($"{item.Title}: {item.Score}");
}

Dependency Injection

using Microsoft.Extensions.DependencyInjection;
using Qdrant.Client;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddQdrantClient(
        this IServiceCollection services,
        string host,
        int port)
    {
        services.AddSingleton<QdrantClient>(
            _ => new QdrantClient(host, port)
        );
        return services;
    }
}

// Usage in ASP.NET Core
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddQdrantClient("localhost", 6334);
    }
}

// Inject in controller
public class SearchController : ControllerBase
{
    private readonly QdrantClient _client;

    public SearchController(QdrantClient client)
    {
        _client = client;
    }

    [HttpPost("search")]
    public async Task<IActionResult> Search([FromBody] float[] vector)
    {
        var results = await _client.SearchAsync(
            collectionName: "my_collection",
            vector: vector,
            limit: 10
        );
        return Ok(results);
    }
}

Async Enumerable

using System.Collections.Generic;
using System.Runtime.CompilerServices;

public async IAsyncEnumerable<RetrievedPoint> ScrollAllPointsAsync(
    QdrantClient client,
    string collectionName,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    PointId? offset = null;

    while (!cancellationToken.IsCancellationRequested)
    {
        var (points, nextOffset) = await client.ScrollAsync(
            collectionName: collectionName,
            limit: 100,
            offset: offset
        );

        foreach (var point in points)
        {
            yield return point;
        }

        offset = nextOffset;
        if (offset == null)
            break;
    }
}

// Usage
await foreach (var point in ScrollAllPointsAsync(client, "my_collection"))
{
    Console.WriteLine($"ID: {point.Id}");
}

Best Practices

Performance tips:
  • Reuse QdrantClient instances (singleton pattern)
  • Use async/await throughout your application
  • Enable connection pooling with gRPC options
  • Use batch operations for bulk inserts
  • Set wait=false for faster upserts
Common issues:
  • Always handle RpcException for gRPC errors
  • Use CancellationToken for long-running operations
  • Dispose of resources properly (though client is reusable)
  • Ensure vector dimensions match collection config

Next Steps

Java Client

Java client documentation

gRPC API

Learn about gRPC protocol