Leaders Logo

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

Introduction

Vector stores have moved beyond being experimental components and now hold a practical position in enterprise 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 based on semantic proximity, not just literal word matching. This advancement is driven 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 subject reaches maturity because applications can organize this flow with familiar abstractions from the Microsoft ecosystem: dependency injection, Options Pattern, Microsoft.Extensions.AI, configurable providers, and dedicated contracts to isolate vector persistence. The goal is not to tie the solution to a specific database but 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 manner, three groups of data: the item key, the numeric vector produced by the embeddings model, and the metadata necessary for filtering, auditing, and response composition. In a knowledge base, for example, each document snippet can store title, source, category, permissions, model version, and embedding generation date.

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

To make this dynamic concrete, it is useful to visualize the vector store within two complementary flows connected through the persistence contract. The following diagram organizes the cycle into two tracks: knowledge ingestion, responsible for turning content into persisted vectors, and retrieval with grounded generation, responsible for converting a question into a traceable answer. Between them, a pluggable vector provider isolates the chosen infrastructure from the rest of the application.

SVG Image from the Article

In the ingestion track, the journey goes from capturing the source content, which gathers text, metadata, source URI, permissions, and tenant context, to normalization and embedding generation, followed by persistence through the application contract, which writes the key, vector, content, model version, timestamps, and reindexing policy. This persisted content only reaches a concrete backend at the point of the pluggable vector provider, in which dependency injection selects the implementation among Azure AI Search, Cosmos DB, SQL Server, or PostgreSQL with pgvector, without the business logic ceasing to depend only on IKnowledgeVectorStore.

In the retrieval track, the user's question is captured with its intent, tenant, access profile, category filters, and Top-K limit, projected in the same vector space as ingestion and submitted to semantic search, which applies authorization filters before or during neighbor approximation. The most relevant snippets compose a traceable context, with source, score, and provenance, that supports the generation of the answer based exclusively on the recovered evidence. A transversal quality gate, supported by a fixed evaluation set, measures the impact of model, chunking, or index changes on this entire journey.

Domain modeling in .NET 10

With this overview established, the first concrete step is to model the vector record as an explicit part of the search domain, starting with the capture stage described in the ingestion section of the diagram. The application should not treat embeddings as an invisible detail, because decisions such as dimensionality, model version, and update policy directly affect the quality of the ranking.

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 embedding model changes, the solution can identify which records need to be recalculated, without relying on implicit conventions in the backend.

Vector Persistence Contract

With the vector record modeled, the next step in the flow is to define how it will be written and queried. Even when the project uses a specific provider, it is worth keeping an application contract to avoid premature coupling. The contract below separates core operations: writing 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. Switching providers becomes an infrastructure decision, not a rewrite of the business flow.

Ingestion with Microsoft.Extensions.AI

With the persistence contract defined, the next step is to populate it: generating the vectors. In .NET 10, this generation of embeddings can be organized by Microsoft.Extensions.AI, keeping the application decoupled from the concrete service that calculates the vectors. This is important because costs, latency, quality, and privacy can vary greatly 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 perspective, this service should be close to the ingestion pipeline, not mixed with the presentation layer. Thus, text normalization, chunk segmentation, metadata enrichment, and reprocessing can evolve without affecting the user experience.

Configuration with Options Pattern

For ingestion and contract to operate predictably, both depend on well-defined operational parameters: collection name, embedding dimension, distance metric, default results limit, and active provider. The Options Pattern makes these parameters explicit and validatable at 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 on the first actual backend access. It also makes it easier to use different providers for development, staging, and production environments.

Pluggable Providers with DI

With validated parameters, this same active provider then guides the selection of the backend, which is where the diagram places the pluggable vector provider. With dependency injection, this selection can be encapsulated in the service registration. The application code continues to use IKnowledgeVectorStore, while the 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 scattered throughout the solution. When an organization decides to migrate from one managed provider to another, the impact is concentrated on infrastructure and contract tests.

Semantic search and context retrieval

Once the ingestion phase is completed, the same contract is used for the retrieval phase of the diagram. Semantic querying repeats the ingestion logic: generate the embedding for the question, search for nearby vectors, and return evidence ranked by score. In large databases, approximate nearest neighbor structures become relevant because they reduce cost and latency with a controlled loss of recall (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

Once this traceable context has been retrieved, the process reaches the final stage of the retrieval pipeline: generating the answer. RAG should not be understood merely as "fetching documents and calling an LLM." The real value lies in building an answer limited to the recovered evidence, with preserved sources and the possibility of audit. Recent surveys reinforce that the quality of the retrieval pipeline, chunking, and evaluation is decisive for RAG systems in production (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 documental support and makes the solution more suitable for domains such as legal, health, technical support, engineering, and internal policies.

Governance and Security

If the workflow so far ensures well-founded answers, it also expands the data surface that needs to be protected. Embeddings should be treated as sensitive data when derived from sensitive content. They can preserve statistical traces of the original text and take part in inference flows, extraction, or leakage 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 model version, generation date, document source, and reindexing policy. Without these fields, the team loses the ability to explain why an answer changed after a model update or corpus reprocessing.

Observability and Evaluation

Just as governance protects the flow, observability makes it measurable. Vector stores in production should expose metrics beyond availability. Ingestion latency, search latency, score distribution, rate of documents without embedding, errors by provider, cost per thousand embeddings, and perceived quality by query category are relevant operational signals. This data helps differentiate infrastructure issues, corpus degradation, and low alignment of the embedding model to the domain.

This measurement is realized at the quality gate that runs throughout the entire diagram. A simple evaluation can start with a fixed set of real questions, expected answers, and reference documents. With every change of model, chunking, or index, this set measures whether the change improved retrieval or just unpredictably shifted the ranking.

Conclusion

Persisting and retrieving vector context in .NET 10 is an architectural decision, not just a database choice. Microsoft abstractions help organize the complete 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, ready for enterprise demands in 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, p. 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