--- name: azure-search-documents-dotnet description: | Azure AI Search SDK for .NET (Azure.Search.Documents). Use for building search applications with full-text, vector, semantic, and hybrid search. Covers SearchClient (queries, document CRUD), SearchIndexClient (index management), and SearchIndexerClient (indexers, skillsets). Triggers: "Azure Search .NET", "SearchClient", "SearchIndexClient", "vector search C#", "semantic search .NET", "hybrid search", "Azure.Search.Documents". package: Azure.Search.Documents --- # Azure.Search.Documents (.NET) Build search applications with full-text, vector, semantic, and hybrid search capabilities. ## Installation ```bash dotnet add package Azure.Search.Documents dotnet add package Azure.Identity ``` **Current Versions**: Stable v11.7.0, Preview v11.8.0-beta.1 ## Environment Variables ```bash SEARCH_ENDPOINT=https://.search.windows.net SEARCH_INDEX_NAME= # For API key auth (not recommended for production) SEARCH_API_KEY= ``` ## Authentication **DefaultAzureCredential (preferred)**: ```csharp using Azure.Identity; using Azure.Search.Documents; var credential = new DefaultAzureCredential(); var client = new SearchClient( new Uri(Environment.GetEnvironmentVariable("SEARCH_ENDPOINT")), Environment.GetEnvironmentVariable("SEARCH_INDEX_NAME"), credential); ``` **API Key**: ```csharp using Azure; using Azure.Search.Documents; var credential = new AzureKeyCredential( Environment.GetEnvironmentVariable("SEARCH_API_KEY")); var client = new SearchClient( new Uri(Environment.GetEnvironmentVariable("SEARCH_ENDPOINT")), Environment.GetEnvironmentVariable("SEARCH_INDEX_NAME"), credential); ``` ## Client Selection | Client | Purpose | |--------|---------| | `SearchClient` | Query indexes, upload/update/delete documents | | `SearchIndexClient` | Create/manage indexes, synonym maps | | `SearchIndexerClient` | Manage indexers, skillsets, data sources | ## Index Creation ### Using FieldBuilder (Recommended) ```csharp using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; // Define model with attributes public class Hotel { [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.EnLucene)] public string Description { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } [VectorSearchField(VectorSearchDimensions = 1536, VectorSearchProfileName = "vector-profile")] public ReadOnlyMemory? DescriptionVector { get; set; } } // Create index var indexClient = new SearchIndexClient(endpoint, credential); var fieldBuilder = new FieldBuilder(); var fields = fieldBuilder.Build(typeof(Hotel)); var index = new SearchIndex("hotels") { Fields = fields, VectorSearch = new VectorSearch { Profiles = { new VectorSearchProfile("vector-profile", "hnsw-algo") }, Algorithms = { new HnswAlgorithmConfiguration("hnsw-algo") } } }; await indexClient.CreateOrUpdateIndexAsync(index); ``` ### Manual Field Definition ```csharp var index = new SearchIndex("hotels") { Fields = { new SimpleField("hotelId", SearchFieldDataType.String) { IsKey = true, IsFilterable = true }, new SearchableField("hotelName") { IsSortable = true }, new SearchableField("description") { AnalyzerName = LexicalAnalyzerName.EnLucene }, new SimpleField("rating", SearchFieldDataType.Double) { IsFilterable = true, IsSortable = true }, new SearchField("descriptionVector", SearchFieldDataType.Collection(SearchFieldDataType.Single)) { VectorSearchDimensions = 1536, VectorSearchProfileName = "vector-profile" } } }; ``` ## Document Operations ```csharp var searchClient = new SearchClient(endpoint, indexName, credential); // Upload (add new) var hotels = new[] { new Hotel { HotelId = "1", HotelName = "Hotel A" } }; await searchClient.UploadDocumentsAsync(hotels); // Merge (update existing) await searchClient.MergeDocumentsAsync(hotels); // Merge or Upload (upsert) await searchClient.MergeOrUploadDocumentsAsync(hotels); // Delete await searchClient.DeleteDocumentsAsync("hotelId", new[] { "1", "2" }); // Batch operations var batch = IndexDocumentsBatch.Create( IndexDocumentsAction.Upload(hotel1), IndexDocumentsAction.Merge(hotel2), IndexDocumentsAction.Delete(hotel3)); await searchClient.IndexDocumentsAsync(batch); ``` ## Search Patterns ### Basic Search ```csharp var options = new SearchOptions { Filter = "rating ge 4", OrderBy = { "rating desc" }, Select = { "hotelId", "hotelName", "rating" }, Size = 10, Skip = 0, IncludeTotalCount = true }; SearchResults results = await searchClient.SearchAsync("luxury", options); Console.WriteLine($"Total: {results.TotalCount}"); await foreach (SearchResult result in results.GetResultsAsync()) { Console.WriteLine($"{result.Document.HotelName} (Score: {result.Score})"); } ``` ### Faceted Search ```csharp var options = new SearchOptions { Facets = { "rating,count:5", "category" } }; var results = await searchClient.SearchAsync("*", options); foreach (var facet in results.Value.Facets["rating"]) { Console.WriteLine($"Rating {facet.Value}: {facet.Count}"); } ``` ### Autocomplete and Suggestions ```csharp // Autocomplete var autocompleteOptions = new AutocompleteOptions { Mode = AutocompleteMode.OneTermWithContext }; var autocomplete = await searchClient.AutocompleteAsync("lux", "suggester-name", autocompleteOptions); // Suggestions var suggestOptions = new SuggestOptions { UseFuzzyMatching = true }; var suggestions = await searchClient.SuggestAsync("lux", "suggester-name", suggestOptions); ``` ## Vector Search See [references/vector-search.md](references/vector-search.md) for detailed patterns. ```csharp using Azure.Search.Documents.Models; // Pure vector search var vectorQuery = new VectorizedQuery(embedding) { KNearestNeighborsCount = 5, Fields = { "descriptionVector" } }; var options = new SearchOptions { VectorSearch = new VectorSearchOptions { Queries = { vectorQuery } } }; var results = await searchClient.SearchAsync(null, options); ``` ## Semantic Search See [references/semantic-search.md](references/semantic-search.md) for detailed patterns. ```csharp var options = new SearchOptions { QueryType = SearchQueryType.Semantic, SemanticSearch = new SemanticSearchOptions { SemanticConfigurationName = "my-semantic-config", QueryCaption = new QueryCaption(QueryCaptionType.Extractive), QueryAnswer = new QueryAnswer(QueryAnswerType.Extractive) } }; var results = await searchClient.SearchAsync("best hotel for families", options); // Access semantic answers foreach (var answer in results.Value.SemanticSearch.Answers) { Console.WriteLine($"Answer: {answer.Text} (Score: {answer.Score})"); } // Access captions await foreach (var result in results.Value.GetResultsAsync()) { var caption = result.SemanticSearch?.Captions?.FirstOrDefault(); Console.WriteLine($"Caption: {caption?.Text}"); } ``` ## Hybrid Search (Vector + Keyword + Semantic) ```csharp var vectorQuery = new VectorizedQuery(embedding) { KNearestNeighborsCount = 5, Fields = { "descriptionVector" } }; var options = new SearchOptions { QueryType = SearchQueryType.Semantic, SemanticSearch = new SemanticSearchOptions { SemanticConfigurationName = "my-semantic-config" }, VectorSearch = new VectorSearchOptions { Queries = { vectorQuery } } }; // Combines keyword search, vector search, and semantic ranking var results = await searchClient.SearchAsync("luxury beachfront", options); ``` ## Field Attributes Reference | Attribute | Purpose | |-----------|---------| | `SimpleField` | Non-searchable field (filters, sorting, facets) | | `SearchableField` | Full-text searchable field | | `VectorSearchField` | Vector embedding field | | `IsKey = true` | Document key (required, one per index) | | `IsFilterable = true` | Enable $filter expressions | | `IsSortable = true` | Enable $orderby | | `IsFacetable = true` | Enable faceted navigation | | `IsHidden = true` | Exclude from results | | `AnalyzerName` | Specify text analyzer | ## Error Handling ```csharp using Azure; try { var results = await searchClient.SearchAsync("query"); } catch (RequestFailedException ex) when (ex.Status == 404) { Console.WriteLine("Index not found"); } catch (RequestFailedException ex) { Console.WriteLine($"Search error: {ex.Status} - {ex.ErrorCode}: {ex.Message}"); } ``` ## Best Practices 1. **Use `DefaultAzureCredential`** over API keys for production 2. **Use `FieldBuilder`** with model attributes for type-safe index definitions 3. **Use `CreateOrUpdateIndexAsync`** for idempotent index creation 4. **Batch document operations** for better throughput 5. **Use `Select`** to return only needed fields 6. **Configure semantic search** for natural language queries 7. **Combine vector + keyword + semantic** for best relevance ## Reference Files | File | Contents | |------|----------| | [references/vector-search.md](references/vector-search.md) | Vector search, hybrid search, vectorizers | | [references/semantic-search.md](references/semantic-search.md) | Semantic ranking, captions, answers |