在本快速入门中,你将使用 Azure.Search.Documents 客户端库通过示例数据创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 进行索引和查询,使用 BM25 排名算法对结果进行评分。
本快速入门创建和查询包含有关四家酒店的数据的小酒店快速入门索引。
小提示
可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
先决条件
Microsoft Entra ID 先决条件
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,你需要:
- 安装使用 Microsoft Entra ID 进行无密钥身份验证所需的 Azure CLI。
- 将
Search Service Contributor
和Search Index Data Contributor
两个角色分配给你的用户帐户。 你可以在 Azure 门户的“访问控制(IAM)”“添加角色分配”下分配角色。> 有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
检索资源信息
需要检索以下信息才能使用 Azure AI 搜索服务对应用程序进行身份验证:
变量名称 | 价值 |
---|---|
SEARCH_API_ENDPOINT |
可以在 Azure 门户中找到此值。 选择搜索服务,然后从左侧菜单中选择“概述”。
Essentials 下的 URL 值是所需的终结点。 示例终结点可能类似于 https://mydemo.search.azure.cn 。 |
设置
创建一个新文件夹
full-text-quickstart
以包含该应用程序,并在该文件夹中使用以下命令打开 Visual Studio Code:mkdir full-text-quickstart && cd full-text-quickstart
使用以下命令创建新的控制台应用程序:
dotnet new console
安装适用于 .NET 的 Azure AI 搜索客户端库 (Azure.Search.Documents)
dotnet add package Azure.Search.Documents
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,请使用以下命令安装 Azure.Identity 包:
dotnet add package Azure.Identity
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,请使用以下命令登录到 Azure:
az cloud set -n AzureChinaCloud az login # az cloud set -n AzureCloud //means return to Public Azure.
创建、加载和查询搜索索引
在前面的设置部分中,你创建了一个新的控制台应用程序并安装了 Azure AI 搜索客户端库。
在本部分中,你将添加代码以创建搜索索引、使用文档加载它并运行查询。 运行程序以查看控制台中的结果。 有关代码的详细说明,请参阅说明代码部分。
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将 DefaultAzureCredential
对象替换为 AzureKeyCredential
对象。
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.azure.cn/");
DefaultAzureCredential credential = new();
在 Program.cs 中粘贴以下代码。 使用搜索服务名称和管理 API 密钥编辑
serviceName
和apiKey
变量。using System; using Azure; using Azure.Identity; using Azure.Search.Documents; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; using Azure.Search.Documents.Models; namespace AzureSearch.Quickstart { class Program { static void Main(string[] args) { // Your search service endpoint Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.azure.cn/"); // Use the recommended keyless credential instead of the AzureKeyCredential credential. DefaultAzureCredential credential = new(); //AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key"); // Create a SearchIndexClient to send create/delete index commands SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential); // Create a SearchClient to load and query documents string indexName = "hotels-quickstart"; SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential); // Delete index if it exists Console.WriteLine("{0}", "Deleting index...\n"); DeleteIndexIfExists(indexName, searchIndexClient); // Create index Console.WriteLine("{0}", "Creating index...\n"); CreateIndex(indexName, searchIndexClient); SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName); // Load documents Console.WriteLine("{0}", "Uploading documents...\n"); UploadDocuments(ingesterClient); // Wait 2 secondsfor indexing to complete before starting queries (for demo and console-app purposes only) Console.WriteLine("Waiting for indexing...\n"); System.Threading.Thread.Sleep(2000); // Call the RunQueries method to invoke a series of queries Console.WriteLine("Starting queries...\n"); RunQueries(searchClient); // End the program Console.WriteLine("{0}", "Complete. Press any key to end this program...\n"); Console.ReadKey(); } // Delete the hotels-quickstart index to reuse its name private static void DeleteIndexIfExists(string indexName, SearchIndexClient searchIndexClient) { searchIndexClient.GetIndexNames(); { searchIndexClient.DeleteIndex(indexName); } } // Create hotels-quickstart index private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient) { FieldBuilder fieldBuilder = new FieldBuilder(); var searchFields = fieldBuilder.Build(typeof(Hotel)); var definition = new SearchIndex(indexName, searchFields); var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" }); definition.Suggesters.Add(suggester); searchIndexClient.CreateOrUpdateIndex(definition); } // Upload documents in a single Upload request. private static void UploadDocuments(SearchClient searchClient) { IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create( IndexDocumentsAction.Upload( new Hotel() { HotelId = "1", HotelName = "Stay-Kay City Hotel", Description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.", Category = "Boutique", Tags = new[] { "view", "air conditioning", "concierge" }, ParkingIncluded = false, LastRenovationDate = new DateTimeOffset(2022, 1, 18, 0, 0, 0, TimeSpan.Zero), Rating = 3.6, Address = new Address() { StreetAddress = "677 5th Ave", City = "Beijing", StateProvince = "NY", PostalCode = "10022", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "2", HotelName = "Old Century Hotel", Description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.", Category = "Boutique", Tags = new[] { "pool", "free wifi", "concierge" }, ParkingIncluded = false, LastRenovationDate = new DateTimeOffset(2019, 2, 18, 0, 0, 0, TimeSpan.Zero), Rating = 3.60, Address = new Address() { StreetAddress = "140 University Town Center Dr", City = "Sarasota", StateProvince = "FL", PostalCode = "34243", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "3", HotelName = "Gastronomic Landscape Hotel", Description = "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.", Category = "Suite", Tags = new[] { "restaurant", "bar", "continental breakfast" }, ParkingIncluded = true, LastRenovationDate = new DateTimeOffset(2015, 9, 20, 0, 0, 0, TimeSpan.Zero), Rating = 4.80, Address = new Address() { StreetAddress = "3393 Peachtree Rd", City = "Atlanta", StateProvince = "GA", PostalCode = "30326", Country = "USA" } }), IndexDocumentsAction.Upload( new Hotel() { HotelId = "4", HotelName = "Sublime Palace Hotel", Description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.", Category = "Boutique", Tags = new[] { "concierge", "view", "air conditioning" }, ParkingIncluded = true, LastRenovationDate = new DateTimeOffset(2020, 2, 06, 0, 0, 0, TimeSpan.Zero), Rating = 4.60, Address = new Address() { StreetAddress = "7400 San Pedro Ave", City = "San Antonio", StateProvince = "TX", PostalCode = "78216", Country = "USA" } }) ); try { IndexDocumentsResult result = searchClient.IndexDocuments(batch); } catch (Exception) { // If for some reason any documents are dropped during indexing, you can compensate by delaying and // retrying. This simple demo just logs the failed document keys and continues. Console.WriteLine("Failed to index some of the documents: {0}"); } } // Run queries, use WriteDocuments to print output private static void RunQueries(SearchClient searchClient) { SearchOptions options; SearchResults<Hotel> response; // Query 1 Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n"); options = new SearchOptions() { IncludeTotalCount = true, Filter = "", OrderBy = { "" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Rating"); response = searchClient.Search<Hotel>("*", options); WriteDocuments(response); // Query 2 Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n"); options = new SearchOptions() { Filter = "Rating gt 4", OrderBy = { "Rating desc" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Rating"); response = searchClient.Search<Hotel>("hotels", options); WriteDocuments(response); // Query 3 Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n"); options = new SearchOptions() { SearchFields = { "Tags" } }; options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Tags"); response = searchClient.Search<Hotel>("pool", options); WriteDocuments(response); // Query 4 - Use Facets to return a faceted navigation structure for a given query // Filters are typically used with facets to narrow results on OnClick events Console.WriteLine("Query #4: Facet on 'Category'...\n"); options = new SearchOptions() { Filter = "" }; options.Facets.Add("Category"); options.Select.Add("HotelId"); options.Select.Add("HotelName"); options.Select.Add("Category"); response = searchClient.Search<Hotel>("*", options); WriteDocuments(response); // Query 5 Console.WriteLine("Query #5: Look up a specific document...\n"); Response<Hotel> lookupResponse; lookupResponse = searchClient.GetDocument<Hotel>("3"); Console.WriteLine(lookupResponse.Value.HotelId); // Query 6 Console.WriteLine("Query #6: Call Autocomplete on HotelName...\n"); var autoresponse = searchClient.Autocomplete("sa", "sg"); WriteDocuments(autoresponse); } // Write search results to console private static void WriteDocuments(SearchResults<Hotel> searchResults) { foreach (SearchResult<Hotel> result in searchResults.GetResults()) { Console.WriteLine(result.Document); } Console.WriteLine(); } private static void WriteDocuments(AutocompleteResults autoResults) { foreach (AutocompleteItem result in autoResults.Results) { Console.WriteLine(result.Text); } Console.WriteLine(); } } }
在同一文件夹中,创建一个名为 Hotel.cs 的新文件,并粘贴以下代码。 此代码定义酒店文档的结构。
using System; using System.Text.Json.Serialization; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; namespace AzureSearch.Quickstart { public partial class Hotel { [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] public string Description { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Category { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } [SearchableField] public Address Address { get; set; } } }
创建名为 Hotel.cs 的新文件,并粘贴以下代码以定义酒店文档的结构。 该字段的属性决定字段在应用程序中的使用方式。 例如,
IsFilterable
属性必须分配给每个支持筛选表达式的字段。using System; using System.Text.Json.Serialization; using Azure.Search.Documents.Indexes; using Azure.Search.Documents.Indexes.Models; namespace AzureSearch.Quickstart { public partial class Hotel { [SimpleField(IsKey = true, IsFilterable = true)] public string HotelId { get; set; } [SearchableField(IsSortable = true)] public string HotelName { get; set; } [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)] public string Description { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Category { get; set; } [SearchableField(IsFilterable = true, IsFacetable = true)] public string[] Tags { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public bool? ParkingIncluded { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public DateTimeOffset? LastRenovationDate { get; set; } [SimpleField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public double? Rating { get; set; } [SearchableField] public Address Address { get; set; } } }
创建一个名为 Address.cs 的新文件,并粘贴以下代码以定义地址文档的结构。
using Azure.Search.Documents.Indexes; namespace AzureSearch.Quickstart { public partial class Address { [SearchableField(IsFilterable = true)] public string StreetAddress { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string City { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string StateProvince { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string PostalCode { get; set; } [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)] public string Country { get; set; } } }
创建一个名为 Hotel.Methods.cs 的新文件,并粘贴以下代码以定义
ToString()
类的Hotel
重写。using System; using System.Text; namespace AzureSearch.Quickstart { public partial class Hotel { public override string ToString() { var builder = new StringBuilder(); if (!String.IsNullOrEmpty(HotelId)) { builder.AppendFormat("HotelId: {0}\n", HotelId); } if (!String.IsNullOrEmpty(HotelName)) { builder.AppendFormat("Name: {0}\n", HotelName); } if (!String.IsNullOrEmpty(Description)) { builder.AppendFormat("Description: {0}\n", Description); } if (!String.IsNullOrEmpty(Category)) { builder.AppendFormat("Category: {0}\n", Category); } if (Tags != null && Tags.Length > 0) { builder.AppendFormat("Tags: [ {0} ]\n", String.Join(", ", Tags)); } if (ParkingIncluded.HasValue) { builder.AppendFormat("Parking included: {0}\n", ParkingIncluded.Value ? "yes" : "no"); } if (LastRenovationDate.HasValue) { builder.AppendFormat("Last renovated on: {0}\n", LastRenovationDate); } if (Rating.HasValue) { builder.AppendFormat("Rating: {0}\n", Rating); } if (Address != null && !Address.IsEmpty) { builder.AppendFormat("Address: \n{0}\n", Address.ToString()); } return builder.ToString(); } } }
创建一个名为 Address.Methods.cs 的新文件,并粘贴以下代码以定义
ToString()
类的Address
重写。using System; using System.Text; using System.Text.Json.Serialization; namespace AzureSearch.Quickstart { public partial class Address { public override string ToString() { var builder = new StringBuilder(); if (!IsEmpty) { builder.AppendFormat("{0}\n{1}, {2} {3}\n{4}", StreetAddress, City, StateProvince, PostalCode, Country); } return builder.ToString(); } [JsonIgnore] public bool IsEmpty => String.IsNullOrEmpty(StreetAddress) && String.IsNullOrEmpty(City) && String.IsNullOrEmpty(StateProvince) && String.IsNullOrEmpty(PostalCode) && String.IsNullOrEmpty(Country); } }
生成并使用以下命令运行应用程序。
dotnet run
输出包含 Console.WriteLine 中的消息,并添加了查询信息和结果。
解释代码
在前面的部分中,你创建了一个新的控制台应用程序并安装了 Azure AI 搜索客户端库。 你添加了代码来创建搜索索引、加载文档和运行查询。 你运行了该程序,在控制台中查看了结果。
在本部分中,我们将介绍添加到控制台应用程序的代码。
创建搜索客户端
在 Program.cs中,你创建了两个客户端:
- SearchIndexClient 会创建索引。
- SearchClient 会加载并查询现有索引。
这两个客户端都需要之前在资源信息部分中介绍的搜索服务终结点和凭据。
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将 DefaultAzureCredential
对象替换为 AzureKeyCredential
对象。
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.azure.cn/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
// Your search service endpoint
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.azure.cn/");
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
string indexName = "hotels-quickstart";
SearchClient searchClient = new SearchClient(serviceEndpoint, indexName, credential);
// REDACTED FOR BREVITY . . .
}
创建索引
此快速入门教程创建一个酒店索引,您将在其中加载酒店数据并对其执行查询。 在此步骤中,你会定义索引中的字段。 每个字段定义都包含名称、数据类型以及确定如何使用该字段的属性。
在此示例中,为了简单和可读性,使用了 Azure.Search.Documents 库的同步方法。 但是,对于生产场景,应使用异步方法来保持应用程序的可缩放性和响应性。 例如,使用 CreateIndexAsync,而不是 CreateIndex。
定义结构
你创建了两个帮助程序类(Hotel.cs 和 Address.cs)来定义酒店文档的结构及其地址。
Hotel
类包括酒店 ID、名称、说明、类别、标记、停车、装修日期、评级和地址的字段。
Address
类包括街道地址、城市、州/省/自治区、邮政编码和国家/地区的字段。
在 Azure.Search.Documents 客户端库中,可以使用 SearchableField 和 SimpleField 来简化字段定义。 两者都是 SearchField 的派生形式,可能会简化你的代码:
SimpleField
可以是任何数据类型,始终不可搜索(全文搜索查询将忽略它),并且可检索(未隐藏)。 其他属性默认情况下处于关闭状态,但可以启用。 你可能会将SimpleField
用于仅在筛选器、方面或计分概要文件中使用的文档 ID 或字段。 如果是这样,请确保应用该场景所需的所有属性,例如,通过使用IsKey = true
来标识文档 ID。 有关详细信息,请参阅源代码中的 SimpleFieldAttribute.cs。SearchableField
必须是字符串,并且始终可搜索、可检索。 其他属性默认情况下处于关闭状态,但可以启用。 因为此字段类型是可搜索的,所以它支持同义词和分析器属性的完整补集。 有关详细信息,请参阅源代码中的 SearchableFieldAttribute.cs。
无论使用基本 SearchField
API 还是任一帮助程序模型,都必须显式启用筛选器、facet 和排序属性。 例如,IsFilterable、IsSortable 和 IsFacetable 必须进行显式属性化,如上一示例所示。
创建搜索索引
在 Program.cs 中,你会创建一个 SearchIndex 对象,然后调用 CreateIndex 方法来表示搜索服务中的索引。 此索引还包括一个 SearchSuggester 以便在指定字段上启用自动完成。
// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient searchIndexClient)
{
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));
var definition = new SearchIndex(indexName, searchFields);
var suggester = new SearchSuggester("sg", new[] { "HotelName", "Category", "Address/City", "Address/StateProvince" });
definition.Suggesters.Add(suggester);
searchIndexClient.CreateOrUpdateIndex(definition);
}
加载文档
Azure AI 搜索对存储在服务中的内容进行搜索。 在此步骤中,你会加载符合刚刚创建的酒店索引的 JSON 文档。
在 Azure AI 搜索中,搜索文档这一数据结构既是索引输入,也是查询输出。 文档输入从外部数据源获取,可能是数据库中的行、Blob 存储中的 blob 或磁盘上的 JSON 文档。 在此示例中,我们采用了快捷方式,并在代码本身中嵌入了四个酒店的 JSON 文档。
上传文档时,必须使用 IndexDocumentsBatch 对象。
IndexDocumentsBatch
对象包含 Actions 集合,其中每个操作均包含一个文档和一个属性,该属性用于指示 Azure AI 搜索要执行什么操作(upload、merge、delete 和 mergeOrUpload)。
在 Program.cs 中,你会创建文档和索引操作的数组,然后将该数组传递给 IndexDocumentsBatch
。 以下文档符合 hotel 类定义的 hotels-quickstart 索引。
// Upload documents in a single Upload request.
private static void UploadDocuments(SearchClient searchClient)
{
IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
IndexDocumentsAction.Upload(
new Hotel()
{
HotelId = "1",
HotelName = "Stay-Kay City Hotel",
Description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.",
Category = "Boutique",
Tags = new[] { "view", "air conditioning", "concierge" },
ParkingIncluded = false,
LastRenovationDate = new DateTimeOffset(2022, 1, 18, 0, 0, 0, TimeSpan.Zero),
Rating = 3.6,
Address = new Address()
{
StreetAddress = "677 5th Ave",
City = "Beijing",
StateProvince = "NY",
PostalCode = "10022",
Country = "USA"
}
}),
// REDACTED FOR BREVITY
}
初始化 IndexDocumentsBatch 对象后,可以通过对 SearchClient 对象调用 IndexDocuments 将其发送到索引。
你可以在Main()
中使用 SearchClient 加载文档,但此操作还需要服务所需的管理员权限,这通常与 SearchIndexClient 相关。 设置此操作的一种方法是通过 SearchIndexClient
(在本示例中为 searchIndexClient
)获取 SearchClient。
SearchClient ingesterClient = searchIndexClient.GetSearchClient(indexName);
// Load documents
Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(ingesterClient);
由于有一个按顺序运行所有命令的控制台应用,因此会在索引和查询之间添加 2 秒的等待时间。
// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
Console.WriteLine("Waiting for indexing...\n");
System.Threading.Thread.Sleep(2000);
2 秒的延迟可对索引编制进行补偿(这是异步操作),这样可在执行查询之前对所有文档编制索引。 以延迟方式编写代码通常仅在演示、测试和示例应用程序中是必要的。
搜索索引
对第一个文档编制索引后,可立即获取查询结果,但索引的实际测试应等到对所有文档编制索引后进行。
此部分添加了两个功能:查询逻辑和结果。 对于查询,请使用 Search 方法。 此方法接受搜索文本(查询字符串)以及其他选项。
SearchResults 类表示结果。
在 Program.cs 中,该方法会将 WriteDocuments
搜索结果输出到控制台。
// Write search results to console
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
foreach (SearchResult<Hotel> result in searchResults.GetResults())
{
Console.WriteLine(result.Document);
}
Console.WriteLine();
}
private static void WriteDocuments(AutocompleteResults autoResults)
{
foreach (AutocompleteItem result in autoResults.Results)
{
Console.WriteLine(result.Text);
}
Console.WriteLine();
}
查询示例 1
RunQueries
方法会执行查询并返回结果。 结果是 Hotel 对象。 此示例显示了方法签名和第一个查询。 此查询演示了 Select
参数,通过该参数可以使用文档中的选定字段来编写结果。
// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
SearchOptions options;
SearchResults<Hotel> response;
// Query 1
Console.WriteLine("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
options = new SearchOptions()
{
IncludeTotalCount = true,
Filter = "",
OrderBy = { "" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Address/City");
response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);
// REDACTED FOR BREVITY
}
查询示例 2
在第二个查询中,搜索某个术语,添加筛选器(用于选择评级大于 4 的文档),然后按评级降序排序。 筛选器是布尔表达式,该表达式通过索引中的 IsFilterable 字段求值。 筛选器查询包括或排除值。 同样,筛选器查询没有关联的相关性分数。
// Query 2
Console.WriteLine("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
options = new SearchOptions()
{
Filter = "Rating gt 4",
OrderBy = { "Rating desc" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Rating");
response = searchClient.Search<Hotel>("hotels", options);
WriteDocuments(response);
查询示例 3
第三个查询演示了用于将全文搜索操作的范围限定为特定字段的 searchFields
。
// Query 3
Console.WriteLine("Query #3: Limit search to specific fields (pool in Tags field)...\n");
options = new SearchOptions()
{
SearchFields = { "Tags" }
};
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Tags");
response = searchClient.Search<Hotel>("pool", options);
WriteDocuments(response);
查询示例 4
第四个查询演示了 facets
,可用于构建方面导航结构。
// Query 4
Console.WriteLine("Query #4: Facet on 'Category'...\n");
options = new SearchOptions()
{
Filter = ""
};
options.Facets.Add("Category");
options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Category");
response = searchClient.Search<Hotel>("*", options);
WriteDocuments(response);
查询示例 5
在第五个查询中,返回一个特定文档。 文档查找是对结果集中的 OnClick
事件的典型响应。
// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");
Response<Hotel> lookupResponse;
lookupResponse = searchClient.GetDocument<Hotel>("3");
Console.WriteLine(lookupResponse.Value.HotelId);
查询示例 6
最后一个查询显示了“自动完成”的语法,它模拟部分用户输入,即“sa”,该输入解析为 sourceFields 中的两个可能的匹配项,与你在索引中定义的建议器相关联。
// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
var autoresponse = searchClient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);
查询摘要
前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。
全文搜索和筛选器是使用 SearchClient.Search 方法执行的。 搜索查询可在 searchText
字符串中传递,而筛选表达式则可在 SearchOptions 类的 Filter 属性中传递。 若要筛选但不搜索,只需传递 "*"
作为 searchText
方法的 参数。 若要在不筛选的情况下进行搜索,请保持 Filter
属性未设置,或者根本不传入 SearchOptions
实例。
在本快速入门中,你将使用 Azure.Search.Documents 客户端库通过示例数据创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 进行索引和查询,使用 BM25 排名算法对结果进行评分。
本快速入门创建和查询包含有关四家酒店的数据的小酒店快速入门索引。
小提示
可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
先决条件
Microsoft Entra ID 先决条件
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,你需要:
- 安装使用 Microsoft Entra ID 进行无密钥身份验证所需的 Azure CLI。
- 将
Search Service Contributor
和Search Index Data Contributor
两个角色分配给你的用户帐户。 你可以在 Azure 门户的“访问控制(IAM)”“添加角色分配”下分配角色。> 有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
检索资源信息
需要检索以下信息才能使用 Azure AI 搜索服务对应用程序进行身份验证:
变量名称 | 价值 |
---|---|
SEARCH_API_ENDPOINT |
可以在 Azure 门户中找到此值。 选择搜索服务,然后从左侧菜单中选择“概述”。
Essentials 下的 URL 值是所需的终结点。 示例终结点可能类似于 https://mydemo.search.azure.cn 。 |
设置
本快速入门中的示例适用于 Java 运行时。 安装 Java 开发工具包,例如 Azul Zulu OpenJDK。 Microsoft Build of OpenJDK 或你喜欢的 JDK 应该也能正常工作。
安装 Apache Maven。 然后运行
mvn -v
以确认安装成功。在项目的根目录中创建一个新的
pom.xml
文件,并将以下代码复制到该文件中:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>azure.search.sample</groupId> <artifactId>azuresearchquickstart</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-search-documents</artifactId> <version>11.7.3</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-core</artifactId> <version>1.53.0</version> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.15.1</version> </dependency> </dependencies> </project>
安装依赖项,包括适用于 Java 的 Azure AI 搜索客户端库(Azure.Search.Documents),以及适用于 Java 的 Azure 标识客户端库:
mvn clean dependency:copy-dependencies
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,请使用以下命令登录到 Azure:
az cloud set -n AzureChinaCloud az login # az cloud set -n AzureCloud //means return to Public Azure.
创建、加载和查询搜索索引
在前面的设置部分中,你安装了 Azure AI 搜索客户端库和其他依赖项。
在本部分中,你将添加代码以创建搜索索引、使用文档加载它并运行查询。 运行程序以查看控制台中的结果。 有关代码的详细说明,请参阅说明代码部分。
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将 DefaultAzureCredential
对象替换为 AzureKeyCredential
对象。
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
创建名为 App.java 的新文件,并将以下代码粘贴到 App.java:
import java.util.Arrays; import java.util.ArrayList; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.LocalDateTime; import java.time.LocalDate; import java.time.LocalTime; import com.azure.core.util.Configuration; import com.azure.core.util.Context; import com.azure.identity.DefaultAzureCredential; import com.azure.identity.DefaultAzureCredentialBuilder; import com.azure.search.documents.SearchClient; import com.azure.search.documents.SearchClientBuilder; import com.azure.search.documents.indexes.SearchIndexClient; import com.azure.search.documents.indexes.SearchIndexClientBuilder; import com.azure.search.documents.indexes.models.IndexDocumentsBatch; import com.azure.search.documents.models.SearchOptions; import com.azure.search.documents.indexes.models.SearchIndex; import com.azure.search.documents.indexes.models.SearchSuggester; import com.azure.search.documents.util.AutocompletePagedIterable; import com.azure.search.documents.util.SearchPagedIterable; public class App { public static void main(String[] args) { // Your search service endpoint "https://<Put your search service NAME here>.search.azure.cn/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build(); //AzureKeyCredential credential = new AzureKeyCredential("<Your search service admin key>"); // Create a SearchIndexClient to send create/delete index commands SearchIndexClient searchIndexClient = new SearchIndexClientBuilder() .endpoint(searchServiceEndpoint) .credential(credential) .buildClient(); // Create a SearchClient to load and query documents String indexName = "hotels-quickstart-java"; SearchClient searchClient = new SearchClientBuilder() .endpoint(searchServiceEndpoint) .credential(credential) .indexName(indexName) .buildClient(); // Create Search Index for Hotel model searchIndexClient.createOrUpdateIndex( new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null)) .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName")))); // Upload sample hotel documents to the Search Index uploadDocuments(searchClient); // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only) System.out.println("Waiting for indexing...\n"); try { Thread.sleep(2000); } catch (InterruptedException e) { } // Call the RunQueries method to invoke a series of queries System.out.println("Starting queries...\n"); RunQueries(searchClient); // End the program System.out.println("Complete.\n"); } // Upload documents in a single Upload request. private static void uploadDocuments(SearchClient searchClient) { var hotelList = new ArrayList<Hotel>(); var hotel = new Hotel(); hotel.hotelId = "1"; hotel.hotelName = "Stay-Kay City Hotel"; hotel.description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities."; hotel.category = "Boutique"; hotel.tags = new String[] { "view", "air conditioning", "concierge" }; hotel.parkingIncluded = false; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2022, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 3.6; hotel.address = new Address(); hotel.address.streetAddress = "677 5th Ave"; hotel.address.city = "Beijing"; hotel.address.stateProvince = "NY"; hotel.address.postalCode = "10022"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "2"; hotel.hotelName = "Old Century Hotel"; hotel.description = "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.", hotel.category = "Boutique"; hotel.tags = new String[] { "pool", "free wifi", "concierge" }; hotel.parkingIncluded = false; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2019, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 3.60; hotel.address = new Address(); hotel.address.streetAddress = "140 University Town Center Dr"; hotel.address.city = "Sarasota"; hotel.address.stateProvince = "FL"; hotel.address.postalCode = "34243"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "3"; hotel.hotelName = "Gastronomic Landscape Hotel"; hotel.description = "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services."; hotel.category = "Suite"; hotel.tags = new String[] { "restaurant", "bar", "continental breakfast" }; hotel.parkingIncluded = true; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 4.80; hotel.address = new Address(); hotel.address.streetAddress = "3393 Peachtree Rd"; hotel.address.city = "Atlanta"; hotel.address.stateProvince = "GA"; hotel.address.postalCode = "30326"; hotel.address.country = "USA"; hotelList.add(hotel); hotel = new Hotel(); hotel.hotelId = "4"; hotel.hotelName = "Sublime Palace Hotel"; hotel.description = "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience."; hotel.category = "Boutique"; hotel.tags = new String[] { "concierge", "view", "air conditioning" }; hotel.parkingIncluded = true; hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2020, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC); hotel.rating = 4.60; hotel.address = new Address(); hotel.address.streetAddress = "7400 San Pedro Ave"; hotel.address.city = "San Antonio"; hotel.address.stateProvince = "TX"; hotel.address.postalCode = "78216"; hotel.address.country = "USA"; hotelList.add(hotel); var batch = new IndexDocumentsBatch<Hotel>(); batch.addMergeOrUploadActions(hotelList); try { searchClient.indexDocuments(batch); } catch (Exception e) { e.printStackTrace(); // If for some reason any documents are dropped during indexing, you can compensate by delaying and // retrying. This simple demo just logs failure and continues System.err.println("Failed to index some of the documents"); } } // Write search results to console private static void WriteSearchResults(SearchPagedIterable searchResults) { searchResults.iterator().forEachRemaining(result -> { Hotel hotel = result.getDocument(Hotel.class); System.out.println(hotel); }); System.out.println(); } // Write autocomplete results to console private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults) { autocompleteResults.iterator().forEachRemaining(result -> { String text = result.getText(); System.out.println(text); }); System.out.println(); } // Run queries, use WriteDocuments to print output private static void RunQueries(SearchClient searchClient) { // Query 1 System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n"); SearchOptions options = new SearchOptions(); options.setIncludeTotalCount(true); options.setFilter(""); options.setOrderBy(""); options.setSelect("HotelId", "HotelName", "Address/City"); WriteSearchResults(searchClient.search("*", options, Context.NONE)); // Query 2 System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n"); options = new SearchOptions(); options.setFilter("Rating gt 4"); options.setOrderBy("Rating desc"); options.setSelect("HotelId", "HotelName", "Rating"); WriteSearchResults(searchClient.search("hotels", options, Context.NONE)); // Query 3 System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n"); options = new SearchOptions(); options.setSearchFields("Tags"); options.setSelect("HotelId", "HotelName", "Tags"); WriteSearchResults(searchClient.search("pool", options, Context.NONE)); // Query 4 System.out.println("Query #4: Facet on 'Category'...\n"); options = new SearchOptions(); options.setFilter(""); options.setFacets("Category"); options.setSelect("HotelId", "HotelName", "Category"); WriteSearchResults(searchClient.search("*", options, Context.NONE)); // Query 5 System.out.println("Query #5: Look up a specific document...\n"); Hotel lookupResponse = searchClient.getDocument("3", Hotel.class); System.out.println(lookupResponse.hotelId); System.out.println(); // Query 6 System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n"); WriteAutocompleteResults(searchClient.autocomplete("s", "sg")); } }
创建名为 Hotel.java 的新文件,并将以下代码粘贴到 Hotel.java:
import com.azure.search.documents.indexes.SearchableField; import com.azure.search.documents.indexes.SimpleField; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.time.OffsetDateTime; /** * Model class representing a hotel. */ @JsonInclude(Include.NON_NULL) public class Hotel { /** * Hotel ID */ @JsonProperty("HotelId") @SimpleField(isKey = true) public String hotelId; /** * Hotel name */ @JsonProperty("HotelName") @SearchableField(isSortable = true) public String hotelName; /** * Description */ @JsonProperty("Description") @SearchableField(analyzerName = "en.microsoft") public String description; /** * Category */ @JsonProperty("Category") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String category; /** * Tags */ @JsonProperty("Tags") @SearchableField(isFilterable = true, isFacetable = true) public String[] tags; /** * Whether parking is included */ @JsonProperty("ParkingIncluded") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public Boolean parkingIncluded; /** * Last renovation time */ @JsonProperty("LastRenovationDate") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public OffsetDateTime lastRenovationDate; /** * Rating */ @JsonProperty("Rating") @SimpleField(isFilterable = true, isSortable = true, isFacetable = true) public Double rating; /** * Address */ @JsonProperty("Address") public Address address; @Override public String toString() { try { return new ObjectMapper().writeValueAsString(this); } catch (JsonProcessingException e) { e.printStackTrace(); return ""; } } }
创建名为 Address.java 的新文件,并将以下代码粘贴到 Address.java:
import com.azure.search.documents.indexes.SearchableField; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonInclude.Include; /** * Model class representing an address. */ @JsonInclude(Include.NON_NULL) public class Address { /** * Street address */ @JsonProperty("StreetAddress") @SearchableField public String streetAddress; /** * City */ @JsonProperty("City") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String city; /** * State or province */ @JsonProperty("StateProvince") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String stateProvince; /** * Postal code */ @JsonProperty("PostalCode") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String postalCode; /** * Country */ @JsonProperty("Country") @SearchableField(isFilterable = true, isSortable = true, isFacetable = true) public String country; }
运行新的控制台应用程序:
javac Address.java App.java Hotel.java -cp ".;target\dependency\*" java -cp ".;target\dependency\*" App
解释代码
在前面的部分中,你创建了一个新的控制台应用程序并安装了 Azure AI 搜索客户端库。 你添加了代码来创建搜索索引、加载文档和运行查询。 你运行了该程序,在控制台中查看了结果。
在本部分中,我们将介绍添加到控制台应用程序的代码。
创建搜索客户端
在 App.java 中创建了两个客户端:
- SearchIndexClient 会创建索引。
- SearchClient 会加载并查询现有索引。
这两个客户端都需要之前在资源信息部分中介绍的搜索服务终结点和凭据。
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将 DefaultAzureCredential
对象替换为 AzureKeyCredential
对象。
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
public static void main(String[] args) {
// Your search service endpoint
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/";
// Use the recommended keyless credential instead of the AzureKeyCredential credential.
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
//AzureKeyCredential credential = new AzureKeyCredential("Your search service admin key");
// Create a SearchIndexClient to send create/delete index commands
SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.buildClient();
// Create a SearchClient to load and query documents
String indexName = "hotels-quickstart-java";
SearchClient searchClient = new SearchClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(credential)
.indexName(indexName)
.buildClient();
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
// REDACTED FOR BREVITY . . .
}
创建索引
此快速入门教程创建一个酒店索引,您将在其中加载酒店数据并对其执行查询。 在此步骤中,你会定义索引中的字段。 每个字段定义都包含名称、数据类型以及确定如何使用该字段的属性。
在此示例中,为了简单和可读性,使用了 Azure.Search.Documents 库的同步方法。 但是,对于生产场景,应使用异步方法来保持应用程序的可缩放性和响应性。 例如,使用 CreateIndexAsync,而不是 CreateIndex。
定义结构
你创建了两个帮助程序类(Hotel.java 和 Address.java)来定义酒店文档的结构及其地址。 Hotel 类包括酒店 ID、名称、说明、类别、标记、停车、装修日期、评级和地址的字段。 Address 类包括街道地址、城市、州/省/自治区、邮政编码和国家/地区的字段。
在 Azure.Search.Documents 客户端库中,可以使用 SearchableField 和 SimpleField 来简化字段定义。
-
SimpleField
可以是任何数据类型,始终不可搜索(全文搜索查询将忽略它),并且可检索(未隐藏)。 其他属性默认情况下处于关闭状态,但可以启用。 你可能会将 SimpleField 用于仅在筛选器、方面或计分概要文件中使用的文档 ID 或字段。 如果是这样,请确保应用该场景所需的所有属性,例如将 IsKey = true 应用于文档 ID。 -
SearchableField
必须是字符串,并且始终可搜索、可检索。 其他属性默认情况下处于关闭状态,但可以启用。 因为此字段类型是可搜索的,所以它支持同义词和分析器属性的完整补集。
无论使用基本 SearchField
API 还是任一帮助程序模型,都必须显式启用筛选器、facet 和排序属性。 例如,isFilterable
、isSortable
和 isFacetable
必须进行显式属性化,如上例中所示。
创建搜索索引
在 App.java
中,你会在 SearchIndex
方法中创建 main
对象,然后调用 createOrUpdateIndex
方法,在搜索服务中创建索引。 此索引还包括一个 SearchSuggester
以便在指定字段上启用自动完成。
// Create Search Index for Hotel model
searchIndexClient.createOrUpdateIndex(
new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
.setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
加载文档
Azure AI 搜索对存储在服务中的内容进行搜索。 在此步骤中,你会加载符合刚刚创建的酒店索引的 JSON 文档。
在 Azure AI 搜索中,搜索文档这一数据结构既是索引输入,也是查询输出。 文档输入从外部数据源获取,可能是数据库中的行、Blob 存储中的 blob 或磁盘上的 JSON 文档。 在此示例中,我们采用了快捷方式,并在代码本身中嵌入了四个酒店的 JSON 文档。
上传文档时,必须使用 IndexDocumentsBatch 对象。
IndexDocumentsBatch
对象包含 IndexActions 集合,其中每个操作均包含一个文档和一个属性,该属性用于指示 Azure AI 搜索要执行什么操作(upload、merge、delete 和 mergeOrUpload)。
在 App.java
中,你会创建文档和索引操作,然后将其传递给 IndexDocumentsBatch
。 以下文档符合 hotel 类定义的 hotels-quickstart 索引。
private static void uploadDocuments(SearchClient searchClient)
{
var hotelList = new ArrayList<Hotel>();
var hotel = new Hotel();
hotel.hotelId = "1";
hotel.hotelName = "Stay-Kay City Hotel";
hotel.description = "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.",
hotel.category = "Boutique";
hotel.tags = new String[] { "view", "air conditioning", "concierge" };
hotel.parkingIncluded = false;
hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2022, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
hotel.rating = 3.6;
hotel.address = new Address();
hotel.address.streetAddress = "677 5th Ave";
hotel.address.city = "Beijing";
hotel.address.stateProvince = "NY";
hotel.address.postalCode = "10022";
hotel.address.country = "USA";
hotelList.add(hotel);
// REDACTED FOR BREVITY
var batch = new IndexDocumentsBatch<Hotel>();
batch.addMergeOrUploadActions(hotelList);
try
{
searchClient.indexDocuments(batch);
}
catch (Exception e)
{
e.printStackTrace();
// If for some reason any documents are dropped during indexing, you can compensate by delaying and
// retrying. This simple demo just logs failure and continues
System.err.println("Failed to index some of the documents");
}
}
初始化 IndexDocumentsBatch
对象后,可通过对 对象调用 SearchClient
,将其发送到索引。
你可以在main()
中使用 SearchClient 加载文档,但此操作还需要服务所需的管理员权限,这通常与 SearchIndexClient 相关。 设置此操作的一种方法是通过 SearchIndexClient
(在本示例中为 searchIndexClient
)获取 SearchClient。
uploadDocuments(searchClient);
由于有一个按顺序运行所有命令的控制台应用,因此会在索引和查询之间添加 2 秒的等待时间。
// Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
System.out.println("Waiting for indexing...\n");
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
}
2 秒的延迟可对索引编制进行补偿(这是异步操作),这样可在执行查询之前对所有文档编制索引。 以延迟方式编写代码通常仅在演示、测试和示例应用程序中是必要的。
搜索索引
对第一个文档编制索引后,可立即获取查询结果,但索引的实际测试应等到对所有文档编制索引后进行。
此部分添加了两个功能:查询逻辑和结果。 对于查询,请使用 Search 方法。 此方法接受搜索文本(查询字符串)以及其他选项。
在 App.java
中,该方法会将 WriteDocuments
搜索结果打印到控制台。
// Write search results to console
private static void WriteSearchResults(SearchPagedIterable searchResults)
{
searchResults.iterator().forEachRemaining(result ->
{
Hotel hotel = result.getDocument(Hotel.class);
System.out.println(hotel);
});
System.out.println();
}
// Write autocomplete results to console
private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
{
autocompleteResults.iterator().forEachRemaining(result ->
{
String text = result.getText();
System.out.println(text);
});
System.out.println();
}
查询示例 1
RunQueries
方法会执行查询并返回结果。 结果是 Hotel 对象。 此示例显示了方法签名和第一个查询。 此查询演示了 Select
参数,通过该参数可以使用文档中的选定字段来编写结果。
// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient searchClient)
{
// Query 1
System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
SearchOptions options = new SearchOptions();
options.setIncludeTotalCount(true);
options.setFilter("");
options.setOrderBy("");
options.setSelect("HotelId", "HotelName", "Address/City");
WriteSearchResults(searchClient.search("*", options, Context.NONE));
}
查询示例 2
在第二个查询中,搜索某个术语,添加筛选器(用于选择评级大于 4 的文档),然后按评级降序排序。 筛选器是布尔表达式,该表达式通过索引中的 isFilterable
字段求值。 筛选器查询包括或排除值。 同样,筛选器查询没有关联的相关性分数。
// Query 2
System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
options = new SearchOptions();
options.setFilter("Rating gt 4");
options.setOrderBy("Rating desc");
options.setSelect("HotelId", "HotelName", "Rating");
WriteSearchResults(searchClient.search("hotels", options, Context.NONE));
查询示例 3
第三个查询演示了用于将全文搜索操作的范围限定为特定字段的 searchFields
。
// Query 3
System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");
options = new SearchOptions();
options.setSearchFields("Tags");
options.setSelect("HotelId", "HotelName", "Tags");
WriteSearchResults(searchClient.search("pool", options, Context.NONE));
查询示例 4
第四个查询演示了 facets
,可用于构建方面导航结构。
// Query 4
System.out.println("Query #4: Facet on 'Category'...\n");
options = new SearchOptions();
options.setFilter("");
options.setFacets("Category");
options.setSelect("HotelId", "HotelName", "Category");
WriteSearchResults(searchClient.search("*", options, Context.NONE));
查询示例 5
在第五个查询中,返回一个特定文档。
// Query 5
System.out.println("Query #5: Look up a specific document...\n");
Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
System.out.println(lookupResponse.hotelId);
System.out.println();
查询示例 6
最后一个查询显示了“自动完成”的语法,它模拟部分用户输入,即“s”,该输入解析为 中的两个可能的匹配项,与你在索引中定义的建议器相关联。sourceFields
// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
查询摘要
前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。
全文搜索和筛选器是使用 SearchClient.search 方法执行的。 搜索查询可在 searchText
字符串中传递,而筛选表达式则可在 filter
类的 属性中传递。 若要筛选但不搜索,只需传递“*”作为 searchText
方法的 search
参数。 若要在不筛选的情况下进行搜索,请保持 filter
属性未设置,或者根本不传入 SearchOptions
实例。
在本快速入门中,你将使用 Azure.Search.Documents 客户端库通过示例数据创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 进行索引和查询,使用 BM25 排名算法对结果进行评分。
本快速入门创建和查询包含有关四家酒店的数据的小酒店快速入门索引。
小提示
可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
先决条件
Microsoft Entra ID 先决条件
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,你需要:
- 安装使用 Microsoft Entra ID 进行无密钥身份验证所需的 Azure CLI。
- 将
Search Service Contributor
和Search Index Data Contributor
两个角色分配给你的用户帐户。 你可以在 Azure 门户的“访问控制(IAM)”“添加角色分配”下分配角色。> 有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
检索资源信息
需要检索以下信息才能使用 Azure AI 搜索服务对应用程序进行身份验证:
变量名称 | 价值 |
---|---|
SEARCH_API_ENDPOINT |
可以在 Azure 门户中找到此值。 选择搜索服务,然后从左侧菜单中选择“概述”。
Essentials 下的 URL 值是所需的终结点。 示例终结点可能类似于 https://mydemo.search.azure.cn 。 |
设置
创建一个新文件夹
full-text-quickstart
以包含该应用程序,并在该文件夹中使用以下命令打开 Visual Studio Code:mkdir full-text-quickstart && cd full-text-quickstart
使用以下命令创建
package.json
:npm init -y
安装适用于 JavaScript 的 Azure AI 搜索客户端库 (Azure.Search.Documents)
npm install @azure/search-documents
对于建议的无密码身份验证,请使用以下命令安装 Azure 标识客户端库:
npm install @azure/identity
创建、加载和查询搜索索引
在前面的设置部分中,你安装了 Azure AI 搜索客户端库和其他依赖项。
在本部分中,你将添加代码以创建搜索索引、使用文档加载它并运行查询。 运行程序以查看控制台中的结果。 有关代码的详细说明,请参阅说明代码部分。
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将 DefaultAzureCredential
对象替换为 AzureKeyCredential
对象。
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/";
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
创建名为 index.js 的新文件,并输入以下代码到 index.js 中。
// Import from the @azure/search-documents library import { SearchIndexClient, odata } from "@azure/search-documents"; // Import from the Azure Identity library import { DefaultAzureCredential } from "@azure/identity"; // Importing the hotels sample data import hotelData from './hotels.json' assert { type: "json" }; // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); // Defining the index definition const indexDefinition = { "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Description_fr", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.lucene" }, { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }; async function main() { // Your search service endpoint const searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. const credential = new DefaultAzureCredential(); //const credential = new AzureKeyCredential(Your search service admin key); // Create a SearchIndexClient to send create/delete index commands const searchIndexClient = new SearchIndexClient(searchServiceEndpoint, credential); // Creating a search client to upload documents and issue queries const indexName = "hotels-quickstart"; const searchClient = searchIndexClient.getSearchClient(indexName); console.log('Checking if index exists...'); await deleteIndexIfExists(searchIndexClient, indexName); console.log('Creating index...'); let index = await searchIndexClient.createIndex(indexDefinition); console.log(`Index named ${index.name} has been created.`); console.log('Uploading documents...'); let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']); console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `); // waiting one second for indexing to complete (for demo purposes only) sleep(1000); console.log('Querying the index...'); console.log(); await sendQueries(searchClient); } async function deleteIndexIfExists(searchIndexClient, indexName) { try { await searchIndexClient.deleteIndex(indexName); console.log('Deleting index...'); } catch { console.log('Index does not exist yet.'); } } async function sendQueries(searchClient) { // Query 1 console.log('Query #1 - search everything:'); let searchOptions = { includeTotalCount: true, select: ["HotelId", "HotelName", "Rating"] }; let searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(`Result count: ${searchResults.count}`); console.log(); // Query 2 console.log('Query #2 - search with filter, orderBy, and select:'); let state = 'FL'; searchOptions = { filter: odata `Address/StateProvince eq ${state}`, orderBy: ["Rating desc"], select: ["HotelId", "HotelName", "Rating"] }; searchResults = await searchClient.search("wifi", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 3 console.log('Query #3 - limit searchFields:'); searchOptions = { select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("sublime palace", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 4 console.log('Query #4 - limit searchFields and use facets:'); searchOptions = { facets: ["Category"], select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 5 console.log('Query #5 - Lookup document:'); let documentResult = await searchClient.getDocument('3'); console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`); console.log(); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } main().catch((err) => { console.error("The sample encountered an error:", err); });
创建名为 hotels.json 的文件,并将以下代码粘贴到 hotels.json 中:
{ "value": [ { "HotelId": "1", "HotelName": "Stay-Kay City Hotel", "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.", "Category": "Boutique", "Tags": ["view", "air conditioning", "concierge"], "ParkingIncluded": false, "LastRenovationDate": "2022-01-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "677 5th Ave", "City": "Beijing", "StateProvince": "NY", "PostalCode": "10022" } }, { "HotelId": "2", "HotelName": "Old Century Hotel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.", "Category": "Boutique", "Tags": ["pool", "free wifi", "concierge"], "ParkingIncluded": "false", "LastRenovationDate": "2019-02-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243" } }, { "HotelId": "3", "HotelName": "Gastronomic Landscape Hotel", "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.", "Category": "Suite", "Tags": ["restaurant", "bar", "continental breakfast"], "ParkingIncluded": "true", "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.8, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326" } }, { "HotelId": "4", "HotelName": "Sublime Palace Hotel", "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.", "Category": "Boutique", "Tags": ["concierge", "view", "air conditioning"], "ParkingIncluded": true, "LastRenovationDate": "2020-02-06T00:00:00Z", "Rating": 4.6, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216" } } ] }
创建名为 hotels_quickstart_index.json 的文件,并将以下代码粘贴到 hotels_quickstart_index.json 中:
{ "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String", "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }
使用以下命令登录到 Azure:
az cloud set -n AzureChinaCloud az login # az cloud set -n AzureCloud //means return to Public Azure.
使用以下命令运行 JavaScript 代码:
node index.js
解释代码
创建索引
Hotels_quickstart_index.json 文件定义 Azure AI 搜索如何处理下一步中加载的文档。 每个字段均由 name
标识,并具有指定的 type
。 每个字段还包含一系列索引属性,这些属性指定 Azure AI 搜索是否可以根据字段进行搜索、筛选、排序和创建分面。 大多数字段采用简单数据类型,但有些字段(例如 AddressType
)采用复杂类型,可让你在索引中创建丰富的数据结构。 可详细了解支持的数据类型,以及创建索引 (REST) 中所述的索引属性。
建立索引定义后,我们需要在 index.js 顶部导入 hotels_quickstart_index.json,使 main 函数能够访问索引定义。
const indexDefinition = require('./hotels_quickstart_index.json');
然后在 main 函数中,我们创建一个 SearchIndexClient
用来创建和管理 Azure AI 搜索索引。
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
接下来,我们要删除该索引(如果它已存在)。 这是测试/演示代码的常见做法。
为此,我们要定义一个简单函数,它会尝试删除索引。
async function deleteIndexIfExists(indexClient, indexName) {
try {
await indexClient.deleteIndex(indexName);
console.log('Deleting index...');
} catch {
console.log('Index does not exist yet.');
}
}
为了运行该函数,我们将从索引定义中提取索引名称,并将 indexName
连同 indexClient
传递给 deleteIndexIfExists()
函数。
const indexName = indexDefinition["name"];
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
之后,我们就可用 createIndex()
方法创建索引了。
console.log('Creating index...');
let index = await indexClient.createIndex(indexDefinition);
console.log(`Index named ${index.name} has been created.`);
加载文档
在 Azure AI 搜索中,文档这一数据结构既是索引输入,也是查询输出。 可将此类数据推送到索引,或者使用索引器。 在这种情况下,我们将以编程方式将文档推送到索引。
文档输入可以是数据库中的行、Blob 存储中的 Blob,或磁盘上的 JSON 文档(在本示例中为 JSON 文档)。 与我们使用 indexDefinition
执行的操作类似,我们还需要在 index.js 顶部导入 hotels.json
,以便可在我们的 main 函数中访问数据。
const hotelData = require('./hotels.json');
为了将数据编入搜索索引中,现在需要创建 SearchClient
。 虽然 SearchIndexClient
用于创建和管理索引,但 SearchClient
用于上传文档和查询索引。
创建 SearchClient
的方法有两种。 第一种方法是从头开始创建 SearchClient
:
const searchClient = new SearchClient(endpoint, indexName, new AzureKeyCredential(apiKey));
或者,你可使用 getSearchClient()
的 SearchIndexClient
方法来创建 SearchClient
:
const searchClient = indexClient.getSearchClient(indexName);
现在已定义了客户端,需将文档上传到搜索索引。 在此示例中,我们使用 mergeOrUploadDocuments()
方法,该方法将上传文档;如果已存在具有相同密钥的文档,则它会将这些文档与现有文档合并。
console.log('Uploading documents...');
let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
搜索索引
创建索引并上传文档后,便可将查询发送到索引。 在本部分中,我们将向搜索索引发送 5 个不同的查询,以演示可供你使用的不同查询功能部分。
在 sendQueries()
函数中编写查询,我们将在 main 函数中调用此函数,如下所示:
await sendQueries(searchClient);
使用 search()
的 searchClient
方法发送查询。 第一个参数是搜索文本,第二个参数指定搜索选项。
查询示例 1
第一个查询会搜索 *
,这等效于搜索所有内容并选择索引中的三个字段。 最佳做法是通过 select
仅选择你需要的字段,因为回发不必要的数据可能会增加查询的延迟时间。
此查询的 searchOptions
还将 includeTotalCount
设置为 true
,这将返回找到的匹配结果数。
async function sendQueries(searchClient) {
console.log('Query #1 - search everything:');
let searchOptions = {
includeTotalCount: true,
select: ["HotelId", "HotelName", "Rating"]
};
let searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log(`Result count: ${searchResults.count}`);
// remaining queries go here
}
还应将下述其余查询添加到 sendQueries()
函数。 为了方便阅读,此处将它们分开。
查询示例 2
在下一个查询中,我们指定搜索词 "wifi"
,还包括一个筛选器,以仅返回状态等于 'FL'
的结果。 还会按酒店的 Rating
对结果进行排序。
console.log('Query #2 - Search with filter, orderBy, and select:');
let state = 'FL';
searchOptions = {
filter: odata`Address/StateProvince eq ${state}`,
orderBy: ["Rating desc"],
select: ["HotelId", "HotelName", "Rating"]
};
searchResults = await searchClient.search("wifi", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
查询示例 3
接下来,使用 searchFields
参数将搜索限制为单个可搜索字段。 如果你知道自己只对某些字段中的匹配感兴趣,则很适合使用该选项来提高查询的效率。
console.log('Query #3 - Limit searchFields:');
searchOptions = {
select: ["HotelId", "HotelName", "Rating"],
searchFields: ["HotelName"]
};
searchResults = await searchClient.search("Sublime Palace", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log();
查询示例 4
查询中包含的另一个常见选项是 facets
。 通过方面,可在 UI 上构建筛选器,使用户能够轻松地了解可筛选出的值。
console.log('Query #4 - Use facets:');
searchOptions = {
facets: ["Category"],
select: ["HotelId", "HotelName", "Rating"],
searchFields: ["HotelName"]
};
searchResults = await searchClient.search("*", searchOptions);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
查询示例 5
最终查询使用 getDocument()
的 searchClient
方法。 这样,你就可通过文档的密钥有效地检索文档。
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
查询摘要
前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。
全文搜索和筛选器是使用 searchClient.search
方法执行的。 搜索查询可在searchText
字符串中传递,而筛选表达式则可在 filter
类的 SearchOptions
属性中传递。 若要筛选但不搜索,只需传递“*”作为 searchText
方法的 search
参数。 若要在不筛选的情况下进行搜索,请保持 filter
属性未设置,或者根本不传入 SearchOptions
实例。
在本快速入门中,你将使用 PowerShell 和 Azure AI 搜索 REST API 创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 编制索引和查询,使用 BM25 排名算法来评分结果。
本快速入门使用 azure-search-sample-data 存储库中的虚构酒店数据来填充索引。
小提示
可以下载 源代码 以从已完成的项目开始,或按照以下步骤创建自己的项目。
先决条件
拥有有效订阅的 Azure 帐户。 创建试用版订阅。
使用 Microsoft Entra ID 进行无密钥身份验证的 Azure CLI。
PowerShell 7.3 或更高版本。 本快速入门使用 Invoke-RestMethod 进行 REST API 调用。
配置访问权限
可以使用 API 密钥或具有角色分配的 Microsoft Entra ID 连接到 Azure AI 搜索服务。 使用密钥更容易上手,但使用角色更安全。
配置建议的基于角色的访问:
登录到 Azure 门户 并选择搜索服务。
在左窗格中,选择“设置”“密钥”。>
在“API 访问控制”下,选择两项。
此选项同时启用基于密钥的身份验证和无密钥身份验证。 分配角色后,可以返回到此步骤并选择 基于角色的访问控制。
在左窗格中,选择“访问控制”(IAM)。
选择添加>添加角色分配。
将 搜索服务参与者 和 搜索索引数据参与者 角色分配给用户帐户。
有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
获取端点
在下一部分中,请指定以下终结点来建立与 Azure AI 搜索服务的连接。 这些步骤假定你 配置了基于角色的访问。
若要获取服务终结点,请执行以下步骤:
登录到 Azure 门户 并选择搜索服务。
在左窗格中,选择“ 概述”。
记下 URL,该 URL 应类似于
https://my-service.search.azure.cn
。
连接到 Azure AI 搜索
在对 Azure AI 搜索服务进行 REST API 调用之前,必须进行身份验证并连接到该服务。 在 PowerShell 中执行以下步骤,该命令支持步骤 2 和 3 中使用的 Azure CLI 命令。
连接到搜索服务:
在本地系统上,打开 PowerShell。
登录到 Azure 订阅。 如果有多个订阅,请选择包含搜索服务的订阅。
az cloud set -n AzureChinaCloud az login # az cloud set -n AzureCloud //means return to Public Azure.
创建一个对象用于存储
$token
访问令牌。$token = az account get-access-token --resource https://search.azure.com/ --query accessToken --output tsv
创建用于存储令牌和内容类型的
$headers
对象。$headers = @{ 'Authorization' = "Bearer $token" 'Content-Type' = 'application/json' 'Accept' = 'application/json' }
只需为每个会话设置一次标头,但必须将其添加到每个请求。
创建一个
$url
对象,以搜索服务上的索引集合为目标。 将<YOUR-SEARCH-SERVICE>
替换为在 Get 终结点中获取的值。$url = "<YOUR-SEARCH-SERVICE>/indexes?api-version=2024-07-01&`$select=name"
运行
Invoke-RestMethod
以向搜索服务发送 GET 请求。 包括ConvertTo-Json
以查看来自服务的响应。Invoke-RestMethod -Uri $url -Headers $headers | ConvertTo-Json
如果服务为空且没有索引,则响应类似于以下示例。 否则,你会看到索引定义的 JSON 表示形式。
{ "@odata.context": "https://my-service.search.azure.cn/$metadata#indexes", "value": [ ] }
创建搜索索引
在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。 索引在概念上类似于关系数据库中的表,但它专为搜索作(如全文搜索)而设计。
在上一部分启动的同一 PowerShell 会话中运行以下命令。
创建索引:
创建对象
$body
以定义索引架构。$body = @" { "name": "hotels-quickstart", "fields": [ {"name": "HotelId", "type": "Edm.String", "key": true, "filterable": true}, {"name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false}, {"name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzer": "en.lucene"}, {"name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true}, {"name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true}, {"name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true}, {"name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true}, {"name": "Address", "type": "Edm.ComplexType", "fields": [ {"name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true}, {"name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true} ] } ] } "@
更新
$url
对象以定位新索引。 将<YOUR-SEARCH-SERVICE>
替换为在 Get 终结点中获取的值。$url = "<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart?api-version=2024-07-01"
运行
Invoke-RestMethod
以在搜索服务上创建索引。Invoke-RestMethod -Uri $url -Headers $headers -Method Put -Body $body | ConvertTo-Json
响应应包含索引架构的 JSON 表示形式。
关于创建索引请求
本快速入门调用索引 - 创建 (REST API) 以在搜索服务上生成名为 hotels-quickstart
的搜索索引及其物理数据结构。
在索引架构中, fields
集合定义酒店文档的结构。 每个字段都有一个 name
、数据和 type
属性,用于确定其索引和查询期间的行为。 该 HotelId
字段标记为密钥,Azure AI 搜索需要唯一标识索引中的每个文档。
有关索引架构的要点:
使用字符串字段 (
Edm.String
) 使数值数据全文可搜索。 其他受支持的数据类型,例如Edm.Int32
,可筛选、可排序、可分面和可检索,但不可搜索。我们的大多数字段都是简单的数据类型,但你可以定义复杂类型来表示嵌套数据,例如
Address
字段。字段属性确定允许的操作。 默认情况下,REST API 允许很多操作。 例如,所有字符串都是可搜索和可检索的。 使用 REST API 时,仅当需要禁用行为时,才能使用属性。
加载索引
新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。
在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为简单起见,本快速入门以内联 JSON 形式提供示例酒店文档。 但是,在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。
将文档上传到索引:
创建一个对象来存储四个
$body
示例文档的 JSON 有效负载。$body = @" { "value": [ { "@search.action": "upload", "HotelId": "1", "HotelName": "Stay-Kay City Hotel", "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.", "Category": "Boutique", "Tags": [ "view", "air conditioning", "concierge" ], "ParkingIncluded": false, "LastRenovationDate": "2022-01-18T00:00:00Z", "Rating": 3.60, "Address": { "StreetAddress": "677 5th Ave", "City": "Beijing", "StateProvince": "NY", "PostalCode": "10022", "Country": "USA" } }, { "@search.action": "upload", "HotelId": "2", "HotelName": "Old Century Hotel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.", "Category": "Boutique", "Tags": [ "pool", "free wifi", "concierge" ], "ParkingIncluded": false, "LastRenovationDate": "2019-02-18T00:00:00Z", "Rating": 3.60, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243", "Country": "USA" } }, { "@search.action": "upload", "HotelId": "3", "HotelName": "Gastronomic Landscape Hotel", "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.", "Category": "Suite", "Tags": [ "restaurant", "bar", "continental breakfast" ], "ParkingIncluded": true, "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.80, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326", "Country": "USA" } }, { "@search.action": "upload", "HotelId": "4", "HotelName": "Sublime Palace Hotel", "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.", "Category": "Boutique", "Tags": [ "concierge", "view", "air conditioning" ], "ParkingIncluded": true, "LastRenovationDate": "2020-02-06T00:00:00Z", "Rating": 4.60, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216", "Country": "USA" } } ] } "@
更新对象
$url
以面向索引终结点。 将<YOUR-SEARCH-SERVICE>
替换为在 Get 终结点中获取的值。$url = "<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs/index?api-version=2024-07-01"
运行
Invoke-RestMethod
以将上传请求发送到搜索服务。Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body $body | ConvertTo-Json
响应应包含每个上传文档的键和状态。
关于上传请求
本快速入门调用 Documents - Index (REST API) 将四个示例酒店文档添加到索引。 与上一个请求相比,URI 扩展为包含 docs
集合和 index
操作。
数组中的每个 value
文档都表示一个酒店,并包含与索引架构匹配的字段。 该 @search.action
参数指定要对每个文档执行的操作。 我们的示例使用 upload
,如果文档不存在,则添加文档;如果文档不存在,则更新文档。
查询索引
现在,文档已加载到索引中,可以使用全文搜索在其字段中查找特定字词或短语。
若要针对索引运行全文查询,请执行:
$url
更新对象以指定搜索参数。 将<YOUR-SEARCH-SERVICE>
替换为在 Get 终结点中获取的值。$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2024-07-01&search=attached restaurant&searchFields=Description,Tags&$select=HotelId,HotelName,Tags,Description&$count=true'
运行
Invoke-RestMethod
以将查询请求发送到搜索服务。Invoke-RestMethod -Uri $url -Headers $headers | ConvertTo-Json
响应应类似于以下示例,其中显示了一个匹配的酒店文档、其相关性分数及其所选字段。
{ "@odata.context": "https://my-service.search.azure.cn/indexes('hotels-quickstart')/$metadata#docs(*)", "@odata.count": 1, "value": [ { "@search.score": 0.5575875, "HotelId": "3", "HotelName": "Gastronomic Landscape Hotel", "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.", "Tags": "restaurant bar continental breakfast" } ] }
关于查询请求
本快速入门调用 Documents - Search Post (REST API) 以查找与搜索条件匹配的酒店文档。 URI 仍针对 docs
集合,但不再包括 index
操作。
全文搜索请求始终包含 search
包含查询文本的参数。 查询文本可以包含一个或多个字词、短语或运算符。 此外 search
,还可以指定其他参数来优化搜索行为和结果。
我们的查询在每个酒店文档的Description
和Tags
字段中搜索术语“附属餐厅”。 该$select
参数将响应中返回的字段限制为HotelId
、HotelName
Tags
和Description
。 参数 $count
请求匹配文档的总数。
其他查询示例
运行以下命令以浏览查询语法。 可以执行字符串搜索、使用 $filter
表达式、限制结果集、选择特定字段等。 请记住,用您在<YOUR-SEARCH-SERVICE>
中获得的值替换。
# Query example 1
# Search the index for the terms 'restaurant' and 'wifi'
# Return only the HotelName, Description, and Tags fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2024-07-01&search=restaurant wifi&$count=true&$select=HotelName,Description,Tags'
# Query example 2
# Use a filter to find hotels rated 4 or higher
# Return only the HotelName and Rating fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2024-07-01&search=*&$filter=Rating gt 4&$select=HotelName,Rating'
# Query example 3
# Take the top two results
# Return only the HotelName and Category fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2024-07-01&search=boutique&$top=2&$select=HotelName,Category'
# Query example 4
# Sort by a specific field (Address/City) in ascending order
# Return only the HotelName, Address/City, Tags, and Rating fields
$url = '<YOUR-SEARCH-SERVICE>/indexes/hotels-quickstart/docs?api-version=2024-07-01&search=pool&$orderby=Address/City asc&$select=HotelName, Address/City, Tags, Rating'
在本快速入门中,你将使用 Azure.Search.Documents 客户端库通过示例数据创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 进行索引和查询,使用 BM25 排名算法对结果进行评分。
本快速入门创建和查询包含有关四家酒店的数据的小酒店快速入门索引。
小提示
可以下载并运行一个已完成的笔记本。
先决条件
- 拥有有效订阅的 Azure 帐户。 创建试用版订阅。
- Azure AI 搜索服务。 如果还没有,请创建服务。 对于这个快速入门,您可以使用免费服务。
- 具有 Python 扩展的 Visual Studio Code 或 Python 3.10 或更高版本的等效 IDE。 如果尚未安装合适的 Python 版本,请按照 VS Code Python 教程中的说明进行作。
Microsoft Entra ID 先决条件
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,你需要:
- 安装使用 Microsoft Entra ID 进行无密钥身份验证所需的 Azure CLI。
- 将
Search Service Contributor
和Search Index Data Contributor
两个角色分配给你的用户帐户。 你可以在 Azure 门户的“访问控制(IAM)”“添加角色分配”下分配角色。> 有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
检索资源信息
需要检索以下信息才能使用 Azure AI 搜索服务对应用程序进行身份验证:
变量名称 | 价值 |
---|---|
SEARCH_API_ENDPOINT |
可以在 Azure 门户中找到此值。 选择搜索服务,然后从左侧菜单中选择“概述”。
Essentials 下的 URL 值是所需的终结点。 示例终结点可能类似于 https://mydemo.search.azure.cn 。 |
设置你的环境
在 Jupyter 笔记本中运行示例代码。 因此,需要设置环境以运行 Jupyter 笔记本。
下载或复制来自 GitHub 的示例笔记本。
在 Visual Studio Code 中打开笔记本。
创建一个新的 Python 环境,用于安装本教程所需的包。
重要
请勿将包安装到你的全局 Python 安装中。 安装 Python 包时,应始终使用虚拟或 conda 环境,否则可能会中断 Python 的全局安装。
py -3 -m venv .venv .venv\scripts\activate
设置可能需要 1 分钟。 如果遇到问题,请参阅 VS Code 中的 Python 环境。
如果还没有 Jupyter 笔记本,请安装 Jupyter 笔记本和 IPython 内核。
pip install jupyter pip install ipykernel python -m ipykernel install --user --name=.venv
选择笔记本内核。
- 在笔记本的右上角,选择“选择内核”。
- 如果在列表中看到
.venv
,请选择它。 如果未看到,请选择“选择另一个内核”“Python 环境”>>.venv
。
创建、加载和查询搜索索引
在本部分中,你将添加代码以创建搜索索引、使用文档加载它并运行查询。 运行程序以查看控制台中的结果。 有关代码的详细说明,请参阅说明代码部分。
确保笔记本在
.venv
内核中打开,如上一部分所述。运行第一个代码单元以安装所需的包,包括 azure-search-documents。
! pip install azure-search-documents==11.6.0b1 --quiet ! pip install azure-identity --quiet ! pip install python-dotenv --quiet
根据身份验证方法,将第二个代码单元的内容替换为以下代码。
注释
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将
DefaultAzureCredential
对象替换为AzureKeyCredential
对象。from azure.core.credentials import AzureKeyCredential from azure.identity import DefaultAzureCredential, AzureAuthorityHosts search_endpoint: str = "https://<Put your search service NAME here>.search.azure.cn/" authority = AzureAuthorityHosts.AZURE_CHINA_CLOUD credential = DefaultAzureCredential(authority=authority) index_name: str = "hotels-quickstart-python"
从“创建索引”代码单元格移除以下两行。 已在上一个代码单元中设置凭据。
from azure.core.credentials import AzureKeyCredential credential = AzureKeyCredential(search_api_key)
运行“创建索引”代码单元来创建搜索索引。
按顺序运行其余代码单元以加载文档并运行查询。
解释代码
创建索引
SearchIndexClient
用于创建和管理 Azure AI 搜索的索引。 每个字段由name
标识,并具有指定的type
。
每个字段还包含一系列索引属性,这些属性指定 Azure AI 搜索是否可以根据字段进行搜索、筛选、排序和创建分面。 大多数字段采用简单数据类型,但有些字段(例如 AddressType
)采用复杂类型,可让你在索引中创建丰富的数据结构。 可详细了解支持的数据类型,以及创建索引 (REST) 中所述的索引属性。
创建文档有效负载并上传文档
针对操作类型(上传或合并上传等)使用索引操作。 文档源自 GitHub 上的 HotelsData 示例。
搜索索引
对第一个文档编制索引后,可立即获取查询结果,但索引的实际测试应等到对所有文档编制索引后进行。
使用 search.client 类的“search”方法。
笔记本中的示例查询包括:
- 基础查询:执行空搜索 (
search=*
),返回任意文档的未排名列表 (search score = 1.0)。 由于没有条件,因此所有文档都包含在结果中。 - 术语查询:将整个术语添加到搜索表达式 ("wifi")。 此查询指定结果仅包含
select
语句中的那些字段。 限制返回的字段可最大程度地减少通过网络发回的数据量,并降低搜索延迟。 - 筛选查询:添加筛选表达式,仅返回评分高于 4 的酒店(按降序排列)。
- 字段范围:添加
search_fields
到特定字段的范围查询执行。 - 方面:为搜索结果中找到的正匹配生成方面。 没有零匹配项。 如果搜索结果不包括 wifi 一词,则 wifi 不会出现在分面导航结构中。
- 查找文档:基于文档的键返回文档。 如果要在用户选择搜索结果中的项时提供钻取,此操作非常有用。
- 自动完成:在用户在搜索框中键入时提供可能的匹配项。 “自动完成”使用建议器 (
sg
) 来了解哪些字段包含建议器请求的潜在匹配。 在本快速入门中,这些字段为Tags
、Address/City
、Address/Country
。 若要模拟自动完成,请输入字母“sa”作为字符串的一部分。 SearchClient 的自动完成方法会发回可能的术语匹配。
移除索引
如果已完成此索引,可以通过运行“清理”代码单元将其删除。 删除不必要的索引可以释放空间,以便逐步完成更多快速入门和教程。
在本快速入门中,你将使用 Azure AI 搜索 REST API 创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 编制索引和查询,使用 BM25 排名算法来评分结果。
本快速入门使用 azure-search-sample-data 存储库中的虚构酒店数据来填充索引。
小提示
可以下载 源代码 以从已完成的项目开始,或按照以下步骤创建自己的项目。
先决条件
配置访问权限
可以使用 API 密钥或具有角色分配的 Microsoft Entra ID 连接到 Azure AI 搜索服务。 使用密钥更容易上手,但使用角色更安全。
配置建议的基于角色的访问:
登录到 Azure 门户 并选择搜索服务。
在左窗格中,选择“设置”“密钥”。>
在“API 访问控制”下,选择两项。
此选项同时启用基于密钥的身份验证和无密钥身份验证。 分配角色后,可以返回到此步骤并选择 基于角色的访问控制。
在左窗格中,选择“访问控制”(IAM)。
选择添加>添加角色分配。
将 搜索服务参与者 和 搜索索引数据参与者 角色分配给用户帐户。
有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
获取终结点和令牌
在下一部分中,请指定以下终结点和令牌来建立与 Azure AI 搜索服务的连接。 这些步骤假定你 配置了基于角色的访问。
若要获取服务终结点和令牌,请按以下步骤进行操作:
登录到 Azure 门户 并选择搜索服务。
在左窗格中,选择“ 概述”。
记下 URL,该 URL 应类似于
https://my-service.search.azure.cn
。在本地系统上,打开终端。
登录到 Azure 订阅。 如果有多个订阅,请选择包含搜索服务的订阅。
az cloud set -n AzureChinaCloud az login # az cloud set -n AzureCloud //means return to Public Azure.
记下 Microsoft Entra 令牌。
az account get-access-token --scope https://search.azure.com/.default
设置文件
必须先创建一个文件来存储服务终结点、身份验证令牌和最终请求,然后才能对 Azure AI 搜索服务进行 REST API 调用。 Visual Studio Code 中的 REST 客户端扩展支持此任务。
若要建立请求文件,请执行以下步骤:
在本地系统上,打开 Visual Studio Code。
创建
.rest
或.http
文件。将以下占位符和请求粘贴到文件中。
@baseUrl = PUT-YOUR-SEARCH-SERVICE-ENDPOINT-HERE @token = PUT-YOUR-PERSONAL-IDENTITY-TOKEN-HERE ### List existing indexes by name GET {{baseUrl}}/indexes?api-version=2024-07-01 HTTP/1.1 Authorization: Bearer {{token}}
将
@baseUrl
和@token
占位符替换为在 Get 终结点和令牌中获取的值。 不要包含引号。在
### List existing indexes by name
下,选择“发送请求”。相邻窗格中应该会显示响应。 如果有现有索引,则会列出它们。 否则,列表将为空。 如果 HTTP 代码为
200 OK
,则你已经做好了执行后续步骤的准备。
创建搜索索引
在将内容添加到 Azure AI 搜索之前,必须创建一个索引来定义内容的存储和结构化方式。 索引在概念上类似于关系数据库中的表,但它专为搜索作(如全文搜索)而设计。
创建索引:
将以下请求粘贴到文件中。
### Create a new index POST {{baseUrl}}/indexes?api-version=2024-07-01 HTTP/1.1 Content-Type: application/json Authorization: Bearer {{token}} { "name": "hotels-quickstart", "fields": [ {"name": "HotelId", "type": "Edm.String", "key": true, "filterable": true}, {"name": "HotelName", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": true, "facetable": false}, {"name": "Description", "type": "Edm.String", "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzer": "en.lucene"}, {"name": "Category", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true}, {"name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true}, {"name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true}, {"name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true}, {"name": "Address", "type": "Edm.ComplexType", "fields": [ {"name": "StreetAddress", "type": "Edm.String", "filterable": false, "sortable": false, "facetable": false, "searchable": true}, {"name": "City", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "StateProvince", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "PostalCode", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true}, {"name": "Country", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "facetable": true} ] } ] }
在
### Create a new index
下,选择“发送请求”。应会收到一个
HTTP/1.1 201 Created
响应,其正文包含索引架构的 JSON 表示形式。
关于创建索引请求
本快速入门调用索引 - 创建 (REST API) 以在搜索服务上生成名为 hotels-quickstart
的搜索索引及其物理数据结构。
在索引架构中, fields
集合定义酒店文档的结构。 每个字段都有一个 name
、数据和 type
属性,用于确定其索引和查询期间的行为。 该 HotelId
字段标记为密钥,Azure AI 搜索需要唯一标识索引中的每个文档。
有关索引架构的要点:
使用字符串字段 (
Edm.String
) 使数值数据全文可搜索。 其他受支持的数据类型,例如Edm.Int32
,可筛选、可排序、可分面和可检索,但不可搜索。我们的大多数字段都是简单的数据类型,但你可以定义复杂类型来表示嵌套数据,例如
Address
字段。字段属性确定允许的操作。 默认情况下,REST API 允许很多操作。 例如,所有字符串都是可搜索和可检索的。 使用 REST API 时,仅当需要禁用行为时,才能使用属性。
加载索引
新创建的索引为空。 若要填充索引并使其可搜索,必须上传符合索引架构的 JSON 文档。
在 Azure AI 搜索中,文档既充当索引输入,也充当查询输出的输入。 为简单起见,本快速入门以内联 JSON 形式提供示例酒店文档。 但是,在生产方案中,内容通常从连接的数据源中提取,并使用 索引器转换为 JSON。
将文档上传到索引:
将以下请求粘贴到文件中。
### Upload documents POST {{baseUrl}}/indexes/hotels-quickstart/docs/index?api-version=2024-07-01 HTTP/1.1 Content-Type: application/json Authorization: Bearer {{token}} { "value": [ { "@search.action": "upload", "HotelId": "1", "HotelName": "Stay-Kay City Hotel", "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.", "Category": "Boutique", "Tags": [ "view", "air conditioning", "concierge" ], "ParkingIncluded": false, "LastRenovationDate": "2022-01-18T00:00:00Z", "Rating": 3.60, "Address": { "StreetAddress": "677 5th Ave", "City": "Beijing", "StateProvince": "NY", "PostalCode": "10022", "Country": "USA" } }, { "@search.action": "upload", "HotelId": "2", "HotelName": "Old Century Hotel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.", "Category": "Boutique", "Tags": [ "pool", "free wifi", "concierge" ], "ParkingIncluded": false, "LastRenovationDate": "2019-02-18T00:00:00Z", "Rating": 3.60, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243", "Country": "USA" } }, { "@search.action": "upload", "HotelId": "3", "HotelName": "Gastronomic Landscape Hotel", "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.", "Category": "Suite", "Tags": [ "restaurant", "bar", "continental breakfast" ], "ParkingIncluded": true, "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.80, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326", "Country": "USA" } }, { "@search.action": "upload", "HotelId": "4", "HotelName": "Sublime Palace Hotel", "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.", "Category": "Luxury", "Tags": [ "concierge", "view", "air conditioning" ], "ParkingIncluded": true, "LastRenovationDate": "2020-02-06T00:00:00Z", "Rating": 4.60, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216", "Country": "USA" } } ] }
在
### Upload documents
下,选择“发送请求”。您将收到一个
HTTP/1.1 200 OK
响应,其正文包含每个上传文档的密钥和状态。
关于上传请求
本快速入门调用 Documents - Index (REST API) 将四个示例酒店文档添加到索引。 与上一个请求相比,URI 扩展为包含 docs
集合和 index
操作。
数组中的每个 value
文档都表示一个酒店,并包含与索引架构匹配的字段。 该 @search.action
参数指定要对每个文档执行的操作。 我们的示例使用 upload
,如果文档不存在,则添加文档;如果文档不存在,则更新文档。
查询索引
现在,文档已加载到索引中,可以使用全文搜索在其字段中查找特定字词或短语。
若要针对索引运行全文查询,请执行:
将以下请求粘贴到文件中。
### Run a query POST {{baseUrl}}/indexes/hotels-quickstart/docs/search?api-version=2024-07-01 HTTP/1.1 Content-Type: application/json Authorization: Bearer {{token}} { "search": "attached restaurant", "select": "HotelId, HotelName, Tags, Description", "searchFields": "Description, Tags", "count": true }
在
### Run a query
下,选择“发送请求”。应会收到类似于以下示例的
HTTP/1.1 200 OK
响应,其中显示了一个匹配的酒店文档、其相关性分数及其所选字段。{ "@odata.context": "https://my-service.search.azure.cn/indexes('hotels-quickstart')/$metadata#docs(*)", "@odata.count": 1, "value": [ { "@search.score": 0.5575875, "HotelId": "3", "HotelName": "Gastronomic Landscape Hotel", "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel\u2019s restaurant services.", "Tags": [ "restaurant", "bar", "continental breakfast" ] } ] }
关于查询请求
本快速入门调用 Documents - Search Post (REST API) 以查找与搜索条件匹配的酒店文档。 URI 现在针对 /docs/search
操作。
全文搜索请求始终包含 search
包含查询文本的参数。 查询文本可以包含一个或多个字词、短语或运算符。 此外 search
,还可以指定其他参数来优化搜索行为和结果。
我们的查询在每个酒店文档的Description
和Tags
字段中搜索术语“附属餐厅”。 该select
参数将响应中返回的字段限制为HotelId
、HotelName
Tags
和Description
。 参数 count
请求匹配文档的总数。
在本快速入门中,你将使用 Azure.Search.Documents 客户端库通过示例数据创建、加载和查询搜索索引,以便 进行全文搜索。 全文搜索使用 Apache Lucene 进行索引和查询,使用 BM25 排名算法对结果进行评分。
本快速入门创建和查询包含有关四家酒店的数据的小酒店快速入门索引。
小提示
可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
先决条件
Microsoft Entra ID 先决条件
若要使用 Microsoft Entra ID 进行推荐的无密钥身份验证,你需要:
- 安装使用 Microsoft Entra ID 进行无密钥身份验证所需的 Azure CLI。
- 将
Search Service Contributor
和Search Index Data Contributor
两个角色分配给你的用户帐户。 你可以在 Azure 门户的“访问控制(IAM)”“添加角色分配”下分配角色。> 有关详细信息,请参阅使用角色连接到 Azure AI 搜索。
检索资源信息
需要检索以下信息才能使用 Azure AI 搜索服务对应用程序进行身份验证:
变量名称 | 价值 |
---|---|
SEARCH_API_ENDPOINT |
可以在 Azure 门户中找到此值。 选择搜索服务,然后从左侧菜单中选择“概述”。
Essentials 下的 URL 值是所需的终结点。 示例终结点可能类似于 https://mydemo.search.azure.cn 。 |
设置
创建一个新文件夹
full-text-quickstart
以包含该应用程序,并在该文件夹中使用以下命令打开 Visual Studio Code:mkdir full-text-quickstart && cd full-text-quickstart
使用以下命令创建
package.json
:npm init -y
使用以下命令将
package.json
更新为 ECMAScript:npm pkg set type=module
安装适用于 JavaScript 的 Azure AI 搜索客户端库 (Azure.Search.Documents)
npm install @azure/search-documents
对于建议的无密码身份验证,请使用以下命令安装 Azure 标识客户端库:
npm install @azure/identity
创建、加载和查询搜索索引
在前面的设置部分中,你安装了 Azure AI 搜索客户端库和其他依赖项。
在本部分中,你将添加代码以创建搜索索引、使用文档加载它并运行查询。 运行程序以查看控制台中的结果。 有关代码的详细说明,请参阅说明代码部分。
本快速入门中的示例代码使用 Microsoft Entra ID 进行推荐的无密钥身份验证。 如果希望使用 API 密钥,则可以将 DefaultAzureCredential
对象替换为 AzureKeyCredential
对象。
const searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/";
const credential = new DefaultAzureCredential();
创建名为 index.ts 的新文件,并将以下代码粘贴到 index.ts 中:
// Import from the @azure/search-documents library import { SearchIndexClient, SearchClient, SearchFieldDataType, AzureKeyCredential, odata, SearchIndex } from "@azure/search-documents"; // Import from the Azure Identity library import { DefaultAzureCredential } from "@azure/identity"; // Importing the hotels sample data import hotelData from './hotels.json' assert { type: "json" }; // Load the .env file if it exists import * as dotenv from "dotenv"; dotenv.config(); // Defining the index definition const indexDefinition: SearchIndex = { "name": "hotels-quickstart", "fields": [ { "name": "HotelId", "type": "Edm.String" as SearchFieldDataType, "key": true, "filterable": true }, { "name": "HotelName", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": true, "facetable": false }, { "name": "Description", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "en.lucene" }, { "name": "Category", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Tags", "type": "Collection(Edm.String)", "searchable": true, "filterable": true, "sortable": false, "facetable": true }, { "name": "ParkingIncluded", "type": "Edm.Boolean", "filterable": true, "sortable": true, "facetable": true }, { "name": "LastRenovationDate", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "facetable": true }, { "name": "Rating", "type": "Edm.Double", "filterable": true, "sortable": true, "facetable": true }, { "name": "Address", "type": "Edm.ComplexType", "fields": [ { "name": "StreetAddress", "type": "Edm.String" as SearchFieldDataType, "filterable": false, "sortable": false, "facetable": false, "searchable": true }, { "name": "City", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "StateProvince", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "PostalCode", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true }, { "name": "Country", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": true, "sortable": true, "facetable": true } ] } ], "suggesters": [ { "name": "sg", "searchMode": "analyzingInfixMatching", "sourceFields": [ "HotelName" ] } ] }; async function main() { // Your search service endpoint const searchServiceEndpoint = "https://<Put your search service NAME here>.search.azure.cn/"; // Use the recommended keyless credential instead of the AzureKeyCredential credential. const credential = new DefaultAzureCredential(); //const credential = new AzureKeyCredential(Your search service admin key); // Create a SearchIndexClient to send create/delete index commands const searchIndexClient: SearchIndexClient = new SearchIndexClient( searchServiceEndpoint, credential ); // Creating a search client to upload documents and issue queries const indexName: string = "hotels-quickstart"; const searchClient: SearchClient<any> = searchIndexClient.getSearchClient(indexName); console.log('Checking if index exists...'); await deleteIndexIfExists(searchIndexClient, indexName); console.log('Creating index...'); let index: SearchIndex = await searchIndexClient.createIndex(indexDefinition); console.log(`Index named ${index.name} has been created.`); console.log('Uploading documents...'); let indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotelData['value']); console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)} `); // waiting one second for indexing to complete (for demo purposes only) sleep(1000); console.log('Querying the index...'); console.log(); await sendQueries(searchClient); } async function deleteIndexIfExists(searchIndexClient: SearchIndexClient, indexName: string) { try { await searchIndexClient.deleteIndex(indexName); console.log('Deleting index...'); } catch { console.log('Index does not exist yet.'); } } async function sendQueries(searchClient: SearchClient<any>) { // Query 1 console.log('Query #1 - search everything:'); let searchOptions: any = { includeTotalCount: true, select: ["HotelId", "HotelName", "Rating"] }; let searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(`Result count: ${searchResults.count}`); console.log(); // Query 2 console.log('Query #2 - search with filter, orderBy, and select:'); let state = 'FL'; searchOptions = { filter: odata`Address/StateProvince eq ${state}`, orderBy: ["Rating desc"], select: ["HotelId", "HotelName", "Rating"] }; searchResults = await searchClient.search("wifi", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 3 console.log('Query #3 - limit searchFields:'); searchOptions = { select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("sublime cliff", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 4 console.log('Query #4 - limit searchFields and use facets:'); searchOptions = { facets: ["Category"], select: ["HotelId", "HotelName", "Rating"], searchFields: ["HotelName"] }; searchResults = await searchClient.search("*", searchOptions); for await (const result of searchResults.results) { console.log(`${JSON.stringify(result.document)}`); } console.log(); // Query 5 console.log('Query #5 - Lookup document:'); let documentResult = await searchClient.getDocument('3'); console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`); console.log(); } function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); } main().catch((err) => { console.error("The sample encountered an error:", err); });
创建名为 hotels.json 的文件,并将以下代码粘贴到 hotels.json 中:
{ "value": [ { "HotelId": "1", "HotelName": "Stay-Kay City Hotel", "Description": "This classic hotel is fully-refurbished and ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make Beijing one of America's most attractive and cosmopolitan cities.", "Category": "Boutique", "Tags": ["view", "air conditioning", "concierge"], "ParkingIncluded": false, "LastRenovationDate": "2022-01-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "677 5th Ave", "City": "Beijing", "StateProvince": "NY", "PostalCode": "10022" } }, { "HotelId": "2", "HotelName": "Old Century Hotel", "Description": "The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts. The hotel also regularly hosts events like wine tastings, beer dinners, and live music.", "Category": "Boutique", "Tags": ["pool", "free wifi", "concierge"], "ParkingIncluded": "false", "LastRenovationDate": "2019-02-18T00:00:00Z", "Rating": 3.6, "Address": { "StreetAddress": "140 University Town Center Dr", "City": "Sarasota", "StateProvince": "FL", "PostalCode": "34243" } }, { "HotelId": "3", "HotelName": "Gastronomic Landscape Hotel", "Description": "The Gastronomic Hotel stands out for its culinary excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.", "Category": "Suite", "Tags": ["restaurant, "bar", "continental breakfast"], "ParkingIncluded": "true", "LastRenovationDate": "2015-09-20T00:00:00Z", "Rating": 4.8, "Address": { "StreetAddress": "3393 Peachtree Rd", "City": "Atlanta", "StateProvince": "GA", "PostalCode": "30326" } }, { "HotelId": "4", "HotelName": "Sublime Palace Hotel", "Description": "Sublime Palace Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 19th century resort, updated for every modern convenience.", "Category": "Boutique", "Tags": ["concierge", "view", "air conditioning"], "ParkingIncluded": true, "LastRenovationDate": "2020-02-06T00:00:00Z", "Rating": 4.6, "Address": { "StreetAddress": "7400 San Pedro Ave", "City": "San Antonio", "StateProvince": "TX", "PostalCode": "78216" } } ] }
创建
tsconfig.json
文件以转译 TypeScript 代码,然后复制以下 ECMAScript 代码。{ "compilerOptions": { "module": "NodeNext", "target": "ES2022", // Supports top-level await "moduleResolution": "NodeNext", "skipLibCheck": true, // Avoid type errors from node_modules "strict": true // Enable strict type-checking options }, "include": ["*.ts"] }
从 TypeScript 转译到 JavaScript。
tsc
使用以下命令登录到 Azure:
az cloud set -n AzureChinaCloud az login # az cloud set -n AzureCloud //means return to Public Azure.
使用以下命令运行 JavaScript 代码:
node index.js
解释代码
创建索引
创建文件 hotels_quickstart_index.json。 此文件定义 Azure AI 搜索如何处理要在下一步骤中加载的文档。 每个字段均由 name
标识,并具有指定的 type
。 每个字段还包含一系列索引属性,这些属性指定 Azure AI 搜索是否可以根据字段进行搜索、筛选、排序和创建分面。 大多数字段采用简单数据类型,但有些字段(例如 AddressType
)采用复杂类型,可让你在索引中创建丰富的数据结构。 可详细了解支持的数据类型,以及创建索引 (REST) 中所述的索引属性。
我们希望导入 hotels_quickstart_index.json,以便主函数可以访问索引定义。
import indexDefinition from './hotels_quickstart_index.json';
interface HotelIndexDefinition {
name: string;
fields: SimpleField[] | ComplexField[];
suggesters: SearchSuggester[];
};
const hotelIndexDefinition: HotelIndexDefinition = indexDefinition as HotelIndexDefinition;
然后在 main 函数中,我们创建一个 SearchIndexClient
用来创建和管理 Azure AI 搜索索引。
const indexClient = new SearchIndexClient(endpoint, new AzureKeyCredential(apiKey));
接下来,我们要删除该索引(如果它已存在)。 这是测试/演示代码的常见做法。
为此,我们要定义一个简单函数,它会尝试删除索引。
async function deleteIndexIfExists(indexClient: SearchIndexClient, indexName: string): Promise<void> {
try {
await indexClient.deleteIndex(indexName);
console.log('Deleting index...');
} catch {
console.log('Index does not exist yet.');
}
}
为了运行该函数,我们将从索引定义中提取索引名称,并将 indexName
连同 indexClient
传递给 deleteIndexIfExists()
函数。
// Getting the name of the index from the index definition
const indexName: string = hotelIndexDefinition.name;
console.log('Checking if index exists...');
await deleteIndexIfExists(indexClient, indexName);
之后,我们就可用 createIndex()
方法创建索引了。
console.log('Creating index...');
let index = await indexClient.createIndex(hotelIndexDefinition);
console.log(`Index named ${index.name} has been created.`);
加载文档
在 Azure AI 搜索中,文档这一数据结构既是索引输入,也是查询输出。 可将此类数据推送到索引,或者使用索引器。 在这种情况下,我们将以编程方式将文档推送到索引。
文档输入可以是数据库中的行、Blob 存储中的 Blob,或磁盘上的 JSON 文档(在本示例中为 JSON 文档)。 可以下载 hotels.json,或创建包含以下内容的 hotels.json 文件:
与我们使用 indexDefinition 执行的操作类似,我们还需要在 index.ts 顶部导入 hotels.json
,以便可在我们的 main 函数中访问数据。
import hotelData from './hotels.json';
interface Hotel {
HotelId: string;
HotelName: string;
Description: string;
Category: string;
Tags: string[];
ParkingIncluded: string | boolean;
LastRenovationDate: string;
Rating: number;
Address: {
StreetAddress: string;
City: string;
StateProvince: string;
PostalCode: string;
};
};
const hotels: Hotel[] = hotelData["value"];
为了将数据编入搜索索引中,现在需要创建 SearchClient
。 虽然 SearchIndexClient
用于创建和管理索引,但 SearchClient
用于上传文档和查询索引。
创建 SearchClient
的方法有两种。 第一种方法是从头开始创建 SearchClient
:
const searchClient = new SearchClient<Hotel>(endpoint, indexName, new AzureKeyCredential(apiKey));
或者,你可使用 getSearchClient()
的 SearchIndexClient
方法来创建 SearchClient
:
const searchClient = indexClient.getSearchClient<Hotel>(indexName);
现在已定义了客户端,需将文档上传到搜索索引。 在此示例中,我们使用 mergeOrUploadDocuments()
方法,该方法将上传文档;如果已存在具有相同密钥的文档,则它会将这些文档与现有文档合并。 然后检查操作是否成功,因为至少存在第一个文档。
console.log("Uploading documents...");
const indexDocumentsResult = await searchClient.mergeOrUploadDocuments(hotels);
console.log(`Index operations succeeded: ${JSON.stringify(indexDocumentsResult.results[0].succeeded)}`);
使用 tsc && node index.ts
再次运行程序。 应会看到与步骤 1 中显示的消息略有不同的一系列消息。 这一次,索引确实存在,而且你会看到一条消息,它显示在应用创建新索引并向其发布数据之前删除此索引。
在下一步中运行查询之前,请定义一个函数,使程序等待一秒钟。 仅出于测试/演示目的这样做,目的是确保索引完成且文档可在索引中用于查询。
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
若要让程序等待一秒钟,请调用 sleep
函数:
sleep(1000);
搜索索引
创建索引并上传文档后,便可将查询发送到索引。 在本部分中,我们将向搜索索引发送 5 个不同的查询,以演示可供你使用的不同查询功能部分。
在 sendQueries()
函数中编写查询,我们将在 main 函数中调用此函数,如下所示:
await sendQueries(searchClient);
使用 search()
的 searchClient
方法发送查询。 第一个参数是搜索文本,第二个参数指定搜索选项。
查询示例 1
第一个查询会搜索 *
,这等效于搜索所有内容并选择索引中的三个字段。 最佳做法是通过 select
仅选择你需要的字段,因为回发不必要的数据可能会增加查询的延迟时间。
此查询的 searchOptions
还将 includeTotalCount
设置为 true
,这将返回找到的匹配结果数。
async function sendQueries(
searchClient: SearchClient<Hotel>
): Promise<void> {
// Query 1
console.log('Query #1 - search everything:');
const selectFields: SearchFieldArray<Hotel> = [
"HotelId",
"HotelName",
"Rating",
];
const searchOptions1 = {
includeTotalCount: true,
select: selectFields
};
let searchResults = await searchClient.search("*", searchOptions1);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
console.log(`Result count: ${searchResults.count}`);
// remaining queries go here
}
还应将下述其余查询添加到 sendQueries()
函数。 为了方便阅读,此处将它们分开。
查询示例 2
在下一个查询中,我们指定搜索词 "wifi"
,还包括一个筛选器,以仅返回状态等于 'FL'
的结果。 还会按酒店的 Rating
对结果进行排序。
console.log('Query #2 - search with filter, orderBy, and select:');
let state = 'FL';
const searchOptions2 = {
filter: odata`Address/StateProvince eq ${state}`,
orderBy: ["Rating desc"],
select: selectFields
};
searchResults = await searchClient.search("wifi", searchOptions2);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
查询示例 3
接下来,使用 searchFields
参数将搜索限制为单个可搜索字段。 如果你知道自己只对某些字段中的匹配感兴趣,则很适合使用该选项来提高查询的效率。
console.log('Query #3 - limit searchFields:');
const searchOptions3 = {
select: selectFields,
searchFields: ["HotelName"] as const
};
searchResults = await searchClient.search("Sublime Palace", searchOptions3);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
查询示例 4
查询中包含的另一个常见选项是 facets
。 方面允许从 UI 中的结果提供自定向向下钻取。 可以将方面结果转换为结果窗格中的复选框。
console.log('Query #4 - limit searchFields and use facets:');
const searchOptions4 = {
facets: ["Category"],
select: selectFields,
searchFields: ["HotelName"] as const
};
searchResults = await searchClient.search("*", searchOptions4);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
查询示例 5
最终查询使用 getDocument()
的 searchClient
方法。 这样,你就可通过文档的密钥有效地检索文档。
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
查询摘要
前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。
全文搜索和筛选器是使用 searchClient.search
方法执行的。 搜索查询可在searchText
字符串中传递,而筛选表达式则可在 filter
类的 SearchOptions
属性中传递。 若要筛选但不搜索,只需传递“*”作为 searchText
方法的 search
参数。 若要在不筛选的情况下进行搜索,请保持 filter
属性未设置,或者根本不传入 SearchOptions
实例。
清理资源
在自己的订阅中操作时,最好在项目结束时确定是否仍需要已创建的资源。 持续运行资源可能会产生费用。 可以逐个删除资源,也可以删除资源组以删除整个资源集。
你可以在 Azure 门户中查找和管理资源,只需使用左侧导航窗格中的“所有资源”或“资源组”链接即可。
如果使用的是免费服务,请记住只能设置三个索引、索引器和数据源。 可以在 Azure 门户中删除单个项目,以不超出此限制。