快速入門:使用 Azure SDK 進行全文檢索搜尋
瞭解如何使用 Azure.Search.Documents 用戶端連結庫,使用範例數據建立、載入及查詢搜尋索引,以進行 全文搜索。 全文搜索會使用 Apache Lucene 來編制索引和查詢,並使用 BM25 排名演算法來評分結果。
本快速入門會建立和查詢包含四家酒店相關數據的小型旅館快速入門索引。
提示
您可以 下載原始碼 以開始使用已完成的專案,或遵循下列步驟來建立您自己的專案。
必要條件
Microsoft Entra ID 必要條件
針對具有 Microsoft Entra ID 的建議無金鑰驗證,您需要:
- 使用 Microsoft Entra ID 安裝用於無密鑰驗證的 Azure CLI。
-
Search Service Contributor
將和Search Index Data Contributor
角色指派給您的用戶帳戶。 您可以在存取控制 (IAM)>[新增角色指派] 底下的 [Azure 入口網站 中指派角色。 如需詳細資訊,請參閱 使用角色連線到 Azure AI 搜尋服務。
擷取資源資訊
您需要擷取下列資訊,以向 Azure AI 搜尋服務 驗證應用程式:
變數名稱 | 值 |
---|---|
SEARCH_API_ENDPOINT |
您可以在 Azure 入口網站 中找到此值。 選取您的搜尋服務,然後從左側功能表中選取 [ 概觀]。
[基本資訊] 底下的 [URL] 值是您需要的端點。 範例端點看起來會像是 https://mydemo.search.windows.net 。 |
設定
使用下列命令,建立新資料夾
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 login
建立、載入及查詢搜尋索引
在先前 的設定 區段中,您已建立新的控制台應用程式,並安裝 Azure AI 搜尋客戶端連結庫。
在本節中,您會新增程序代碼來建立搜尋索引、使用檔載入它,以及執行查詢。 您可以執行程式,以查看控制台中的結果。 如需程式代碼的詳細說明,請參閱 說明程式代碼 一節。
本快速入門中的範例程式代碼會針對建議的無密鑰驗證使用 Microsoft Entra 識別碼。 如果您想要使用 API 金鑰,可以將 物件取代 DefaultAzureCredential
為 AzureKeyCredential
物件。
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
在 Program.cs 中,貼上下列程序代碼。
serviceName
使用您的搜尋服務名稱和系統管理 API 金鑰編輯和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.windows.net/"); // 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 = "Secret Point Motel", Description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York 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 New York. 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 New York 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 = "New York", 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}"); } } // 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(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; } } }
建立名為 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; } } }
建立名為 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(DescriptionFr)) { builder.AppendFormat("Description (French): {0}\n", DescriptionFr); } 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 識別碼。 如果您想要使用 API 金鑰,可以將 物件取代 DefaultAzureCredential
為 AzureKeyCredential
物件。
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
DefaultAzureCredential credential = new();
static void Main(string[] args)
{
// Your search service endpoint
Uri serviceEndpoint = new Uri($"https://<Put your search service NAME here>.search.windows.net/");
// 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 . . .
}
建立索引
本快速入門會建置您使用旅館數據載入的 Hotels 索引,並對其執行查詢。 在此步驟中,您會定義索引中的欄位。 每個欄位定義都包含名稱、資料類型和屬性,以決定欄位的使用方式。
在此範例中,Azure.Search.Documents 連結庫的同步方法會用於簡單且易讀性。 不過,在生產案例中,則請使用非同步方法,以讓應用程式保持可調整和能回應的狀態。 例如,使用 CreateIndexAsync 而非 CreateIndex。
定義結構
您已建立兩個協助程序類別, Hotel.cs 和 Address.cs,以定義旅館檔及其地址的結構。 類別 Hotel
包含旅館標識碼、名稱、描述、類別、標籤、停車、裝修日期、評等和位址的欄位。 類別 Address
包含街道位址、城市、州/省、郵遞區編碼和國家/地區的欄位。
在 Azure.Search.Documents 用戶端連結庫中,您可以使用 SearchableField 和 SimpleField 來簡化欄位定義。 兩者都是 SearchField 的衍生項目,而且可能會簡化您的程式碼:
SimpleField
可以是任何數據類型,一律不可搜尋(針對全文搜索查詢忽略),而且是可擷取的(不隱藏)。 其他屬性預設為關閉,但可以啟用。 您可以針對僅用於篩選、Facet 或評分設定檔的文件識別碼或欄位使用SimpleField
。 若是如此,請務必套用案例所需的任何屬性,例如文件識別碼的IsKey = true
。 如需詳細資訊,請參閱原始程式碼中的 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
物件包含動作集合,每個物件都包含文件與屬性,後者會告知 Azure AI 搜尋服務所應執行的動作 (上傳、合併、刪除及 mergeOrUpload)。
在 Program.cs中,您會建立檔案和索引動作的陣列,然後將陣列傳遞至 IndexDocumentsBatch
。 下列檔符合 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 = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York 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 New York. 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 New York 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 = "New York",
StateProvince = "NY",
PostalCode = "10022",
Country = "USA"
}
}),
// REDACTED FOR BREVITY
}
一旦將 IndexDocumentsBatch 物件初始化之後,您就可以將其傳送至索引,方法是在 SearchClient 物件上呼叫 IndexDocuments。
您會在 中使用 Main()
SearchClient 載入檔,但作業也需要服務的系統管理員許可權,這通常與 SearchIndexClient 相關聯。 設定此作業的其中一種方式是透過 取得 SearchClient SearchIndexClient
(searchIndexClient
在此範例中)。
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 秒延的遲是針對索引編製進行補償,這是非同步作業,因此可以在執行查詢之前針對所有文件編製索引。 設計延遲程式碼只有在示範、測試與範例應用程式中才有必要。
搜尋索引
您可以在系統為第一個文件編製索引之後立即取得結果,但索引的實際測試應該等到系統為所有文件編製索引之後才進行。
此節新增兩個功能:查詢邏輯,以及結果。 針對查詢,請使用搜尋方法。 這個方法會接受搜尋文字 (查詢字串) 和其他選項。
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 類別的篩選屬性中傳遞。 若要篩選而不進行搜尋,只要針對搜尋方法的 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
角色指派給您的用戶帳戶。 您可以在存取控制 (IAM)>[新增角色指派] 底下的 [Azure 入口網站 中指派角色。 如需詳細資訊,請參閱 使用角色連線到 Azure AI 搜尋服務。
擷取資源資訊
您需要擷取下列資訊,以向 Azure AI 搜尋服務 驗證您的應用程式:
變數名稱 | 值 |
---|---|
SEARCH_API_ENDPOINT |
您可以在 Azure 入口網站 中找到此值。 選取您的搜尋服務,然後從左側功能表中選取 [ 概觀]。
[基本資訊] 底下的 [URL] 值是您需要的端點。 範例端點看起來會像是 https://mydemo.search.windows.net 。 |
設定
本快速入門中的範例適用於 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 login
建立、載入及查詢搜尋索引
在先前 的設定 區段中,您已安裝 Azure AI 搜尋服務用戶端連結庫和其他相依性。
在本節中,您會新增程序代碼來建立搜尋索引、使用檔載入它,以及執行查詢。 您可以執行程式,以查看控制台中的結果。 如需程式代碼的詳細說明,請參閱 說明程式代碼 一節。
本快速入門中的範例程式代碼會針對建議的無密鑰驗證使用 Microsoft Entra 識別碼。 如果您想要使用 API 金鑰,可以將 物件取代 DefaultAzureCredential
為 AzureKeyCredential
物件。
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
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.windows.net/"; // 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 = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York 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 New York. 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 New York 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 = "New York"; 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."; 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 = "Gastronomic 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 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 Palace is part of a lovingly restored 1800 palace."; hotel.descriptionFr = "Le Sublime Palace 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 Palace 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"); } } // 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; /** * 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 ""; } } }
建立名為 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 識別碼。 如果您想要使用 API 金鑰,可以將 物件取代 DefaultAzureCredential
為 AzureKeyCredential
物件。
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
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.windows.net/";
// 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 . . .
}
建立索引
本快速入門會建置您使用旅館數據載入的 Hotels 索引,並對其執行查詢。 在此步驟中,您會定義索引中的欄位。 每個欄位定義都包含名稱、資料類型和屬性,以決定欄位的使用方式。
在此範例中,Azure.Search.Documents 連結庫的同步方法會用於簡單且易讀性。 不過,在生產案例中,則請使用非同步方法,以讓應用程式保持可調整和能回應的狀態。 例如,使用 CreateIndexAsync 而非 CreateIndex。
定義結構
您已建立兩個協助程序類別, Hotel.java和Address.java,以定義旅館文件的結構及其位址。 Hotel 類別包含旅館標識碼、名稱、描述、類別、標籤、停車、裝修日期、評等和位址的欄位。 [位址] 類別包含街道位址、城市、州/省、郵遞區區碼和國家/地區的欄位。
在 Azure.Search.Documents 用戶端程式庫中,您可以使用 SearchableField 和 SimpleField 來簡化欄位定義。
-
SimpleField
可以是任何數據類型,一律不可搜尋(針對全文搜索查詢忽略),而且是可擷取的(不隱藏)。 其他屬性預設為關閉,但可以啟用。 您可以針對僅用於篩選、Facet 或評分設定檔的文件識別碼或欄位使用 SimpleField。 若是如此,請務必套用案例所需的任何屬性,例如文件識別碼的 IsKey = true。 -
SearchableField
必須是字串,而且一律可搜尋並可擷取。 其他屬性預設為關閉,但可以啟用。 因為此欄位類型是可搜尋,所以其支援同義字和分析器屬性的完整補語。
無論您使用的是基本 SearchField
API 或其中一個 協助程式模型,都必須明確啟用篩選、Facet 和排序屬性。 例如, isFilterable
、 isSortable
和 isFacetable
必須明確屬性,如上一個範例所示。
建立搜尋索引
在 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 認知搜尋所應執行的動作 (上傳、合併、刪除及 mergeOrUpload)。
在 App.java
中,您會建立檔案和索引動作,然後將它們傳遞至 IndexDocumentsBatch
。 下列檔符合 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 = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York 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 New York. 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 New York 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 = "New York";
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
物件上的 indexDocuments。
您會在 中使用 main()
SearchClient 載入檔,但作業也需要服務的系統管理員許可權,這通常與 SearchIndexClient 相關聯。 設定此作業的其中一種方式是透過 取得 SearchClient SearchIndexClient
(searchIndexClient
在此範例中)。
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 秒延的遲是針對索引編製進行補償,這是非同步作業,因此可以在執行查詢之前針對所有文件編製索引。 設計延遲程式碼只有在示範、測試與範例應用程式中才有必要。
搜尋索引
您可以在系統為第一個文件編製索引之後立即取得結果,但索引的實際測試應該等到系統為所有文件編製索引之後才進行。
本節會新增兩個功能:查詢邏輯和結果。 針對查詢,請使用搜尋方法。 這個方法會接受搜尋文字 (查詢字串) 以及其他選項。
在 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
最後一個查詢會顯示自動完成的 語法,模擬 的部分使用者輸入 , 其解析為與您在索引中定義之建議工具相關聯的兩個可能相符 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
字串中傳遞,而篩選運算式則可以在 SearchOptions 類別的 filter
屬性中傳遞。 若要篩選而不進行搜尋,只要針對 search
方法的 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
角色指派給您的用戶帳戶。 您可以在存取控制 (IAM)>[新增角色指派] 底下的 [Azure 入口網站 中指派角色。 如需詳細資訊,請參閱 使用角色連線到 Azure AI 搜尋服務。
擷取資源資訊
您需要擷取下列資訊,以向 Azure AI 搜尋服務 驗證您的應用程式:
變數名稱 | 值 |
---|---|
SEARCH_API_ENDPOINT |
您可以在 Azure 入口網站 中找到此值。 選取您的搜尋服務,然後從左側功能表中選取 [ 概觀]。
[基本資訊] 底下的 [URL] 值是您需要的端點。 範例端點看起來會像是 https://mydemo.search.windows.net 。 |
設定
使用下列命令,建立新資料夾
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 識別碼。 如果您想要使用 API 金鑰,可以將 物件取代 DefaultAzureCredential
為 AzureKeyCredential
物件。
String searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
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.windows.net/"; // 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 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) { 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": "Secret Point Motel", "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York 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 New York. 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 New York 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": "New York", "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" } } ] }
建立名為 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": "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" ] } ] }
使用下列命令登入 Azure:
az login
使用下列命令執行 JavaScript 程式代碼:
node index.js
說明程式碼
建立索引
hotels_quickstart_index.json檔案會定義 Azure AI 搜尋如何與您在下一個步驟中載入的檔搭配運作。 每個欄位都會由 name
識別,並具有指定的 type
。 每個欄位也有一系列的索引屬性,可指定 Azure AI 搜尋服務是否可在欄位上進行搜尋、篩選、排序和 Facet 處理。 大部分的欄位都是簡單的資料類型,但有些 (像是 AddressType
) 則為複雜類型,其可讓您在索引中建立豐富的資料結構。 您可以深入了解建立索引 (REST) 中所述的支援資料類型和索引屬性。
在索引定義備妥後,我們想要將 hotels_quickstart_index.json 匯入 index.js 的頂端,讓 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 文件。 類似於我們對的indexDefinition
處理方式,我們也需要在index.js頂端匯hotels.json
入,以便在主要函式中存取數據。
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)}`);
搜尋索引
建立索引並上傳文件之後,您就可以開始將查詢傳送至索引。 在本節中,我們會將五個不同的查詢傳送至搜尋索引,以示範您可用的不同查詢功能片段。
查詢會以 sendQueries()
我們在main函式中呼叫的函式撰寫,如下所示:
await sendQueries(searchClient);
系統會使用 searchClient
的 search()
方法來傳送查詢。 第一個參數是搜尋文字,而第二個參數則指定搜尋選項。
查詢範例 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
。 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)}`);
}
查詢範例 5
最後一個查詢會使用 searchClient
的 getDocument()
方法。 這可讓您依其索引鍵有效率地擷取文件。
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument(key='3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
查詢摘要
先前的查詢會顯示在查詢中比對字詞的多種方式:全文檢索搜尋、篩選和自動完成。
全文搜索和篩選會使用 searchClient.search
方法執行。 搜尋查詢可以傳入searchText
字串,而篩選表達式則可在 類別的 SearchOptions
屬性中filter
傳遞。 若要篩選而不進行搜尋,只要針對 search
方法的 searchText
參數傳遞「*」即可。 若要在不進行篩選的情況下搜尋,則請將 filter
屬性保留在未設定狀態,或完全不要傳入 SearchOptions
執行個體。
瞭解如何使用 Azure.Search.Documents 用戶端連結庫,使用範例數據建立、載入及查詢搜尋索引,以進行 全文搜索。 全文搜索會使用 Apache Lucene 來編制索引和查詢,並使用 BM25 排名演算法來評分結果。
本快速入門會建立和查詢包含四家酒店相關數據的小型旅館快速入門索引。
提示
您可以下載並執行已完成的 筆記本。
必要條件
- 有效的 Azure 訂用帳戶 - 建立免費帳戶
- 一項 Azure AI 搜尋服務。 如果您沒有服務,請建立服務。 您可以使用免費服務層級來進行本快速入門。
- 具有 Python 延伸模組或對等 IDE 的 Visual Studio Code 與 Python 3.10 或更新版本。 如果您沒有安裝適當的 Python 版本,您可以遵循 VS Code Python 教學課程中的指示。
Microsoft Entra ID 必要條件
針對具有 Microsoft Entra ID 的建議無金鑰驗證,您需要:
- 使用 Microsoft Entra ID 安裝用於無密鑰驗證的 Azure CLI。
-
Search Service Contributor
將和Search Index Data Contributor
角色指派給您的用戶帳戶。 您可以在存取控制 (IAM)>[新增角色指派] 底下的 [Azure 入口網站 中指派角色。 如需詳細資訊,請參閱 使用角色連線到 Azure AI 搜尋服務。
擷取資源資訊
您需要擷取下列資訊,以向 Azure AI 搜尋服務 驗證您的應用程式:
變數名稱 | 值 |
---|---|
SEARCH_API_ENDPOINT |
您可以在 Azure 入口網站 中找到此值。 選取您的搜尋服務,然後從左側功能表中選取 [ 概觀]。
[基本資訊] 底下的 [URL] 值是您需要的端點。 範例端點看起來會像是 https://mydemo.search.windows.net 。 |
設定您的環境
您會在 Jupyter Notebook 中執行範例程式代碼。 因此,您必須設定環境以執行 Jupyter Notebook。
從 GitHub 下載或複製範例筆記本。
在 Visual Studio Code 中開啟筆記本。
建立新的 Python 環境,以用來安裝本教學課程所需的套件。
重要
請勿將套件安裝到您的全域 Python 安裝中。 安裝 Python 套件時,您應該一律使用虛擬或 conda 環境,否則您可能會破壞 Python 的全域安裝。
設定可能需要一分鐘的時間。 如果您遇到問題,請參閱 VS Code 中的 Python 環境。
如果您還沒有 Jupyter Notebook,請安裝 Jupyter Notebook 和 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 識別碼。 如果您想要使用 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.windows.net/" authority = AzureAuthorityHosts.AZURE_PUBLIC_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 搜尋服務是否可在欄位上進行搜尋、篩選、排序和 Facet 處理。 大部分的欄位都是簡單的資料類型,但有些 (像是 AddressType
) 則為複雜類型,其可讓您在索引中建立豐富的資料結構。 您可以深入了解建立索引 (REST) 中所述的支援資料類型和索引屬性。
建立文件承載並上傳檔
針對作業類型使用索引動作,例如上傳或合併和上傳。 檔源自 GitHub 上的 HotelsData 範例。
搜尋索引
您可以在系統為第一個文件編製索引之後立即取得結果,但索引的實際測試應該等到系統為所有文件編製索引之後才進行。
使用 search.client class 類別的 search 方法。
筆記本中的範例查詢如下:
- 基本查詢:執行空的搜尋 (
search=*
),傳回未加入清單的任意檔 (搜尋分數 = 1.0)。 因為沒有任何準則,所以所有文件都會包含在結果中。 - 字詞查詢:將整個字詞新增至搜尋表達式 (“wifi” )。 此查詢會指定結果只包含
select
陳述式中的那些欄位。 限制傳回的欄位可將透過網路傳送的資料量降至最低,並減少搜尋延遲。 - 篩選查詢:新增篩選表達式,只傳回評分大於四的酒店,依遞減順序排序。
- 欄位範圍:新增
search_fields
至範圍查詢執行至特定欄位。 - Facet:為搜尋結果中找到的正比對產生 Facet。 沒有零相符項目。 如果搜尋結果不包含wifi一詞,則wifi不會出現在多面向導覽結構中。
- 查閱檔:根據其索引鍵傳回檔。 如果您想要在使用者選取搜尋結果中的項目時提供鑽研,這項作業會很有用。
- 自動完成:在使用者輸入搜尋方塊時,提供潛在的相符專案。 自動完成會使用建議工具 (
sg
) 得知哪些欄位包含建議工具要求的潛在相符項目。 在本快速入門中,這些欄位為Tags
、Address/City
、Address/Country
。 若要模擬自動完成,請將字母 sa 傳入為部分字串。 SearchClient 的自動完成方法會傳回可能的相符字詞。
拿掉索引
如果您已完成此索引,您可以執行 清除 程式代碼資料格來刪除它。 刪除不必要的索引可釋出空間,以逐步執行更多快速入門和教學課程。
瞭解如何使用 Azure.Search.Documents 用戶端連結庫,使用範例數據建立、載入及查詢搜尋索引,以進行 全文搜索。 全文搜索會使用 Apache Lucene 來編制索引和查詢,並使用 BM25 排名演算法來評分結果。
本快速入門會建立和查詢包含四家酒店相關數據的小型旅館快速入門索引。
提示
您可以 下載原始碼 以開始使用已完成的專案,或遵循下列步驟來建立您自己的專案。
必要條件
Microsoft Entra ID 必要條件
針對具有 Microsoft Entra ID 的建議無金鑰驗證,您需要:
- 使用 Microsoft Entra ID 安裝用於無密鑰驗證的 Azure CLI。
-
Search Service Contributor
將和Search Index Data Contributor
角色指派給您的用戶帳戶。 您可以在存取控制 (IAM)>[新增角色指派] 底下的 [Azure 入口網站 中指派角色。 如需詳細資訊,請參閱 使用角色連線到 Azure AI 搜尋服務。
擷取資源資訊
您需要擷取下列資訊,以向 Azure AI 搜尋服務 驗證您的應用程式:
變數名稱 | 值 |
---|---|
SEARCH_API_ENDPOINT |
您可以在 Azure 入口網站 中找到此值。 選取您的搜尋服務,然後從左側功能表中選取 [ 概觀]。
[基本資訊] 底下的 [URL] 值是您需要的端點。 範例端點看起來會像是 https://mydemo.search.windows.net 。 |
設定
使用下列命令,建立新資料夾
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 識別碼。 如果您想要使用 API 金鑰,可以將 物件取代 DefaultAzureCredential
為 AzureKeyCredential
物件。
const searchServiceEndpoint = "https://<Put your search service NAME here>.search.windows.net/";
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": "Description_fr", "type": "Edm.String" as SearchFieldDataType, "searchable": true, "filterable": false, "sortable": false, "facetable": false, "analyzerName": "fr.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.windows.net/"; // 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": "Secret Point Motel", "Description": "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York 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 New York. 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 New York 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": "New York", "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" } } ] }
建立檔案
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 login
使用下列命令執行 JavaScript 程式代碼:
node index.js
說明程式碼
建立索引
建立 hotels_quickstart_index.json 檔案。 此檔案會定義 Azure AI 搜尋如何搭配您在下一個步驟中載入的文件運作。 每個欄位都會由 name
識別,並具有指定的 type
。 每個欄位也有一系列的索引屬性,可指定 Azure AI 搜尋服務是否可在欄位上進行搜尋、篩選、排序和 Facet 處理。 大部分的欄位都是簡單的資料類型,但有些 (像是 AddressType
) 則為複雜類型,其可讓您在索引中建立豐富的資料結構。 您可以深入了解建立索引 (REST) 中所述的支援資料類型和索引屬性。
我們想要匯 入hotels_quickstart_index.json ,讓main函式可以存取索引定義。
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 文件。 您可以下載 hotels. json,或使用下列內容建立自己的 hotels.json 檔案:
與處理 indexDefinition 的方法一樣,我們也需要將 hotels.json
匯入 index.ts 頂端,以便在 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);
搜尋索引
建立索引並上傳文件之後,您就可以開始將查詢傳送至索引。 在本節中,我們會將五個不同的查詢傳送至搜尋索引,以示範您可用的不同查詢功能片段。
查詢會以 sendQueries()
我們在main函式中呼叫的函式撰寫,如下所示:
await sendQueries(searchClient);
系統會使用 searchClient
的 search()
方法來傳送查詢。 第一個參數是搜尋文字,而第二個參數則指定搜尋選項。
查詢範例 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
。 Facet 可讓您從 UI 中的結果提供自我導向的向下鑽研。 Facet 結果可以轉換成結果窗格中的核取方塊。
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
最後一個查詢會使用 searchClient
的 getDocument()
方法。 這可讓您依其索引鍵有效率地擷取文件。
console.log('Query #5 - Lookup document:');
let documentResult = await searchClient.getDocument('3')
console.log(`HotelId: ${documentResult.HotelId}; HotelName: ${documentResult.HotelName}`)
查詢摘要
先前的查詢會顯示在查詢中比對字詞的多種方式:全文檢索搜尋、篩選和自動完成。
全文搜索和篩選會使用 searchClient.search
方法執行。 搜尋查詢可以傳入searchText
字串,而篩選表達式則可在 類別的 SearchOptions
屬性中filter
傳遞。 若要篩選而不進行搜尋,只要針對 search
方法的 searchText
參數傳遞「*」即可。 若要在不進行篩選的情況下搜尋,則請將 filter
屬性保留在未設定狀態,或完全不要傳入 SearchOptions
執行個體。
清除資源
如果您是在自己的訂用帳戶中進行,建議您在專案結束時判斷自己是否仍需要先前所建立的資源。 資源若繼續執行,將需付費。 您可以個別刪除資源,或刪除資源群組以刪除整組資源。
您可以使用左側瀏覽窗格中的 [所有資源] 或 [資源群組] 連結,在 Azure 入口網站 中找到和管理資源。
如果您使用免費服務,請記住您會有三個索引、索引子和資料來源的限制。 您可以在 Azure 入口網站中刪除個別項目,以避免超出限制。
後續步驟
在此快速入門中,您已執行一組工作來建立索引、使用文件來載入索引,以及執行查詢。 在不同的階段中,我們採用捷徑,以簡化程式碼,讓程式碼更容易閱讀及傳達概念。 現在您已熟悉基本概念,請嘗試在 Web 應用程式中呼叫 Azure AI 搜尋服務 API 的教學課程。