本文是旧 版 Microsoft.Azure.Search (版本 10)客户端库的 C# 快速入门,现已由 Azure.Search.Documents (版本 11)客户端库取代。
注释
如果你有现有或飞行中的开发项目,则可以继续使用版本 10。 但是,对于新项目,或使用新功能,应过渡到 新库。
关于本快速入门
在 C# 中创建 .NET Core 控制台应用程序,该应用程序使用 Visual Studio 和 Microsoft.Azure.Search 客户端库创建、加载和查询 Azure 认知搜索索引。
本文介绍如何创建应用程序。 还可以 下载并运行完整的应用程序。
注释
本文中的演示代码使用 Azure 认知搜索版本 10 .NET SDK 的同步方法,为简单起见。 但是,对于生产方案,我们建议在自己的应用程序中使用异步方法,使其保持可缩放且响应迅速。 例如,可以使用 CreateAsync
和 DeleteAsync
不使用 Create
和 Delete
。
先决条件
在开始之前,必须满足以下条件:
具有活动订阅的 Azure 帐户。 免费创建帐户。
Visual Studio(版本不限)。 示例代码和说明已在免费社区版上进行了测试。
获取密钥和 URL
对服务的调用需要 URL 终结点和每个请求的访问密钥。 使用两者创建搜索服务,因此,如果将 Azure 认知搜索添加到订阅,请按照以下步骤获取所需的信息:
登录到 Azure 门户,并在搜索服务 “概述 ”页中获取 URL。 示例终结点可能类似于
https://mydemo.search.windows.net
。在设置>密钥中,获取服务完整权限的管理员密钥。 有两个可交换的管理员密钥,为保证业务连续性而提供,以防需要滚动一个密钥。 可以在请求中使用主要或辅助密钥来添加、修改和删除对象。
获取查询密钥。 最佳做法是发出具有只读访问权限的查询请求。
所有请求都需要对发送到服务的每个请求使用 API 密钥。 拥有有效密钥可以在发送请求的应用和处理请求的服务之间,根据每个请求,建立信任。
设置环境
首先,打开 Visual Studio,并新建能在 .NET Core 上运行的控制台应用项目。
安装 NuGet 包
Microsoft.Azure.Search 包包含一些作为 NuGet 包分发的客户端库。
对于此项目,请使用 NuGet 包的版本 10 Microsoft.Azure.Search
和最新的 Microsoft.Extensions.Configuration.Json
NuGet 包。
在“工具”>“NuGet 包管理器”中,选择“管理解决方案的 NuGet 包...”。
单击“浏览”。
搜索
Microsoft.Azure.Search
并选择版本 10。单击右侧的 “安装 ”,将程序集添加到项目和解决方案。
重复操作
Microsoft.Extensions.Configuration.Json
,选择版本 2.2.0 或更高版本。
添加 Azure 认知搜索服务信息
在解决方案资源管理器中,右键单击项目并选择“ 添加新>项...” 。
在“添加新项”中,搜索“JSON”以返回与 JSON 相关的项类型列表。
选择 JSON 文件,将文件命名为“appsettings.json”,然后单击“ 添加”。
将文件添加到输出目录。 右键单击 appsettings.json 并选择“ 属性”。 在 复制到输出目录 中,选择 如果较新则复制。
将以下 JSON 复制到新的 JSON 文件中。
{ "SearchServiceName": "<YOUR-SEARCH-SERVICE-NAME>", "SearchServiceAdminApiKey": "<YOUR-ADMIN-API-KEY>", "SearchIndexName": "hotels-quickstart" }
将搜索服务名称(YOUR-SEARCH-SERVICE-NAME)和管理 API 密钥(YOUR-ADMIN-API-KEY)替换为有效值。 如果服务终结点是
https://mydemo.search.windows.net
,则服务名称将为“mydemo
”。
添加类“。方法“文件到项目
此步骤需要在控制台中生成有意义的输出。 将结果打印到控制台窗口时,必须以字符串的形式返回 Hotel 对象中的单个字段。 此步骤实现 ToString() 来执行此任务,为此,需要将必要的代码复制到两个新文件。
向项目添加两个空类定义:Address.Methods.cs、Hotel.Methods.cs
在Address.Methods.cs中,使用以下代码( 第 1-25 行)覆盖默认内容。
在Hotel.Methods.cs中,复制 第 1-68 行。
1 - 创建索引
酒店索引由简单字段和复杂字段组成,其中简单字段为“HotelName”或“Description”,而复杂字段是包含子字段的地址或房间集合。 当索引包含复杂类型时,请在单独的类中隔离复杂字段定义。
向项目添加两个空类定义:Address.cs,Hotel.cs
在Address.cs中,使用以下代码覆盖默认内容:
using System; using Microsoft.Azure.Search; using Microsoft.Azure.Search.Models; using Newtonsoft.Json; namespace AzureSearchQuickstart { public partial class Address { [IsSearchable] public string StreetAddress { get; set; } [IsSearchable, IsFilterable, IsSortable, IsFacetable] public string City { get; set; } [IsSearchable, IsFilterable, IsSortable, IsFacetable] public string StateProvince { get; set; } [IsSearchable, IsFilterable, IsSortable, IsFacetable] public string PostalCode { get; set; } [IsSearchable, IsFilterable, IsSortable, IsFacetable] public string Country { get; set; } } }
在Hotel.cs中,类定义索引的整体结构,包括对地址类的引用。
namespace AzureSearchQuickstart { using System; using Microsoft.Azure.Search; using Microsoft.Azure.Search.Models; using Newtonsoft.Json; public partial class Hotel { [System.ComponentModel.DataAnnotations.Key] [IsFilterable] public string HotelId { get; set; } [IsSearchable, IsSortable] public string HotelName { get; set; } [IsSearchable] [Analyzer(AnalyzerName.AsString.EnMicrosoft)] public string Description { get; set; } [IsSearchable] [Analyzer(AnalyzerName.AsString.FrLucene)] [JsonProperty("Description_fr")] public string DescriptionFr { get; set; } [IsSearchable, IsFilterable, IsSortable, IsFacetable] public string Category { get; set; } [IsSearchable, IsFilterable, IsFacetable] public string[] Tags { get; set; } [IsFilterable, IsSortable, IsFacetable] public bool? ParkingIncluded { get; set; } [IsFilterable, IsSortable, IsFacetable] public DateTimeOffset? LastRenovationDate { get; set; } [IsFilterable, IsSortable, IsFacetable] public double? Rating { get; set; } public Address Address { get; set; } } }
字段上的属性确定它在应用程序中的用法。 例如,
IsSearchable
必须将该属性分配给应包含在全文搜索中的每个字段。注释
在 .NET SDK 中,字段必须显式属性为
IsSearchable
、IsFilterable
和IsSortable
IsFacetable
。 此行为与 REST API 形成鲜明对比,后者基于数据类型自动启用归属功能(例如,简单字符串字段可自动支持搜索)。在您的索引中,类型
string
的确切有一个字段必须是 键 字段,用于唯一标识每个文档。 在此架构中,密钥为HotelId
.在此索引中,描述字段使用了可选的
analyzer
属性,当你希望覆盖默认的标准 Lucene 分析器时加以指定。 该description_fr
字段使用法语 Lucene 分析器(FrLucene),因为它存储法语文本。description
正在使用可选的 Microsoft 语言分析器(EnMicrosoft)。在Program.cs中,使用存储在应用程序的配置文件(appsettings.json)中的值创建类的实例
SearchServiceClient
以连接到服务。SearchServiceClient
具有一个Indexes
属性,提供创建、列出、更新或删除 Azure 认知搜索索引所需的所有方法。using System; using System.Linq; using System.Threading; using Microsoft.Azure.Search; using Microsoft.Azure.Search.Models; using Microsoft.Extensions.Configuration; namespace AzureSearchQuickstart { class Program { // Demonstrates index delete, create, load, and query // Commented-out code is uncommented in later steps static void Main(string[] args) { IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); IConfigurationRoot configuration = builder.Build(); SearchServiceClient serviceClient = CreateSearchServiceClient(configuration); string indexName = configuration["SearchIndexName"]; Console.WriteLine("{0}", "Deleting index...\n"); DeleteIndexIfExists(indexName, serviceClient); Console.WriteLine("{0}", "Creating index...\n"); CreateIndex(indexName, serviceClient); // Uncomment next 3 lines in "2 - Load documents" // ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName); // Console.WriteLine("{0}", "Uploading documents...\n"); // UploadDocuments(indexClient); // Uncomment next 2 lines in "3 - Search an index" // Console.WriteLine("{0}", "Searching index...\n"); // RunQueries(indexClient); Console.WriteLine("{0}", "Complete. Press any key to end application...\n"); Console.ReadKey(); } // Create the search service client private static SearchServiceClient CreateSearchServiceClient(IConfigurationRoot configuration) { string searchServiceName = configuration["SearchServiceName"]; string adminApiKey = configuration["SearchServiceAdminApiKey"]; SearchServiceClient serviceClient = new SearchServiceClient(searchServiceName, new SearchCredentials(adminApiKey)); return serviceClient; } // Delete an existing index to reuse its name private static void DeleteIndexIfExists(string indexName, SearchServiceClient serviceClient) { if (serviceClient.Indexes.Exists(indexName)) { serviceClient.Indexes.Delete(indexName); } } // Create an index whose fields correspond to the properties of the Hotel class. // The Address property of Hotel will be modeled as a complex field. // The properties of the Address class in turn correspond to sub-fields of the Address complex field. // The fields of the index are defined by calling the FieldBuilder.BuildForType() method. private static void CreateIndex(string indexName, SearchServiceClient serviceClient) { var definition = new Microsoft.Azure.Search.Models.Index() { Name = indexName, Fields = FieldBuilder.BuildForType<Hotel>() }; serviceClient.Indexes.Create(definition); } } }
如果可能,请共享应用程序中的
SearchServiceClient
单个实例,以避免打开过多的连接。 类方法是线程安全的,以实现此类共享。该类具有多个构造函数。 您所需要的那个选项将搜索服务名称和
SearchCredentials
对象作为参数。SearchCredentials
封装您的 API 密钥。在索引定义中,创建
Field
对象的最简单方法是调用FieldBuilder.BuildForType
该方法,为类型参数传递模型类。 模型类具有与索引字段对应的属性。 通过此映射,可以将文档从搜索索引绑定到模型类的实例。注释
如果不打算使用模型类,仍可以通过直接创建
Field
对象来定义索引。 可以向构造函数提供字段的名称以及数据类型(或字符串字段的分析器)。 您还可以设置其他属性,例如IsSearchable
,IsFilterable
等。按 F5 生成应用并创建索引。
如果项目成功生成,则会打开控制台窗口,将状态消息写入屏幕以删除和创建索引。
2 - 加载文档
在 Azure 认知搜索中,文档是数据结构,既是索引的输入,也是来自查询的输出。 文档输入从外部数据源获取,可能是数据库中的行、Blob 存储中的 blob 或磁盘上的 JSON 文档。 在此示例中,我们采用了快捷方式,并在代码本身中嵌入了四个酒店的 JSON 文档。
上传文档时,必须使用对象 IndexBatch
。 一个 IndexBatch
包含一个 IndexAction
对象集合,每个对象都包含一个文档和一个属性,用于告知 Azure 认知搜索要执行的操作(上传、合并、删除和合并或上传)。
在Program.cs中,创建文档和索引动作的数组,然后将数组传递给
IndexBatch
。 以下文档符合酒店快速入门索引,该索引由酒店类和地址类定义。// Upload documents as a batch private static void UploadDocuments(ISearchIndexClient indexClient) { var actions = new IndexAction<Hotel>[] { IndexAction.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" } } ), IndexAction.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" } } ), IndexAction.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" } } ), IndexAction.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.6, Address = new Address() { StreetAddress = "7400 San Pedro Ave", City = "San Antonio", StateProvince = "TX", PostalCode = "78216", Country = "USA" } } ), }; var batch = IndexBatch.New(actions); try { indexClient.Documents.Index(batch); } catch (IndexBatchException e) { // When a service is under load, indexing might fail for some documents in the batch. // Depending on your application, you can compensate by delaying and retrying. // For this simple demo, we just log the failed document keys and continue. Console.WriteLine( "Failed to index some of the documents: {0}", String.Join(", ", e.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key))); } // Wait 2 seconds before starting queries Console.WriteLine("Waiting for indexing...\n"); Thread.Sleep(2000); }
初始化
IndexBatch
对象后,可以通过调用Documents.Index
SearchIndexClient
对象将其发送到索引。Documents
是一个属性SearchIndexClient
,它提供用于在索引中添加、修改、删除或查询文档的方法。包围对
Index
方法调用的try
/catch
会捕捉索引失败,这可能发生在服务负载过重时。 在生产代码中,可以延迟并重试对失败的文档编制索引,或像示例一样继续记录文档,或者以满足应用程序数据一致性要求的其他方式处理文档。2 秒的延迟可对索引编制进行补偿(这是异步操作),这样可在执行查询之前对所有文档编制索引。 以延迟方式编写代码通常仅在演示、测试和示例应用程序中是必要的。
在主Program.cs中,取消注释“2 - 加载文档”的行。
// Uncomment next 3 lines in "2 - Load documents" ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName); Console.WriteLine("{0}", "Uploading documents...\n"); UploadDocuments(indexClient);
按 F5 重新生成应用。
如果项目成功生成,控制台窗口将打开并写入状态消息,这一次包含有关上传文档的消息。 在 Azure 门户中的“搜索服务 概述 ”页中,hotels-quickstart 索引现在应包含 4 个文档。
有关文档处理的详细信息,请参阅“.NET SDK 如何处理文档”。
3 - 搜索索引
对第一个文档编制索引后,可立即获取查询结果,但索引的实际测试应等到对所有文档编制索引后进行。
此部分添加了两个功能:查询逻辑和结果。 对于查询,请使用 Search
该方法。 此方法采用搜索文本和其他 参数。
类 DocumentsSearchResult
表示结果。
在Program.cs中创建一个 WriteDocuments 方法,用于将搜索结果输出到控制台。
private static void WriteDocuments(DocumentSearchResult<Hotel> searchResults) { foreach (SearchResult<Hotel> result in searchResults.Results) { Console.WriteLine(result.Document); } Console.WriteLine(); }
创建 RunQueries 方法以执行查询并返回结果。 结果是 Hotel 对象。 可以使用 select 参数来显示各个字段。 如果 select 参数中不包含字段,其相应的 Hotel 属性将为 null。
private static void RunQueries(ISearchIndexClient indexClient) { SearchParameters parameters; DocumentSearchResult<Hotel> results; // Query 1 Console.WriteLine("Query 1: Search for term 'Atlanta' with no result trimming"); parameters = new SearchParameters(); results = indexClient.Documents.Search<Hotel>("Atlanta", parameters); WriteDocuments(results); // Query 2 Console.WriteLine("Query 2: Search on the term 'Atlanta', with trimming"); Console.WriteLine("Returning only these fields: HotelName, Tags, Address:\n"); parameters = new SearchParameters() { Select = new[] { "HotelName", "Tags", "Address" }, }; results = indexClient.Documents.Search<Hotel>("Atlanta", parameters); WriteDocuments(results); // Query 3 Console.WriteLine("Query 3: Search for the terms 'restaurant' and 'wifi'"); Console.WriteLine("Return only these fields: HotelName, Description, and Tags:\n"); parameters = new SearchParameters() { Select = new[] { "HotelName", "Description", "Tags" } }; results = indexClient.Documents.Search<Hotel>("restaurant, wifi", parameters); WriteDocuments(results); // Query 4 -filtered query Console.WriteLine("Query 4: Filter on ratings greater than 4"); Console.WriteLine("Returning only these fields: HotelName, Rating:\n"); parameters = new SearchParameters() { Filter = "Rating gt 4", Select = new[] { "HotelName", "Rating" } }; results = indexClient.Documents.Search<Hotel>("*", parameters); WriteDocuments(results); // Query 5 - top 2 results Console.WriteLine("Query 5: Search on term 'boutique'"); Console.WriteLine("Sort by rating in descending order, taking the top two results"); Console.WriteLine("Returning only these fields: HotelId, HotelName, Category, Rating:\n"); parameters = new SearchParameters() { OrderBy = new[] { "Rating desc" }, Select = new[] { "HotelId", "HotelName", "Category", "Rating" }, Top = 2 }; results = indexClient.Documents.Search<Hotel>("boutique", parameters); WriteDocuments(results); }
查询中有两 种方法匹配字词:全文搜索和筛选器。 全文搜索查询在索引中的
IsSearchable
字段中搜索一个或多个字词。 筛选器是对索引中的IsFilterable
字段计算的布尔表达式。 可以将全文搜索和筛选器一起使用,也可以单独使用。使用
Documents.Search
方法执行搜索和筛选。 可以在searchText
参数中传递搜索查询,同时可以在SearchParameters
类的Filter
属性中传递筛选器表达式。 若要在不搜索的情况下进行筛选,只需传递"*"
参数searchText
即可。 若要在不筛选的情况下进行搜索,只需不设置Filter
属性,或者不传入SearchParameters
实例。在Program.cs,在主部分中取消注释“3 - 搜索”的行。
// Uncomment next 2 lines in "3 - Search an index" Console.WriteLine("{0}", "Searching documents...\n"); RunQueries(indexClient);
解决方案现已完成。 按 F5 可重新生成应用并完整运行该程序。
输出包含与之前相同的消息,并添加了查询信息和结果。
清理资源
在自己的订阅中操作时,最好在项目结束时确定是否仍需要已创建的资源。 持续运行资源可能会产生费用。 可以逐个删除资源,也可以删除资源组以删除整个资源集。
可以使用左侧导航窗格中的“所有资源”或“资源组”链接 ,在门户中查找和管理资源。
如果您使用免费服务,请记住,您只能使用三个索引、索引器和数据源。 可以在门户中删除单个项目,以不超出此限制。
后续步骤
在本 C# 快速入门中,你已完成一系列任务来创建索引、使用文档加载索引以及运行查询。 在不同的阶段,我们采用快捷方式来简化代码,从而实现可读性和可理解性。 如果你熟悉基本概念,我们建议下一篇文章来探索将加深知识的替代方法和概念。
示例代码和索引是此代码的扩展版本。 下一个示例添加房间集合,使用不同的类和动作,并深入了解处理是如何工作的。
希望优化并节省云支出?