Azure.Search.Documents (.NET)
Build search applications with full-text, vector, semantic, and hybrid search capabilities.
Installation
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
SEARCH_ENDPOINT=https://<search-service>.search.windows.net SEARCH_INDEX_NAME=<index-name>
For API key auth (not recommended for production)
SEARCH_API_KEY=<api-key>
Authentication
DefaultAzureCredential (preferred):
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:
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)
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<float>? 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
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
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
var options = new SearchOptions { Filter = "rating ge 4", OrderBy = { "rating desc" }, Select = { "hotelId", "hotelName", "rating" }, Size = 10, Skip = 0, IncludeTotalCount = true };
SearchResults<Hotel> results = await searchClient.SearchAsync<Hotel>("luxury", options);
Console.WriteLine($"Total: {results.TotalCount}"); await foreach (SearchResult<Hotel> result in results.GetResultsAsync()) { Console.WriteLine($"{result.Document.HotelName} (Score: {result.Score})"); }
Faceted Search
var options = new SearchOptions { Facets = { "rating,count:5", "category" } };
var results = await searchClient.SearchAsync<Hotel>("*", options);
foreach (var facet in results.Value.Facets["rating"]) { Console.WriteLine($"Rating {facet.Value}: {facet.Count}"); }
Autocomplete and Suggestions
// 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<Hotel>("lux", "suggester-name", suggestOptions);
Vector Search
See references/vector-search.md for detailed patterns.
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<Hotel>(null, options);
Semantic Search
See references/semantic-search.md for detailed patterns.
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<Hotel>("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)
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<Hotel>("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
using Azure;
try { var results = await searchClient.SearchAsync<Hotel>("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
-
Use DefaultAzureCredential over API keys for production
-
Use FieldBuilder with model attributes for type-safe index definitions
-
Use CreateOrUpdateIndexAsync for idempotent index creation
-
Batch document operations for better throughput
-
Use Select to return only needed fields
-
Configure semantic search for natural language queries
-
Combine vector + keyword + semantic for best relevance
Reference Files
File Contents
references/vector-search.md Vector search, hybrid search, vectorizers
references/semantic-search.md Semantic ranking, captions, answers