Leaders Logo

Vector Store in .NET 10: Context Persistence and Retrieval with Microsoft Abstractions

Introduction

Vector stores have shifted from being experimental components to occupying a practical role in corporate architectures that combine semantic search, recommendation, agents, and Retrieval-Augmented Generation (RAG). The central idea is to persist vector representations of content, queries, and metadata in order to retrieve evidence by semantic proximity, not just literal word matching. This advancement is supported by sentence embeddings, dense retrieval, and generative models capable of consuming retrieved context (REIMERS; GUREVYCH, 2019) (KARPUKHIN et al., 2020) (LEWIS et al., 2020).

In .NET 10, the topic reaches maturity because the application can organize this flow with familiar abstractions from the Microsoft ecosystem: dependency injection, Options Pattern, Microsoft.Extensions.AI, configurable providers, and custom contracts to isolate vector persistence. The goal is not to tie the solution to a specific database, but rather to create a clear boundary between domain, embedding generation, storage, and context retrieval.

The role of a vector store

A vector store maintains, in an indexable way, three groups of data: the item key, the numerical vector produced by the embeddings model, and the necessary metadata for filtering, auditing, and response composition. In a knowledge base, for example, each document fragment can store the title, source, category, permissions, model version, and the embedding generation date.

This structure allows querying the nearest neighbors of a question, retrieving likely answer fragments, and building an evidence-based prompt. In RAG scenarios, the final quality depends less on “calling a larger model” and more on retrieving correct, ordered, and traceable context (LEWIS et al., 2020).

Domain modeling in .NET 10

The first step is to model the vector record as an explicit part of the search domain. The application should not treat embeddings as an invisible detail, since decisions like dimension, model version, and update policy directly affect ranking quality.

public sealed class KnowledgeChunk
{
    public required string Id { get; init; }
    public required string DocumentId { get; init; }
    public required string Title { get; init; }
    public required string Content { get; init; }
    public required string SourceUri { get; init; }
    public required string EmbeddingModel { get; init; }
    public required ReadOnlyMemory<float> Vector { get; init; }
    public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
}

This modeling favors maintenance and reprocessing. When the embeddings model changes, the solution is able to identify which records need to be recalculated, without relying on implicit conventions in the backend.

Vector persistence contract

Even when the project uses a specific provider, it is worth maintaining an application contract to avoid premature coupling. The contract below separates core operations: saving vectorized content, retrieving by key, and searching for semantically close neighbors.

public sealed record VectorSearchResult(
    string Id,
    string Content,
    string SourceUri,
    double Score);

public interface IKnowledgeVectorStore
{
    Task UpsertAsync(KnowledgeChunk chunk, CancellationToken cancellationToken = default);

    Task<KnowledgeChunk?> GetAsync(string id, CancellationToken cancellationToken = default);

    Task<IReadOnlyList<VectorSearchResult>> SearchAsync(
        ReadOnlyMemory<float> queryVector,
        int top,
        CancellationToken cancellationToken = default);
}

This design is compatible with different backends, such as Azure AI Search, Azure Cosmos DB, SQL Server with vector support, PostgreSQL with pgvector, Qdrant, or Elasticsearch. Changing the provider becomes an infrastructure decision, not a rewrite of the business flow.

Ingestion with Microsoft.Extensions.AI

In .NET 10, embedding generation can be organized by Microsoft.Extensions.AI, keeping the application decoupled from the concrete service that computes vectors. This point is important because costs, latency, quality, and privacy vary widely between local models, managed services, and external providers.

using Microsoft.Extensions.AI;

public sealed class KnowledgeIngestionService(
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
    IKnowledgeVectorStore vectorStore)
{
    public async Task<string> SaveAsync(
        string documentId,
        string title,
        string content,
        string sourceUri,
        CancellationToken cancellationToken = default)
    {
        var embedding = await embeddingGenerator.GenerateVectorAsync(content, cancellationToken: cancellationToken);

        var chunk = new KnowledgeChunk
        {
            Id = $"{documentId}:{Guid.CreateVersion7()}",
            DocumentId = documentId,
            Title = title,
            Content = content,
            SourceUri = sourceUri,
            EmbeddingModel = "configured-embedding-model",
            Vector = embedding
        };

        await vectorStore.UpsertAsync(chunk, cancellationToken);
        return chunk.Id;
    }
}

From an architectural point of view, this service should be close to the ingestion pipeline, not mixed with the presentation layer. This way, text normalization, chunk segmentation, metadata enrichment, and reprocessing can evolve without affecting the user experience.

Configuration with Options Pattern

Vector stores require operational parameters: collection name, embedding dimension, distance metric, default result limit, and active provider. The Options Pattern makes these parameters explicit and validatable during application startup.

public sealed class VectorStoreOptions
{
    public const string SectionName = "VectorStore";

    public required string Provider { get; init; }
    public required string CollectionName { get; init; }
    public int Dimensions { get; init; } = 1536;
    public int DefaultTopK { get; init; } = 5;
    public string DistanceMetric { get; init; } = "cosine";
}

builder.Services
    .AddOptions<VectorStoreOptions>()
    .BindConfiguration(VectorStoreOptions.SectionName)
    .Validate(options => options.Dimensions > 0, "Dimensions must be positive.")
    .Validate(options => options.DefaultTopK is > 0 and <= 50, "DefaultTopK must be between 1 and 50.")
    .ValidateOnStart();

This approach prevents the application from discovering invalid configurations only at the first real access to the backend. It also makes it easier to use different providers for development, staging, and production environments.

Pluggable Providers with DI

With dependency injection, backend selection can be encapsulated in the service registration. The application code continues to use IKnowledgeVectorStore, while composition chooses the appropriate implementation.

builder.Services.AddSingleton<IKnowledgeVectorStore>(serviceProvider =>
{
    var options = serviceProvider
        .GetRequiredService<IOptions<VectorStoreOptions>>()
        .Value;

    return options.Provider.ToLowerInvariant() switch
    {
        "azure-ai-search" => ActivatorUtilities.CreateInstance<AzureAiSearchVectorStore>(serviceProvider),
        "cosmosdb" => ActivatorUtilities.CreateInstance<CosmosDbVectorStore>(serviceProvider),
        "qdrant" => ActivatorUtilities.CreateInstance<QdrantVectorStore>(serviceProvider),
        _ => throw new InvalidOperationException($"Unsupported vector store provider: {options.Provider}")
    };
});

This pattern reduces the risk of dependencies being scattered throughout the solution. When an organization decides to migrate from one managed provider to another, the impact remains concentrated in the infrastructure and contract tests.

Semantic Search and Context Retrieval

Semantic querying repeats the ingestion logic: generate the question embedding, search for nearby vectors, and return evidence ordered by score. In large databases, approximate nearest neighbors structures become relevant because they reduce cost and latency with controlled recall loss (MALKOV; YASHUNIN, 2020).

public sealed class SemanticSearchService(
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
    IKnowledgeVectorStore vectorStore,
    IOptions<VectorStoreOptions> options)
{
    public async Task<IReadOnlyList<VectorSearchResult>> SearchAsync(
        string question,
        CancellationToken cancellationToken = default)
    {
        var embedding = await embeddingGenerator.GenerateVectorAsync(question, cancellationToken: cancellationToken);

        return await vectorStore.SearchAsync(
            embedding,
            options.Value.DefaultTopK,
            cancellationToken);
    }
}

When the application requires strict filters, such as tenant, access profile, category, or document validity, these filters must be applied before or during the vector search. Without this, the ranking may be semantically good but operationally incorrect.

RAG with traceable context

RAG should not be understood merely as “fetching documents and calling an LLM.” The real value lies in building an answer limited to the retrieved evidence, with preserved sources and auditability. Recent surveys reinforce that the quality of the retrieval pipeline, chunking, and evaluation is decisive for production RAG systems (GAO et al., 2024).

public sealed class GroundedAnswerService(
    SemanticSearchService searchService,
    IChatClient chatClient)
{
    public async Task<string> AnswerAsync(string question, CancellationToken cancellationToken = default)
    {
        var matches = await searchService.SearchAsync(question, cancellationToken);

        var context = string.Join(
            "\n\n",
            matches.Select(match => $"Source: {match.SourceUri}\nScore: {match.Score:F4}\n{match.Content}"));

        var response = await chatClient.GetResponseAsync(
            $"""
            Answer using only the context below. If the context is insufficient, say so.

            Question:
            {question}

            Context:
            {context}
            """,
            cancellationToken: cancellationToken);

        return response.Text;
    }
}

This discipline reduces answers without documentary support and makes the solution more suitable for domains such as legal, healthcare, technical support, engineering, and internal policies.

Governance and security

Embeddings should be treated as sensitive data when derived from sensitive content. They may preserve statistical traces of the original text and participate in inference, extraction, or leakage flows when access controls are weak (CARLINI et al., 2021). Therefore, permissions, retention, masking, tenant segregation, and audit trails need to accompany vector persistence from the initial design.

It is also recommended to store the model version, generation date, document source, and reindexing policy. Without these fields, the team loses the ability to explain why a response changed after a model update or corpus reprocessing.

Observability and evaluation

Vector stores in production should expose metrics beyond availability. Ingestion latency, search latency, score distribution, rate of documents without embeddings, error by provider, cost per thousand embeddings, and perceived quality by query category are relevant operational signals. This data helps distinguish between infrastructure issues, corpus degradation, and low model-domain adherence for embeddings.

A simple evaluation can start with a fixed set of real questions, expected answers, and reference documents. With every change in model, chunking, or index, this set measures whether the change improved retrieval or merely shifted the ranking unpredictably.

Conclusion

Persisting and retrieving vector context in .NET 10 is an architectural decision, not just a database choice. Microsoft abstractions help organize the full cycle: ingestion, configuration, pluggable provider, semantic search, grounded generation, and observability. When these points are treated as clear contracts, the application gains the freedom to switch backends, evolve embedding models, and operate RAG with traceability. The result is a more modular, testable platform that is prepared for corporate demands for semantic search and contextual intelligence.

References

  • REIMERS, Nils; GUREVYCH, Iryna. Sentence-BERT: sentence embeddings using Siamese BERT-networks. In: PROCEEDINGS OF THE 2019 CONFERENCE ON EMPIRICAL METHODS IN NATURAL LANGUAGE PROCESSING. Hong Kong: Association for Computational Linguistics, 2019. pp. 3982-3992. reference.Description
  • KARPUKHIN, Vladimir et al. Dense passage retrieval for open-domain question answering. In: PROCEEDINGS OF THE 2020 CONFERENCE ON EMPIRICAL METHODS IN NATURAL LANGUAGE PROCESSING. Online: Association for Computational Linguistics, 2020. pp. 6769-6781. reference.Description
  • LEWIS, Patrick et al. Retrieval-augmented generation for knowledge-intensive NLP tasks. In: ADVANCES IN NEURAL INFORMATION PROCESSING SYSTEMS. Red Hook: Curran Associates, 2020. v. 33, pp. 9459-9474. reference.Description
  • MALKOV, Yu A.; YASHUNIN, Dmitry A. Efficient and robust approximate nearest neighbor search using hierarchical navigable small world graphs. IEEE Transactions on Pattern Analysis and Machine Intelligence, New York, v. 42, n. 4, pp. 824-836, 2020. reference.Description
  • GAO, Yunfan et al. Retrieval-augmented generation for large language models: a survey. arXiv preprint arXiv:2312.10997, 2024. reference.Description
  • CARLINI, Nicholas et al. Extracting training data from large language models. In: 30TH USENIX SECURITY SYMPOSIUM. Berkeley: USENIX Association, 2021. pp. 2633-2650. reference.Description
About the author