* chore: upgrade maintenance scripts to robust PyYAML parsing - Replaces fragile regex frontmatter parsing with PyYAML/yaml library - Ensures multi-line descriptions and complex characters are handled safely - Normalizes quoting and field ordering across all maintenance scripts - Updates validator to strictly enforce description quality * fix: restore and refine truncated skill descriptions - Recovered 223+ truncated descriptions from git history (6.5.0 regression) - Refined long descriptions into concise, complete sentences (<200 chars) - Added missing descriptions for brainstorming and orchestration skills - Manually fixed imagen skill description - Resolved dangling links in competitor-alternatives skill * chore: sync generated registry files and document fixes - Regenerated skills index with normalized forward-slash paths - Updated README and CATALOG to reflect restored descriptions - Documented restoration and script improvements in CHANGELOG.md * fix: restore missing skill and align metadata for full 955 count - Renamed SKILL.MD to SKILL.md in andruia-skill-smith to ensure indexing - Fixed risk level and missing section in andruia-skill-smith - Synchronized all registry files for final 955 skill count * chore(scripts): add cross-platform runners and hermetic test orchestration * fix(scripts): harden utf-8 output and clone target writeability * fix(skills): add missing date metadata for strict validation * chore(index): sync generated metadata dates * fix(catalog): normalize skill paths to prevent CI drift * chore: sync generated registry files * fix: enforce LF line endings for generated registry files
344 lines
9.2 KiB
Markdown
344 lines
9.2 KiB
Markdown
---
|
|
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.
|
|
risk: unknown
|
|
source: community
|
|
date_added: '2026-02-27'
|
|
---
|
|
|
|
# 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-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)**:
|
|
```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<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
|
|
|
|
```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<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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```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<Hotel>("lux", "suggester-name", suggestOptions);
|
|
```
|
|
|
|
## Vector Search
|
|
|
|
See 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<Hotel>(null, options);
|
|
```
|
|
|
|
## Semantic Search
|
|
|
|
See 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<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)
|
|
|
|
```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<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
|
|
|
|
```csharp
|
|
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
|
|
|
|
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 | Vector search, hybrid search, vectorizers |
|
|
| references/semantic-search.md | Semantic ranking, captions, answers |
|
|
|
|
## When to Use
|
|
This skill is applicable to execute the workflow or actions described in the overview.
|