{"id":3525,"date":"2026-02-26T18:26:00","date_gmt":"2026-02-26T18:26:00","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2026\/02\/26\/vector-data-in-net-building-blocks-for-ai-part-2\/"},"modified":"2026-02-26T18:26:00","modified_gmt":"2026-02-26T18:26:00","slug":"vector-data-in-net-building-blocks-for-ai-part-2","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2026\/02\/26\/vector-data-in-net-building-blocks-for-ai-part-2\/","title":{"rendered":"Vector Data in .NET \u2013 Building Blocks for AI Part 2"},"content":{"rendered":"<p>Welcome back to the building blocks for AI in .NET series! In <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet-ai-essentials-the-core-building-blocks-explained\/\">part one<\/a>, we explored Microsoft Extensions for AI (MEAI) and how it provides a unified interface for working with large language models. Today, we\u2019re diving into the second building block: <strong>Microsoft.Extensions.VectorData<\/strong>.<\/p>\n<p>In the first post, we learned how to ask questions and even share some content for context with an LLM. Most applications, however, require more than just a simple question or small markdown file for context. You may want the LLM to have access to all of your product manuals to help troubleshoot customer issues, or provide your employee handbook for an HR chatbot.<\/p>\n<p>Another feature that is common in intelligent apps is semantic search. A semantic search uses the meaning of a query, not just the words or letters, to conduct the search. It does this by converting text into <em>embeddings<\/em> which are numerical representations of the semantic meaning of text, and vectors that provide insights into how they are related.<\/p>\n<p>Imagine you have a simple database with just three entries:<\/p>\n<ol>\n<li>Hall pass<\/li>\n<li>Mountain pass<\/li>\n<li>Pass (verb)<\/li>\n<\/ol>\n<p>A traditional approach to finding the answer to queries like \u201cHow do I get over the pass?\u201d or \u201cWhere do I pick up a pass?\u201d breaks the query down into parts to search for. The word \u201cpass\u201d appears in all three database items, so I receive all three entries back despite the different contexts of my queries. Here is a simplified visualization:<\/p>\n<pre><code class=\"language-text\">\"How do I get over the pass?\" \r\nHow | do | I | get | over | the | pass \r\nPass - matches all three entries \r\n\r\n\"Where do I pick up my pass?\"\r\nWhere | do | I | pick | up | my | pass \r\nPass - matches all three entries <\/code><\/pre>\n<p>Now let\u2019s assume I use an embedding to encode the semantic meaning of the word. The database has already been encoded, but I need to create embeddings from my query. This time, however, the embeddings provide me with a semantic result, not a text-based one. The semantic approach looks like this:<\/p>\n<pre><code class=\"language-text\">\"How do I get over the pass?\" \r\n0 | 5 | etc. | 2 \r\n2 - matches the 2nd entry, \"Mountain pass\" \r\n\r\n\"Where do I pick up my pass?\" \r\n6 | 9 | etc. | 1   \r\n1 - matches the 1st entry, \"Hall pass\" <\/code><\/pre>\n<p>A special embeddings model is used to create the embeddings and is trained to understand the semantic meaning of words through context such as the related terms that appear before and after it. Instead of generating embeddings every time the application runs, it makes much more sense to store them in a database. This has the added bonus of being able to use the database\u2019s ability to query and return results, rather than coding the logic yourself or doing it in a suboptimal way.<\/p>\n<p><em>Vector databases<\/em> are designed specifically to store vectors and embeddings. Qdrant, Redis, SQL Server and Cosmos DB are examples of services and products that support storing vector data. Just like MEAI unified LLM access, the vector data extensions provide a common abstraction for working with vector stores.<\/p>\n<h2>Why vectors matter for AI applications<\/h2>\n<p>Before we jump into the code, let\u2019s look a little more closely at vectors. When you ask an LLM a question about your company\u2019s documentation, the model doesn\u2019t magically know your content. Instead, your application typically:<\/p>\n<ol>\n<li><strong>Converts your documents into embeddings<\/strong> \u2013 numerical representations that capture semantic meaning<\/li>\n<li><strong>Stores those embeddings in a vector database<\/strong> along with the original content<\/li>\n<li><strong>Converts the user\u2019s query into an embedding<\/strong> using the same model<\/li>\n<li><strong>Performs a similarity search<\/strong> to find the most relevant documents<\/li>\n<li><strong>Passes the relevant context to the LLM<\/strong> along with the user\u2019s query<\/li>\n<\/ol>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2026\/02\/rag-diagram-scaled.webp\"><img data-opt-id=867296469  fetchpriority=\"high\" decoding=\"async\" class=\"alignnone size-large wp-image-59610\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2026\/02\/rag-diagram-759x1024.webp\" alt=\"rag diagram image\" width=\"759\" height=\"1024\" \/><\/a><\/p>\n<p>This pattern, known as RAG (Retrieval-Augmented Generation), allows models to provide accurate, grounded responses based on your specific data. The challenge? Every vector database has its own SDK, data structures, and query patterns. That\u2019s where Microsoft.Extensions.VectorData comes in.<\/p>\n<h2>One interface, many vector stores<\/h2>\n<p>The Microsoft Extensions for Vector Data library provides abstractions that work across different vector database providers. Here\u2019s what that looks like in practice. First, let\u2019s look at using an example vector database, Qdrant, directly and without the abstractions:<\/p>\n<pre><code class=\"language-csharp\">var qdrantClient = new QdrantClient(\"localhost\", 6334);\r\n\r\nvar collection = \"my_collection\";\r\nawait qdrantClient.CreateCollectionAsync(collection, new VectorParams\r\n{\r\n    Size = 1536,\r\n    Distance = Distance.Cosine\r\n});\r\n\r\nvar points = new List&lt;PointStruct&gt;\r\n{\r\n    new()\r\n    {\r\n        Id = new PointId { Uuid = Guid.NewGuid().ToString() },\r\n        Vectors = embedding,\r\n        Payload =\r\n        {\r\n            [\"text\"] = \"Sample document text\",\r\n            [\"category\"] = \"documentation\"\r\n        }\r\n    }\r\n};\r\n\r\nawait qdrantClient.UpsertAsync(collection, points);\r\n\r\nvar searchResults = await qdrantClient.SearchAsync(collection, queryEmbedding, limit: 5);<\/code><\/pre>\n<p>Now let\u2019s see the same thing using the universal abstractions:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Configure embedding generation once on the vector store\r\nvar embeddingGenerator = new OpenAIClient(apiKey)\r\n    .GetEmbeddingClient(\"text-embedding-3-small\")\r\n    .AsIEmbeddingGenerator();\r\n\r\nvar vectorStore = new QdrantVectorStore(\r\n    new QdrantClient(\"localhost\"),\r\n    ownsClient: true,\r\n    new QdrantVectorStoreOptions { EmbeddingGenerator = embeddingGenerator });\r\n\r\nvar collection = vectorStore.GetCollection&lt;string, DocumentRecord&gt;(\"my_collection\");\r\nawait collection.EnsureCollectionExistsAsync();\r\n\r\nvar record = new DocumentRecord\r\n{\r\n    Key = Guid.NewGuid().ToString(),\r\n    Text = \"Sample document text\",\r\n    Category = \"documentation\"\r\n};\r\n\r\nawait collection.UpsertAsync(record);\r\n\r\nvar searchResults = collection.SearchAsync(\"find documents about sample topics\", top: 5);<\/code><\/pre>\n<p>The second example works with any supported vector store by simply changing the <code>VectorStore<\/code> implementation. Your business logic stays the same.<\/p>\n<h2>Defining your data model<\/h2>\n<p>The vector data abstractions use attributes to map your C# classes to vector database schemas. Here\u2019s a practical example for a document store:<\/p>\n<pre><code class=\"language-csharp\">public class DocumentRecord\r\n{\r\n    [VectorStoreKey]\r\n    public string Key { get; set; }\r\n\r\n    [VectorStoreData]\r\n    public string Text { get; set; }\r\n\r\n    [VectorStoreData(IsIndexed = true)]\r\n    public string Category { get; set; }\r\n\r\n    [VectorStoreData(IsIndexed = true)]\r\n    public DateTimeOffset Timestamp { get; set; }\r\n\r\n    \/\/ The vector is automatically generated from Text when an\r\n    \/\/ IEmbeddingGenerator is configured on the collection or vector store\r\n    [VectorStoreVector(1536, DistanceFunction.CosineSimilarity)]\r\n    public string Embedding =&gt; this.Text;\r\n}<\/code><\/pre>\n<p>The attributes tell the library:<\/p>\n<ul>\n<li><strong><code>VectorStoreKey<\/code><\/strong> \u2013 This property uniquely identifies each record<\/li>\n<li><strong><code>VectorStoreData<\/code><\/strong> \u2013 These are metadata fields you can filter and retrieve<\/li>\n<li><strong><code>VectorStoreVector<\/code><\/strong> \u2013 This is the embedding vector with its dimensions and distance function<\/li>\n<\/ul>\n<h2>Working with collections<\/h2>\n<p>Once you\u2019ve defined your data model, working with collections is straightforward. The library provides a consistent interface regardless of your underlying vector store:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Get or create a collection\r\nvar collection = vectorStore.GetCollection&lt;string, DocumentRecord&gt;(\"documents\");\r\n\r\n\/\/ Check if the collection exists\r\nbool exists = await collection.CollectionExistsAsync();\r\nawait collection.EnsureCollectionExistsAsync();\r\n\r\n\/\/ Insert or update records\r\nawait collection.UpsertAsync(documentRecord);\r\n\r\n\/\/ Batch operations are supported\r\nawait collection.UpsertBatchAsync(documentRecords);\r\n\r\n\/\/ Retrieve by key\r\nvar record = await collection.GetAsync(\"some-key\");\r\n\r\n\/\/ Delete records\r\nawait collection.DeleteAsync(\"some-key\");\r\nawait collection.DeleteBatchAsync([\"key1\", \"key2\", \"key3\"]);<\/code><\/pre>\n<h2>Semantic search<\/h2>\n<p>The real power comes when you perform semantic searches using the <code>SearchAsync<\/code> method. When an <code>IEmbeddingGenerator<\/code> is configured on the vector store or collection, simply pass your query text and embeddings are generated automatically:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Embeddings are generated automatically when IEmbeddingGenerator is configured\r\nawait foreach (var result in collection.SearchAsync(\"What is semantic search?\", top: 5))\r\n{\r\n    Console.WriteLine($\"Score: {result.Score}, Text: {result.Record.Text}\");\r\n}<\/code><\/pre>\n<p>If you already have a pre-computed <code>ReadOnlyMemory&lt;float&gt;<\/code> embedding\u2014for example, when batching embeddings yourself\u2014you can pass it directly instead:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Pass a pre-computed embedding vector directly\r\nReadOnlyMemory&lt;float&gt; precomputedEmbedding = \/* your embedding *\/;\r\nawait foreach (var result in collection.SearchAsync(precomputedEmbedding, top: 5))\r\n{\r\n    Console.WriteLine($\"Score: {result.Score}, Text: {result.Record.Text}\");\r\n}<\/code><\/pre>\n<h2>Filtering results<\/h2>\n<p>You can combine vector similarity with metadata filtering to narrow down results:<\/p>\n<pre><code class=\"language-csharp\">var searchOptions = new VectorSearchOptions&lt;DocumentRecord&gt;\r\n{\r\n    Filter = r =&gt; r.Category == \"documentation\" &amp;&amp;\r\n                  r.Timestamp &gt; DateTimeOffset.UtcNow.AddDays(-30)\r\n};\r\n\r\nvar results = collection.SearchAsync(\"find relevant documentation\", top: 10, searchOptions);<\/code><\/pre>\n<p>Filters use standard LINQ expressions. The supported operations include:<\/p>\n<ul>\n<li>Equality comparisons (<code>==<\/code>, <code>!=<\/code>)<\/li>\n<li>Range queries (<code>&gt;<\/code>, <code>&lt;<\/code>, <code>&gt;=<\/code>, <code>&lt;=<\/code>)<\/li>\n<li>Logical operators (<code>&amp;&amp;<\/code>, <code>||<\/code>)<\/li>\n<li>Collection membership (<code>.Contains()<\/code>)<\/li>\n<\/ul>\n<h2>Integrating with embeddings<\/h2>\n<p>The recommended approach is to configure an <code>IEmbeddingGenerator<\/code> on the vector store or collection. Embeddings are then generated automatically during both upsert and search\u2014no manual preprocessing required:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Configure an embedding generator on the vector store\r\nvar embeddingGenerator = new OpenAIClient(apiKey)\r\n    .GetEmbeddingClient(\"text-embedding-3-small\")\r\n    .AsIEmbeddingGenerator();\r\n\r\nvar vectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = embeddingGenerator });\r\nvar collection = vectorStore.GetCollection&lt;string, DocumentRecord&gt;(\"documents\");\r\nawait collection.EnsureCollectionExistsAsync();\r\n\r\n\/\/ Embeddings are generated automatically on upsert\r\nvar record = new DocumentRecord\r\n{\r\n    Key = Guid.NewGuid().ToString(),\r\n    Text = \"Sample text to store\"\r\n};\r\nawait collection.UpsertAsync(record);\r\n\r\n\/\/ Embeddings are also generated automatically on search\r\nawait foreach (var result in collection.SearchAsync(\"find similar text\", top: 5))\r\n{\r\n    Console.WriteLine($\"Score: {result.Score}, Text: {result.Record.Text}\");\r\n}<\/code><\/pre>\n<h2>Implementing RAG patterns<\/h2>\n<p>Bringing it all together, here\u2019s a simplified RAG implementation using both Microsoft.Extensions.AI and Microsoft.Extensions.VectorData:<\/p>\n<pre><code class=\"language-csharp\">public async Task&lt;string&gt; AskQuestionAsync(string question)\r\n{\r\n    \/\/ Find relevant documents - embeddings are generated automatically\r\n    var contextParts = new List&lt;string&gt;();\r\n    await foreach (var result in collection.SearchAsync(question, top: 3))\r\n    {\r\n        contextParts.Add(result.Record.Text);\r\n    }\r\n\r\n    \/\/ Build context from results\r\n    var context = string.Join(\"nn\", contextParts);\r\n\r\n    \/\/ Create prompt with context\r\n    var messages = new List&lt;ChatMessage&gt;\r\n    {\r\n        new(ChatRole.System, \r\n            \"Answer questions based on the provided context. If the context doesn't contain relevant information, say so.\"),\r\n        new(ChatRole.User, \r\n            $\"Context:n{context}nnQuestion: {question}\")\r\n    };\r\n\r\n    \/\/ Get response from LLM\r\n    var response = await chatClient.GetResponseAsync(messages);\r\n    return response.Message.Text;\r\n}<\/code><\/pre>\n<h2>Supported vector stores<\/h2>\n<p>Microsoft.Extensions.VectorData works with a wide range of vector databases through official connectors:<\/p>\n<ul>\n<li><strong>Azure AI Search<\/strong> \u2013 <code>Microsoft.Extensions.VectorData.AzureAISearch<\/code><\/li>\n<li><strong>Qdrant<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.Qdrant<\/code><\/li>\n<li><strong>Redis<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.Redis<\/code><\/li>\n<li><strong>PostgreSQL<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.Postgres<\/code><\/li>\n<li><strong>Azure Cosmos DB (NoSQL)<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.AzureCosmosDBNoSQL<\/code><\/li>\n<li><strong>SQL Server<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.SqlServer<\/code><\/li>\n<li><strong>SQLite<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.Sqlite<\/code><\/li>\n<li><strong>In-Memory<\/strong> \u2013 <code>Microsoft.SemanticKernel.Connectors.InMemory<\/code> (great for testing and development)<\/li>\n<\/ul>\n<p>For the full list of supported connectors\u2014including Elasticsearch, MongoDB, Weaviate, Pinecone, and more\u2014see the <a href=\"https:\/\/learn.microsoft.com\/semantic-kernel\/concepts\/vector-store-connectors\/out-of-the-box-connectors\/?pivots=programming-language-csharp\">out-of-the-box connectors documentation<\/a>.<\/p>\n<h2>Why separate from the core AI extensions?<\/h2>\n<p>You might wonder why vector data is in a separate library from the core Microsoft.Extensions.AI package. The answer is simple: not every intelligent application needs vector storage. Many scenarios \u2013 like chatbots, content generation, or classification tasks \u2013 work perfectly fine with just the LLM abstractions. By keeping vector data separate, the core library remains lightweight and focused.<\/p>\n<p>When you do need vectors for semantic search, RAG, or long-term memory, you can add the vector data package and immediately benefit from the same consistent patterns you\u2019re already using with MEAI.<\/p>\n<h2>Summary<\/h2>\n<p>Microsoft.Extensions.VectorData brings the same benefits to vector databases that Microsoft.Extensions.AI brings to LLMs: a unified, provider-agnostic interface that makes your code portable and your architecture flexible. Whether you\u2019re implementing RAG patterns, building semantic search, or creating long-term memory for AI agents, these abstractions let you focus on your application logic instead of database-specific SDKs.<\/p>\n<p>In the next post, we\u2019ll explore the Microsoft Agent Framework and see how these building blocks come together to create sophisticated agentic workflows. Until then, here are some resources to help you get started with vector data in .NET:<\/p>\n<ul>\n<li>Learn by code\n<ul>\n<li><a href=\"https:\/\/github.com\/dotnet\/ai-samples\">AI samples repository<\/a><\/li>\n<\/ul>\n<\/li>\n<li>Learn by following tutorials\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/ai\/\">.NET AI documentation<\/a><\/li>\n<\/ul>\n<\/li>\n<li>Learn by watching videos\n<ul>\n<li><a href=\"https:\/\/youtu.be\/qcp6ufe_XYo\">AI building blocks<\/a><\/li>\n<li><a href=\"https:\/\/youtu.be\/N0DzWMkEnzk\">Building intelligent apps with .NET<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Happy coding!<\/p>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/vector-data-in-dotnet-building-blocks-for-ai-part-2\/\">Vector Data in .NET \u2013 Building Blocks for AI Part 2<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\">.NET Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Welcome back to the building blocks for AI in .NET series! In part one, we explored Microsoft Extensions for AI [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3526,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[7],"tags":[],"class_list":["post-3525","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3525","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=3525"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3525\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media\/3526"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=3525"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=3525"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=3525"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}