使用 Azure.Search.Documents 客户端库构建一款控制台应用程序,用来创建、加载程查询搜索索引。
或者,可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
设置你的环境
启动 Visual Studio 并为控制台应用创建新项目。
在“工具”>“NuGet 包管理器”中,选择“管理解决方案的 NuGet 包...”。
选择“浏览”。
搜索 Azure.Search.Documents 包,并选择 11.0 或更高版本。
选择安装”,将该程序集添加到你的项目和解决方案。
创建搜索客户端
在“Program.cs”中,将命名空间更改为 AzureSearch.SDK.Quickstart.v11
,然后添加以下 using
指令。
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using Azure.Search.Documents.Models;
复制以下代码以创建两个客户端。 SearchIndexClient 创建索引,SearchClient 加载并查询现有索引。 两者都需要服务终结点和管理员 API 密钥才能使用创建/删除权限进行身份验证。
由于代码为你生成了 URI,因此只需在 serviceName
属性中指定搜索服务名称。
static void Main(string[] args)
{
string serviceName = "<your-search-service-name>";
string apiKey = "<your-search-service-admin-api-key>";
string indexName = "hotels-quickstart";
// Create a SearchIndexClient to send create/delete index commands
Uri serviceEndpoint = new Uri($"https://{serviceName}.search.azure.cn/");
AzureKeyCredential credential = new AzureKeyCredential(apiKey);
SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);
// Create a SearchClient to load and query documents
SearchClient srchclient = new SearchClient(serviceEndpoint, indexName, credential);
. . .
}
创建索引
本快速入门生成 Hotels 索引,你将在其中加载酒店数据并对其执行查询。 在此步骤中,定义索引中的字段。 每个字段定义都包含名称、数据类型以及确定如何使用该字段的属性。
在此示例中,为了简单和可读性,使用了 Azure.Search.Documents 库的同步方法。 但是,对于生产场景,应使用异步方法来保持应用程序的可缩放性和响应性。 例如,使用 CreateIndexAsync,而不是 CreateIndex。
向项目添加一个空的类定义:Hotel.cs
将以下代码复制到 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(AnalyzerName = LexicalAnalyzerName.Values.FrLucene)]
[JsonPropertyName("Description_fr")]
public string DescriptionFr { 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; }
}
}
在 Azure.Search.Documents 客户端库中,可以使用 SearchableField 和 SimpleField 来简化字段定义。 两者都是 SearchField 的派生形式,可能会简化你的代码:
SimpleField
可以是任何数据类型,始终不可搜索(全文搜索查询将忽略它),并且可检索(未隐藏)。 其他属性默认情况下处于关闭状态,但可以启用。 你可能会将 SimpleField
用于仅在筛选器、facet 或计分概要文件中使用的文档 ID 或字段。 如果是这样,请确保应用该方案所需的所有属性,例如为文档 ID 应用 IsKey = true
。 有关详细信息,请参阅源代码中的 SimpleFieldAttribute.cs。
SearchableField
必须是字符串,并且始终可搜索、可检索。 其他属性默认情况下处于关闭状态,但可以启用。 因为此字段类型是可搜索的,所以它支持同义词和分析器属性的完整补集。 有关详细信息,请参阅源代码中的 SearchableFieldAttribute.cs。
无论使用基本 SearchField
API 还是任一帮助程序模型,都必须显式启用筛选器、facet 和排序属性。 例如,IsFilterable、IsSortable 和 IsFacetable 必须进行显式属性化,如上一示例所示。
向项目添加第二个空的类定义: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 和 Address.Methods.cs,以重写 ToString()
。 这些类用于在控制台输出中呈现搜索结果。 本文未提供这些类的内容,但可以从 GitHub 中的文件里复制代码。
在 Program.cs 中,创建一个 SearchIndex 对象,然后调用 CreateIndex 方法来表示搜索服务中的索引。 此索引还包括一个 SearchSuggester 以便在指定字段上启用自动完成。
// Create hotels-quickstart index
private static void CreateIndex(string indexName, SearchIndexClient adminClient)
{
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);
adminClient.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 = "Secret Point Motel",
Description = "The hotel is ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Time's 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.",
DescriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de Beijing. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de Beijing l'une des villes les plus attractives et cosmopolites de l'Amérique.",
Category = "Boutique",
Tags = new[] { "pool", "air conditioning", "concierge" },
ParkingIncluded = false,
LastRenovationDate = new DateTimeOffset(1970, 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 = "Twin Dome Motel",
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.",
DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
Category = "Boutique",
Tags = new[] { "pool", "free wifi", "concierge" },
ParkingIncluded = false,
LastRenovationDate = new DateTimeOffset(1979, 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 = "Triple Landscape Hotel",
Description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
DescriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
Category = "Resort and Spa",
Tags = new[] { "air conditioning", "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 Cliff Hotel",
Description = "Sublime Cliff 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 1800 palace.",
DescriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
Category = "Boutique",
Tags = new[] { "concierge", "view", "24-hour front desk service" },
ParkingIncluded = true,
LastRenovationDate = new DateTimeOffset(1960, 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}");
}
}
初始化 IndexDocumentsBatch 对象后,可以通过对 SearchClient 对象调用 IndexDocuments 将其发送到索引。
将以下行添加到 Main()
。 加载文档是使用 SearchClient 完成的,但此操作还需要对服务的管理员权限,通常与 SearchIndexClient 相关联。 设置此操作的一种方法是通过 SearchIndexClient
(在本示例中为 adminClient
)获取 SearchClient。
SearchClient ingesterClient = adminClient.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();
}
创建 RunQueries
方法用于执行查询并返回结果。 结果是 Hotel 对象。 此示例显示了方法签名和第一个查询。 此查询演示了 Select 参数,通过该参数可以使用文档中的选定字段来编写结果。
// Run queries, use WriteDocuments to print output
private static void RunQueries(SearchClient srchclient)
{
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 = srchclient.Search<Hotel>("*", options);
WriteDocuments(response);
在第二个查询中,搜索某个术语,添加筛选器(用于选择评级大于 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 = srchclient.Search<Hotel>("hotels", options);
WriteDocuments(response);
第三个查询演示了用于将全文搜索操作的范围限定为特定字段的 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 = srchclient.Search<Hotel>("pool", options);
WriteDocuments(response);
第四个查询演示了 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 = srchclient.Search<Hotel>("*", options);
WriteDocuments(response);
在第五个查询中,返回一个特定文档。 文档查找是对结果集中的 OnClick
事件的典型响应。
// Query 5
Console.WriteLine("Query #5: Look up a specific document...\n");
Response<Hotel> lookupResponse;
lookupResponse = srchclient.GetDocument<Hotel>("3");
Console.WriteLine(lookupResponse.Value.HotelId);
最后一个查询显示了“自动完成”的语法,它模拟部分用户输入,即“sa”,该输入解析为 sourceFields 中的两个可能的匹配项,与你在索引中定义的建议器相关联。
// Query 6
Console.WriteLine("Query #6: Call Autocomplete on HotelName that starts with 'sa'...\n");
var autoresponse = srchclient.Autocomplete("sa", "sg");
WriteDocuments(autoresponse);
已将 RunQueries
添加到 Main()
。
// Call the RunQueries method to invoke a series of queries
Console.WriteLine("Starting queries...\n");
RunQueries(srchclient);
// End the program
Console.WriteLine("{0}", "Complete. Press any key to end this program...\n");
Console.ReadKey();
前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。
全文搜索和筛选器是使用 SearchClient.Search 方法执行的。 搜索查询可在 searchText
字符串中传递,而筛选表达式则可在 SearchOptions 类的 Filter 属性中传递。 若要筛选但不搜索,只需传递 "*"
作为 Search 方法的 searchText
参数。 若要在不筛选的情况下进行搜索,请保持 Filter
属性未设置,或者根本不传入 SearchOptions
实例。
运行程序
按 F5 可重新生成应用并完整运行该程序。
输出包含 Console.WriteLine 中的消息,并添加了查询信息和结果。
使用 Jupyter 笔记本和 Azure SDK for Python 中的 azure-search-documents 库来创建、加载和查询搜索索引。
或者,可以下载并运行一个已完成的笔记本。
设置你的环境
使用带有 Python 扩展的 Visual Studio Code(或等效的 IDE),Python 版本为 3.10 或更高版本。
建议针对本快速入门使用虚拟环境:
启动 Visual Studio Code。
打开命令面板 (Ctrl+Shift+P)。
搜索“Python: 创建环境”。
选择 Venv.
选择 Python 解释器。 选择版本 3.10 或更高版本。
设置可能需要 1 分钟。 如果遇到问题,请参阅 VS Code 中的 Python 环境。
安装包并设置变量
安装包,包括 azure-search-documents。
! pip install azure-search-documents==11.6.0b1 --quiet
! pip install azure-identity --quiet
! pip install python-dotenv --quiet
提供服务的终结点和 API 密钥:
search_endpoint: str = "PUT-YOUR-SEARCH-SERVICE-ENDPOINT-HERE"
search_api_key: str = "PUT-YOUR-SEARCH-SERVICE-ADMIN-API-KEY-HERE"
index_name: str = "hotels-quickstart"
创建索引
from azure.core.credentials import AzureKeyCredential
credential = AzureKeyCredential(search_api_key)
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents import SearchClient
from azure.search.documents.indexes.models import (
ComplexField,
SimpleField,
SearchFieldDataType,
SearchableField,
SearchIndex
)
# Create a search schema
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
fields = [
SimpleField(name="HotelId", type=SearchFieldDataType.String, key=True),
SearchableField(name="HotelName", type=SearchFieldDataType.String, sortable=True),
SearchableField(name="Description", type=SearchFieldDataType.String, analyzer_name="en.lucene"),
SearchableField(name="Description_fr", type=SearchFieldDataType.String, analyzer_name="fr.lucene"),
SearchableField(name="Category", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="Tags", collection=True, type=SearchFieldDataType.String, facetable=True, filterable=True),
SimpleField(name="ParkingIncluded", type=SearchFieldDataType.Boolean, facetable=True, filterable=True, sortable=True),
SimpleField(name="LastRenovationDate", type=SearchFieldDataType.DateTimeOffset, facetable=True, filterable=True, sortable=True),
SimpleField(name="Rating", type=SearchFieldDataType.Double, facetable=True, filterable=True, sortable=True),
ComplexField(name="Address", fields=[
SearchableField(name="StreetAddress", type=SearchFieldDataType.String),
SearchableField(name="City", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="StateProvince", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="PostalCode", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
SearchableField(name="Country", type=SearchFieldDataType.String, facetable=True, filterable=True, sortable=True),
])
]
scoring_profiles = []
suggester = [{'name': 'sg', 'source_fields': ['Tags', 'Address/City', 'Address/Country']}]
# Create the search index
index = SearchIndex(name=index_name, fields=fields, suggesters=suggester, scoring_profiles=scoring_profiles)
result = index_client.create_or_update_index(index)
print(f' {result.name} created')
创建文档有效负载
针对操作类型(上传或合并上传等)使用索引操作。 文档源自 GitHub 上的 HotelsData 示例。
# Create a documents payload
documents = [
{
"@search.action": "upload",
"HotelId": "1",
"HotelName": "Secret Point Motel",
"Description": "The hotel is ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Time's 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.",
"Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de Beijing. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de Beijing l'une des villes les plus attractives et cosmopolites de l'Amérique.",
"Category": "Boutique",
"Tags": [ "pool", "air conditioning", "concierge" ],
"ParkingIncluded": "false",
"LastRenovationDate": "1970-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": "Twin Dome Motel",
"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.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Boutique",
"Tags": [ "pool", "free wifi", "concierge" ],
"ParkingIncluded": "false",
"LastRenovationDate": "1979-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": "Triple Landscape Hotel",
"Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Resort and Spa",
"Tags": [ "air conditioning", "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 Cliff Hotel",
"Description": "Sublime Cliff 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 1800 palace.",
"Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
"Category": "Boutique",
"Tags": [ "concierge", "view", "24-hour front desk service" ],
"ParkingIncluded": "true",
"LastRenovationDate": "1960-02-06T00:00:00Z",
"Rating": 4.60,
"Address": {
"StreetAddress": "7400 San Pedro Ave",
"City": "San Antonio",
"StateProvince": "TX",
"PostalCode": "78216",
"Country": "USA"
}
}
]
上传文档
# Upload documents to the index
search_client = SearchClient(endpoint=search_endpoint,
index_name=index_name,
credential=credential)
try:
result = search_client.upload_documents(documents=documents)
print("Upload of new document succeeded: {}".format(result[0].succeeded))
except Exception as ex:
print (ex.message)
index_client = SearchIndexClient(
endpoint=search_endpoint, credential=credential)
运行自己的第一个查询
使用 search.client 类的“search”方法。
此示例执行空搜索 (search=*
),返回任意文档的未排名列表 (search score = 1.0)。 由于没有条件,因此所有文档都包含在结果中。
# Run an empty query (returns selected fields, all documents)
results = search_client.search(query_type='simple',
search_text="*" ,
select='HotelName,Description',
include_total_count=True)
print ('Total Documents Matching Query:', results.get_count())
for result in results:
print(result["@search.score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
运行术语查询
下一个查询将整个术语添加到搜索表达式 ("wifi")。 此查询指定结果仅包含 select
语句中的那些字段。 限制返回的字段可最大程度地减少通过网络发回的数据量,并降低搜索延迟。
results = search_client.search(query_type='simple',
search_text="wifi" ,
select='HotelName,Description,Tags',
include_total_count=True)
print ('Total Documents Matching Query:', results.get_count())
for result in results:
print(result["@search.score"])
print(result["HotelName"])
print(f"Description: {result['Description']}")
添加筛选器
添加筛选表达式,仅返回评分高于 4 的酒店(按降序排列)。
# Add a filter
results = search_client.search(
search_text="hotels",
select='HotelId,HotelName,Rating',
filter='Rating gt 4',
order_by='Rating desc')
for result in results:
print("{}: {} - {} rating".format(result["HotelId"], result["HotelName"], result["Rating"]))
添加字段范围
将 search_fields
添加到特定字段的查询执行范围。
# Add search_fields to scope query matching to the HotelName field
results = search_client.search(
search_text="sublime",
search_fields=['HotelName'],
select='HotelId,HotelName')
for result in results:
print("{}: {}".format(result["HotelId"], result["HotelName"]))
添加 facet
Facet 是针对搜索结果中找到的正匹配项生成的。 没有零匹配。 如果搜索结果不包括 wifi 一词,则 wifi 不会出现在分面导航结构中。
# Return facets
results = search_client.search(search_text="*", facets=["Category"])
facets = results.get_facets()
for facet in facets["Category"]:
print(" {}".format(facet))
查找文档
基于文档的键返回文档。 如果要在用户选择搜索结果中的项时提供钻取,此操作非常有用。
# Look up a specific document by ID
result = search_client.get_document(key="3")
print("Details for hotel '3' are:")
print("Name: {}".format(result["HotelName"]))
print("Rating: {}".format(result["Rating"]))
print("Category: {}".format(result["Category"]))
添加自动完成功能
“自动完成”可以在用户在搜索框中键入时提供可能的匹配项。
“自动完成”使用建议器 (sg
) 来了解哪些字段包含建议器请求的潜在匹配。 在本快速入门中,这些字段为 Tags
、Address/City
、Address/Country
。
若要模拟自动完成,请输入字母“sa”作为字符串的一部分。 SearchClient 的自动完成方法会发回可能的术语匹配。
# Autocomplete a query
search_suggestion = 'sa'
results = search_client.autocomplete(
search_text=search_suggestion,
suggester_name="sg",
mode='twoTerms')
print("Autocomplete for:", search_suggestion)
for result in results:
print (result['text'])
使用 Azure.Search.Documents 库构建 Java 控制台应用,用于创建、加载和查询搜索索引。
或者,可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
设置你的环境
使用以下工具创建本快速入门。
创建项目
启动 Visual Studio Code。
通过使用 Ctrl+Shift+P 打开命令面板。 搜索“创建 Java 项目”。
选择“Maven”。
选择“maven-archetype-quickstart”。
选择最新版本(目前为 1.4)。
输入 azure.search.sample 作为组 ID。
输入 azuresearchquickstart 作为项目 ID。
选择要在其中创建项目的文件夹。
在集成终端中完成项目创建。 按 Enter 接受“1.0-SNAPSHOT”默认值,然后键入“y”以确认项目的属性。
打开在其中创建了项目的文件夹。
指定 Maven 依赖项
打开 pom.xml 文件并添加以下依赖项。
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-search-documents</artifactId>
<version>11.5.2</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>1.34.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
将编译器 Java 版本更改为 11。
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
创建搜索客户端
打开 src、main、java、azure、search、sample 下的 App
类。 添加以下导入指令。
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.credential.AzureKeyCredential;
import com.azure.core.util.Context;
import com.azure.search.documents.SearchClient;
import com.azure.search.documents.SearchClientBuilder;
import com.azure.search.documents.models.SearchOptions;
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.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;
以下示例包含搜索服务名称、授予创建和删除权限的管理 API 密钥以及索引名称的占位符。 请将所有三个占位符替换为有效值。 创建两个客户端:SearchIndexClient 创建索引,SearchClient 加载并查询现有索引。 两者都需要服务终结点和管理员 API 密钥才能使用创建和删除权限进行身份验证。
public static void main(String[] args) {
var searchServiceEndpoint = "<YOUR-SEARCH-SERVICE-URL>";
var adminKey = new AzureKeyCredential("<YOUR-SEARCH-SERVICE-ADMIN-KEY>");
String indexName = "<YOUR-SEARCH-INDEX-NAME>";
SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(adminKey)
.buildClient();
SearchClient searchClient = new SearchClientBuilder()
.endpoint(searchServiceEndpoint)
.credential(adminKey)
.indexName(indexName)
.buildClient();
}
创建索引
本快速入门生成 Hotels 索引,你将在其中加载酒店数据并对其执行查询。 在此步骤中,定义索引中的字段。 每个字段定义都包含名称、数据类型以及确定如何使用该字段的属性。
在此示例中,为了简单和可读性,使用了 azure-search-documents 库的同步方法。 但是,对于生产场景,应使用异步方法来保持应用程序的可缩放性和响应性。 例如,使用 SearchAsyncClient 而不是 SearchClient。
向项目添加一个空的类定义:Hotel.java
将以下代码复制到 Hotel.java
以定义酒店文档的结构。 该字段的属性决定字段在应用程序中的使用方式。 例如,IsFilterable 注释必须分配给每个支持筛选表达式的字段
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azure.search.sample;
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;
/**
* French description
*/
@JsonProperty("DescriptionFr")
@SearchableField(analyzerName = "fr.lucene")
public String descriptionFr;
/**
* 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 "";
}
}
}
在 Azure.Search.Documents 客户端库中,可以使用 SearchableField 和 SimpleField 来简化字段定义。
SimpleField
可以是任何数据类型,始终不可搜索(全文搜索查询将忽略它),并且可检索(未隐藏)。 其他属性默认情况下处于关闭状态,但可以启用。 你可能会将 SimpleField 用于仅在筛选器、facet 或计分概要文件中使用的文档 ID 或字段。 如果是这样,请确保应用该方案所需的所有属性,例如为文档 ID 应用 IsKey = true。
SearchableField
必须是字符串,并且始终可搜索、可检索。 其他属性默认情况下处于关闭状态,但可以启用。 因为此字段类型是可搜索的,所以它支持同义词和分析器属性的完整补集。
无论使用基本 SearchField
API 还是任一帮助程序模型,都必须显式启用筛选器、facet 和排序属性。 例如,isFilterable
、isSortable
和 isFacetable
必须进行显式属性化,如上例中所示。
向项目添加第二个空的类定义:Address.java
。 将以下代码复制到类中。
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azure.search.sample;
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;
}
在 App.java
中,在 main
方法中创建 SearchIndex
对象,然后调用 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 索引。
// 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 = "Secret Point Motel";
hotel.description = "The hotel is ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Time's 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.descriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de Beijing. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de Beijing l'une des villes les plus attractives et cosmopolites de l'Amérique.";
hotel.category = "Boutique";
hotel.tags = new String[] { "pool", "air conditioning", "concierge" };
hotel.parkingIncluded = false;
hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 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 = "Twin Dome Motel";
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.";
hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.";
hotel.category = "Boutique";
hotel.tags = new String[] { "pool", "free wifi", "concierge" };
hotel.parkingIncluded = false;
hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1979, 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 = "Triple Landscape Hotel";
hotel.description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.";
hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.";
hotel.category = "Resort and Spa";
hotel.tags = new String[] { "air conditioning", "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 Cliff Hotel";
hotel.description = "Sublime Cliff 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 1800 palace.";
hotel.descriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.";
hotel.category = "Boutique";
hotel.tags = new String[] { "concierge", "view", "24-hour front desk service" };
hotel.parkingIncluded = true;
hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1960, 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");
}
}
初始化 IndexDocumentsBatch
对象后,可通过对 SearchClient
对象调用 indexDocuments,将其发送到索引。
将以下行添加到 Main()
。 加载文档是使用 SearchClient
完成的。
// Upload sample hotel documents to the Search Index
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();
}
创建 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));
}
在第二个查询中,搜索某个术语,添加筛选器(用于选择评级大于 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));
第三个查询演示了用于将全文搜索操作的范围限定为特定字段的 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));
第四个查询演示了 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));
在第五个查询中,返回一个特定文档。
// 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();
最后一个查询显示了“自动完成”的语法,它模拟部分用户输入,即“s”,该输入解析为 sourceFields
中的两个可能的匹配项,与你在索引中定义的建议器相关联。
// Query 6
System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
已将 RunQueries
添加到 Main()
。
// 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");
前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。
全文搜索和筛选器是使用 SearchClient.search 方法执行的。 搜索查询可在 searchText
字符串中传递,而筛选表达式则可在 SearchOptions 类的 filter
属性中传递。 若要筛选但不搜索,只需传递“*”作为 search
方法的 searchText
参数。 若要在不筛选的情况下进行搜索,请保持 filter
属性未设置,或者根本不传入 SearchOptions
实例。
运行程序
按 F5 可重新生成应用并完整运行该程序。
输出包含 System.out.println
中的消息,并添加了查询信息和结果。
使用 @azure/search-documents 库构建 Node.js 应用程序,用于创建、加载和查询搜索索引。
或者,可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
设置你的环境
我们使用以下工具创建了本快速入门。
创建项目
启动 Visual Studio Code。
使用 Ctrl+Shift+P 打开命令面板,然后打开“集成终端”。
创建一个开发目录并将其命名为“快速入门”:
mkdir quickstart
cd quickstart
运行以下命令,使用 npm 初始化空项目。 若要完全初始化项目,请多次按 Enter 接受默认值,但许可证除外,它应设置为“MIT”。
npm init
安装 @azure/search-documents
,这是适用于 Azure AI 搜索的 JavaScript/TypeScript SDK。
npm install @azure/search-documents
安装 dotenv
,它用于导入环境变量,例如你的服务名称和 API 密钥。
npm install dotenv
导航到“快速入门”目录,然后检查你的 package.json 文件是否与以下 json 类似,确认已配置项目及其依赖项:
{
"name": "quickstart",
"version": "1.0.0",
"description": "Azure AI Search Quickstart",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Azure",
"Search"
],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@azure/search-documents": "^11.3.0",
"dotenv": "^16.0.2"
}
}
创建一个 .env 文件用来保存搜索服务参数:
SEARCH_API_KEY=<YOUR-SEARCH-ADMIN-API-KEY>
SEARCH_API_ENDPOINT=<YOUR-SEARCH-SERVICE-URL>
将 YOUR-SEARCH-SERVICE-URL
值替换为搜索服务终结点 URL 的名称。 将 <YOUR-SEARCH-ADMIN-API-KEY>
替换为前面记下的密钥值。
创建 index.js 文件
接下来,创建一个 index.js 文件,它是托管代码的主要文件。
在此文件的顶部导入 @azure/search-documents
库:
const { SearchIndexClient, SearchClient, AzureKeyCredential, odata } = require("@azure/search-documents");
接下来,需要 dotenv
包来从 .env 文件中读取参数,如下所示:
// Load the .env file if it exists
require("dotenv").config();
// Getting endpoint and apiKey from .env file
const endpoint = process.env.SEARCH_API_ENDPOINT || "";
const apiKey = process.env.SEARCH_API_KEY || "";
在导入和环境变量设置完成后,就可定义 main 函数。
SDK 中的大部分功能都是异步的,因此我们将 main 函数设置为 async
。 我们还在 main 函数下面包含一个 main().catch()
来捕获和记录遇到的所有错误:
async function main() {
console.log(`Running Azure AI Search JavaScript quickstart...`);
if (!endpoint || !apiKey) {
console.log("Make sure to set valid values for endpoint and apiKey with proper authorization.");
return;
}
// remaining quickstart code will go here
}
main().catch((err) => {
console.error("The sample encountered an error:", err);
});
完成后,即可创建索引。
创建索引
创建文件 hotels_quickstart_index.json。 此文件定义 Azure AI 搜索如何处理要在下一步骤中加载的文档。 每个字段由 name
标识,采用指定的 type
。 每个字段还包含一系列索引属性,这些属性指定 Azure AI 搜索是否可以根据字段进行搜索、筛选、排序和分面。 大多数字段采用简单数据类型,但有些字段(例如 AddressType
)采用复杂类型,可让你在索引中创建丰富的数据结构。 可详细了解支持的数据类型,以及创建索引 (REST) 中所述的索引属性。
将以下内容添加到 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": "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"
]
}
]
}
建立索引定义后,我们需要在 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.`);
运行示例
此时就可运行示例了。 使用终端窗口运行以下命令:
node index.js
如果已下载源代码,但尚未安装所需的包,请先运行 npm install
。
你应会看到一系列消息,其中描述了程序正在执行的操作。
在 Azure 门户中打开搜索服务的“概述”。 选择“索引”选项卡。应该会看到如下例所示的内容:
在下一步骤中,你要向索引添加数据。
加载文档
在 Azure AI 搜索中,文档这一数据结构既是索引输入,也是查询输出。 可将此类数据推送到索引,或者使用索引器。 在本例中,我们将以编程方式将文档推送到索引。
文档输入可以是数据库中的行、Blob 存储中的 Blob,或磁盘上的 JSON 文档(在本示例中为 JSON 文档)。 可以下载 hotels.json,或创建包含以下内容的 hotels.json 文件:
{
"value": [
{
"HotelId": "1",
"HotelName": "Secret Point Motel",
"Description": "The hotel is ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Time's 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.",
"Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de Beijing. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de Beijing l'une des villes les plus attractives et cosmopolites de l'Amérique.",
"Category": "Boutique",
"Tags": ["pool", "air conditioning", "concierge"],
"ParkingIncluded": false,
"LastRenovationDate": "1970-01-18T00:00:00Z",
"Rating": 3.6,
"Address": {
"StreetAddress": "677 5th Ave",
"City": "Beijing",
"StateProvince": "NY",
"PostalCode": "10022"
}
},
{
"HotelId": "2",
"HotelName": "Twin Dome Motel",
"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.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Boutique",
"Tags": ["pool", "free wifi", "concierge"],
"ParkingIncluded": "false",
"LastRenovationDate": "1979-02-18T00:00:00Z",
"Rating": 3.6,
"Address": {
"StreetAddress": "140 University Town Center Dr",
"City": "Sarasota",
"StateProvince": "FL",
"PostalCode": "34243"
}
},
{
"HotelId": "3",
"HotelName": "Triple Landscape Hotel",
"Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Resort and Spa",
"Tags": ["air conditioning", "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 Cliff Hotel",
"Description": "Sublime Cliff 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 1800 palace.",
"Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
"Category": "Boutique",
"Tags": ["concierge", "view", "24-hour front desk service"],
"ParkingIncluded": true,
"LastRenovationDate": "1960-02-06T00:00:00Z",
"Rating": 4.6,
"Address": {
"StreetAddress": "7400 San Pedro Ave",
"City": "San Antonio",
"StateProvince": "TX",
"PostalCode": "78216"
}
}
]
}
与我们使用 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));
或者,你可使用 SearchIndexClient
的 getSearchClient()
方法来创建 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)}`);
使用 node index.js
再次运行程序。 应会看到与步骤 1 中显示的消息略有不同的一系列消息。 这一次,索引确实存在,而且你会看到一条消息,它显示在应用创建新索引并向其发布数据之前删除此索引。
在下一步中运行查询之前,请定义一个函数,使程序等待一秒钟。 仅出于测试/演示目的这样做,目的是确保索引完成且文档可在索引中用于查询。
function sleep(ms) {
var d = new Date();
var d2 = null;
do {
d2 = new Date();
} while (d2 - d < ms);
}
若要让程序等待一秒钟,请调用 sleep
函数,如下所示:
sleep(1000);
搜索索引
创建索引并上传文档后,便可将查询发送到索引。 在本部分中,我们将向搜索索引发送 5 个不同的查询,以演示可供你使用的不同查询功能部分。
在 sendQueries()
函数中编写查询,我们将在 main 函数中调用此函数,如下所示:
await sendQueries(searchClient);
使用 searchClient
的 search()
方法发送查询。 第一个参数是搜索文本,第二个参数指定搜索选项。
第一个查询会搜索 *
,这等效于搜索所有内容并选择索引中的三个字段。 最佳做法是通过 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()
函数。 为了方便阅读,此处将它们分开。
在下一个查询中,我们指定搜索词 "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)}`);
}
接下来,使用 searchFields
参数将搜索限制为单个可搜索字段。 如果你知道自己只对某些字段中的匹配感兴趣,则很适合使用该选项来提高查询的效率。
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();
查询中包含的另一个常见选项是 facets
。 通过 facet,可在 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)}`);
}
最终查询使用 searchClient
的 getDocument()
方法。 这样,你就可通过文档的密钥有效地检索文档。
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
运行示例
通过运行 node index.js
来运行程序。 现在,除上述步骤以外,还会发送查询并将结果写入控制台。
使用 @azure/search-documents 库构建 Node.js 应用程序,用于创建、加载和查询搜索索引。
或者,可以下载源代码,从已完成的项目着手,或按照这些步骤创建自己的项目。
设置你的环境
我们使用以下工具创建了本快速入门。
创建项目
启动 Visual Studio Code。
使用 Ctrl+Shift+P 打开命令面板,然后打开“集成终端”。
创建一个开发目录并将其命名为“快速入门”:
mkdir quickstart
cd quickstart
运行以下命令,使用 npm 初始化空项目。 若要完全初始化项目,请多次按 Enter 接受默认值,但许可证除外,它应设置为“MIT”。
npm init
安装 @azure/search-documents
,这是适用于 Azure AI 搜索的 JavaScript/TypeScript SDK。
npm install @azure/search-documents
安装 dotenv
,它用于导入环境变量,例如你的服务名称和 API 密钥。
npm install dotenv
检查你的 package.json 文件是否与以下 json 类似,确认已配置项目及其依赖项:
{
"name": "quickstart",
"version": "1.0.0",
"description": "Azure AI Search Quickstart",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Azure",
"Search"
],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"@azure/search-documents": "^12.1.0",
"dotenv": "^16.4.5"
}
}
创建一个 .env 文件用来保存搜索服务参数:
SEARCH_API_KEY=<YOUR-SEARCH-ADMIN-API-KEY>
SEARCH_API_ENDPOINT=<YOUR-SEARCH-SERVICE-URL>
将 YOUR-SEARCH-SERVICE-URL
值替换为搜索服务终结点 URL 的名称。 将 <YOUR-SEARCH-ADMIN-API-KEY>
替换为前面记下的密钥值。
创建 index.ts文件
接下来,创建一个 index.js 文件,它是托管代码的主要文件。
在此文件的顶部导入 @azure/search-documents
库:
import {
AzureKeyCredential,
ComplexField,
odata,
SearchClient,
SearchFieldArray,
SearchIndex,
SearchIndexClient,
SearchSuggester,
SimpleField
} from "@azure/search-documents";
接下来,需要 dotenv
包来从 .env 文件中读取参数,如下所示:
// Load the .env file if it exists
import dotenv from 'dotenv';
dotenv.config();
// Getting endpoint and apiKey from .env file
const endpoint = process.env.SEARCH_API_ENDPOINT || "";
const apiKey = process.env.SEARCH_API_KEY || "";
在导入和环境变量设置完成后,就可定义 main 函数。
SDK 中的大部分功能都是异步的,因此我们将 main 函数设置为 async
。 我们还在 main 函数下面包含一个 main().catch()
来捕获和记录遇到的所有错误:
async function main() {
console.log(`Running Azure AI Search JavaScript quickstart...`);
if (!endpoint || !apiKey) {
console.log("Make sure to set valid values for endpoint and apiKey with proper authorization.");
return;
}
// remaining quickstart code will go here
}
main().catch((err) => {
console.error("The sample encountered an error:", err);
});
完成后,即可创建索引。
创建索引
创建文件 hotels_quickstart_index.json。 此文件定义 Azure AI 搜索如何处理要在下一步骤中加载的文档。 每个字段由 name
标识,采用指定的 type
。 每个字段还包含一系列索引属性,这些属性指定 Azure AI 搜索是否可以根据字段进行搜索、筛选、排序和分面。 大多数字段采用简单数据类型,但有些字段(例如 AddressType
)采用复杂类型,可让你在索引中创建丰富的数据结构。 可详细了解支持的数据类型,以及创建索引 (REST) 中所述的索引属性。
将以下内容添加到 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": "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"
]
}
]
}
建立索引定义后,我们需要在 index.ts 顶部导入 hotels_quickstart_index.json,使 main 函数能够访问索引定义。
// Importing the index definition and sample data
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.`);
运行示例
此时,即可生成并运行示例。 使用终端窗口运行以下命令,使用 tsc
生成源,然后使用 node
运行源:
tsc
node index.ts
如果已下载源代码,但尚未安装所需的包,请先运行 npm install
。
你应会看到一系列消息,其中描述了程序正在执行的操作。
在 Azure 门户中打开搜索服务的“概述”。 选择“索引”选项卡。应该会看到如下例所示的内容:
在下一步骤中,你要向索引添加数据。
加载文档
在 Azure AI 搜索中,文档这一数据结构既是索引输入,也是查询输出。 可将此类数据推送到索引,或者使用索引器。 在本例中,我们将以编程方式将文档推送到索引。
文档输入可以是数据库中的行、Blob 存储中的 Blob,或磁盘上的 JSON 文档(在本示例中为 JSON 文档)。 可以下载 hotels.json,或创建包含以下内容的 hotels.json 文件:
{
"value": [
{
"HotelId": "1",
"HotelName": "Secret Point Motel",
"Description": "The hotel is ideally located on the main commercial artery of the city in the heart of Beijing. A few minutes away is Time's 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.",
"Description_fr": "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de Beijing. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de Beijing l'une des villes les plus attractives et cosmopolites de l'Amérique.",
"Category": "Boutique",
"Tags": ["pool", "air conditioning", "concierge"],
"ParkingIncluded": false,
"LastRenovationDate": "1970-01-18T00:00:00Z",
"Rating": 3.6,
"Address": {
"StreetAddress": "677 5th Ave",
"City": "Beijing",
"StateProvince": "NY",
"PostalCode": "10022"
}
},
{
"HotelId": "2",
"HotelName": "Twin Dome Motel",
"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.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Boutique",
"Tags": ["pool", "free wifi", "concierge"],
"ParkingIncluded": "false",
"LastRenovationDate": "1979-02-18T00:00:00Z",
"Rating": 3.6,
"Address": {
"StreetAddress": "140 University Town Center Dr",
"City": "Sarasota",
"StateProvince": "FL",
"PostalCode": "34243"
}
},
{
"HotelId": "3",
"HotelName": "Triple Landscape Hotel",
"Description": "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel's restaurant services.",
"Description_fr": "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.",
"Category": "Resort and Spa",
"Tags": ["air conditioning", "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 Cliff Hotel",
"Description": "Sublime Cliff 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 1800 palace.",
"Description_fr": "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.",
"Category": "Boutique",
"Tags": ["concierge", "view", "24-hour front desk service"],
"ParkingIncluded": true,
"LastRenovationDate": "1960-02-06T00:00:00Z",
"Rating": 4.6,
"Address": {
"StreetAddress": "7400 San Pedro Ave",
"City": "San Antonio",
"StateProvince": "TX",
"PostalCode": "78216"
}
}
]
}
与我们使用 indexDefinition 执行的操作类似,我们还需要在 index.ts 顶部导入 hotels.json
,以便可在我们的 main 函数中访问数据。
import hotelData from './hotels.json';
interface Hotel {
HotelId: string;
HotelName: string;
Description: string;
Description_fr: 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));
或者,你可使用 SearchIndexClient
的 getSearchClient()
方法来创建 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);
使用 searchClient
的 search()
方法发送查询。 第一个参数是搜索文本,第二个参数指定搜索选项。
第一个查询会搜索 *
,这等效于搜索所有内容并选择索引中的三个字段。 最佳做法是通过 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()
函数。 为了方便阅读,此处将它们分开。
在下一个查询中,我们指定搜索词 "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)}`);
}
接下来,使用 searchFields
参数将搜索限制为单个可搜索字段。 如果你知道自己只对某些字段中的匹配感兴趣,则很适合使用该选项来提高查询的效率。
console.log('Query #3 - limit searchFields:');
const searchOptions3 = {
select: selectFields,
searchFields: ["HotelName"] as const
};
searchResults = await searchClient.search("sublime cliff", searchOptions3);
for await (const result of searchResults.results) {
console.log(`${JSON.stringify(result.document)}`);
}
查询中包含的另一个常见选项是 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)}`);
}
最终查询使用 searchClient
的 getDocument()
方法。 这样,你就可通过文档的密钥有效地检索文档。
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
重新运行示例
使用 tsc && node index.ts
生成并运行程序。 现在,除上述步骤以外,还会发送查询并将结果写入控制台。
在此快速入门中,你已完成一系列任务,即创建索引、使用文档加载索引并运行查询。 在不同的阶段,我们采用快捷方式来简化代码,从而实现可读性和可理解性。 现在你已熟悉了基本概念,请尝试在 Web 应用中调用 Azure AI 搜索 API 的教程。