如何在 C# .NET 应用程序中使用 Azure.Search.DocumentsHow to use Azure.Search.Documents in a C# .NET Application

本文介绍了如何使用 C# 和 Azure.Search.Documents(版本 11)客户端库来创建和管理搜索对象。This article explains how to create and manage search objects using C# and the Azure.Search.Documents (version 11) client library.

关于版本 11About version 11

用于 .NET 的 Azure SDK 添加了 Azure SDK 团队提供的一个新的 Azure.Search.Documents 客户端库,该客户端库的功能等同于 Microsoft.Azure.Search 客户端库,但根据情况利用常用的方法和约定。Azure SDK for .NET adds a new Azure.Search.Documents client library from the Azure SDK team that is functionally equivalent to Microsoft.Azure.Search client libraries, but utilizes common approaches and conventions where applicable. 示例包括 AzureKeyCredential 密钥身份验证,以及用于 JSON 序列化的 System.Text.Json.SerializationSome examples include AzureKeyCredential key authentication, and System.Text.Json.Serialization for JSON serialization.

与以前的版本一样,你可以使用此库来执行以下操作:As with previous versions, you can use this library to:

  • 创建和管理搜索索引、数据源、索引器、技能组和同义词映射Create and manage search indexes, data sources, indexers, skillsets, and synonym maps
  • 在索引中加载和管理搜索文档Load and manage search documents in an index
  • 执行查询,这些查询全都无需处理 HTTP 和 JSON 的详细信息Execute queries, all without having to deal with the details of HTTP and JSON

此库作为单个 Azure.Search.Document NuGet 包进行分发,其中包括用于以编程方式访问搜索服务的所有 API。The library is distributed as a single Azure.Search.Document NuGet package, which includes all APIs used for programmatic access to a search service.

客户端库会定义 SearchIndexSearchFieldSearchDocument 之类的类,还会定义 SearchIndexClientSearchClient 类中的 SearchIndexClient.CreateIndexSearchClient.Search 之类的操作。The client library defines classes like SearchIndex, SearchField, and SearchDocument, as well as operations like SearchIndexClient.CreateIndex and SearchClient.Search on the SearchIndexClient and SearchClient classes. 这些类已组织成以下命名空间:These classes are organized into the following namespaces:

Azure.Search.Documents(版本 11)适用于 2020-06-30 版的 Azure 认知搜索 REST APIAzure.Search.Documents (version 11) targets version 2020-06-30 of the Azure Cognitive Search REST API.

此客户端库未提供服务管理操作,例如创建和缩放搜索服务以及管理 API 密钥。The client library does not provide service management operations, such as creating and scaling search services and managing API keys. 如果需要从 .NET 应用程序管理搜索资源,可以使用用于 .NET 的 Azure SDK 中的 Microsoft.Azure.Management.Search 库。If you need to manage your search resources from a .NET application, use the Microsoft.Azure.Management.Search library in the Azure SDK for .NET.

升级到 v11Upgrade to v11

如果你一直使用的是早期版本的 .NET SDK,想要升级到当前的正式发布版本,请参阅升级到 Azure 认知搜索 .NET SDK 版本 11If you have been using the previous version of the .NET SDK and you'd like to upgrade to the current generally available version, see Upgrade to Azure Cognitive Search .NET SDK version 11

SDK 要求SDK requirements

  • Visual Studio 2019 或更高版本。Visual Studio 2019 or later.

  • 有自己的 Azure 认知搜索服务。Your own Azure Cognitive Search service. 要使用 SDK,需要服务的名称以及一个或多个 API 密钥。In order to use the SDK, you will need the name of your service and one or more API keys. 在门户中创建一个服务(如果没有)。Create a service in the portal if you don't have one.

  • 在 Visual Studio 中使用“工具” > “NuGet 包管理器” > “管理解决方案的 NuGet 包...”下载 Azure.Search.Documents 包Download the Azure.Search.Documents package using Tools > NuGet Package Manager > Manage NuGet Packages for Solution in Visual Studio. 搜索包名称 Azure.Search.DocumentsSearch for the package name Azure.Search.Documents.

用于 .NET 的 Azure SDK 符合 .NET Standard 2.0,这意味着 .NET Framework 4.6.1 和 .NET Core 2.0 是最低要求。Azure SDK for .NET conforms to .NET Standard 2.0, which means .NET Framework 4.6.1 and .NET Core 2.0 as minimum requirements.

示例应用程序Example application

本文“通过示例进行讲授”,依赖于 GitHub 上的 DotNetHowTo 代码示例来说明 Azure 认知搜索中的基本概念,尤其是如何创建、加载和查询搜索索引。This article "teaches by example", relying on the DotNetHowTo code example on GitHub to illustrate fundamental concepts in Azure Cognitive Search - specifically, how to create, load, and query a search index.

对于本文的其余部分,假定有一个名为“hotels”的新索引,该索引中通过对结果进行匹配的多个查询填充了一些文档。For the rest of this article, assume a new index named "hotels", populated with a few documents, with several queries that match on results.

下面是展示总体流程的主程序:Below is the main program, showing the overall flow:

// This sample shows how to delete, create, upload documents and query an index
static void Main(string[] args)
{
    IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
    IConfigurationRoot configuration = builder.Build();

    SearchIndexClient indexClient = CreateSearchIndexClient(configuration);

    string indexName = configuration["SearchIndexName"];

    Console.WriteLine("{0}", "Deleting index...\n");
    DeleteIndexIfExists(indexName, indexClient);

    Console.WriteLine("{0}", "Creating index...\n");
    CreateIndex(indexName, indexClient);

    SearchClient searchClient = indexClient.GetSearchClient(indexName);

    Console.WriteLine("{0}", "Uploading documents...\n");
    UploadDocuments(searchClient);

    SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

    Console.WriteLine("{0}", "Run queries...\n");
    RunQueries(indexClientForQueries);

    Console.WriteLine("{0}", "Complete.  Press any key to end application...\n");
    Console.ReadKey();
}

接下来是输出的部分屏幕截图,假设你使用有效的服务名称和 API 密钥运行此应用程序:Next is a partial screenshot of the output, assuming you run this application with a valid service name and API keys:

来自示例程序的 Console.WriteLine 输出

客户端类型Client types

客户端库使用三种客户端类型执行各种操作:SearchIndexClient 用于创建、更新或删除索引;SearchClient 用于加载或查询索引;SearchIndexerClient 用于处理索引器和技能组。The client library uses three client types for various operations: SearchIndexClient to create, update, or delete indexes, SearchClient to load or query an index, and SearchIndexerClient to work with indexers and skillsets. 本文重点介绍了前两种类型。This article focuses on the first two.

所有客户端都至少需要服务名称或终结点,以及一个 API 密钥。At a minimum, all of the clients require the service name or endpoint, and an API key. 通常在配置文件中提供此信息,它类似于在 DotNetHowTo 示例应用程序appsettings.json 文件中找到的内容。It's common to provide this information in a configuration file, similar to what you find in the appsettings.json file of the DotNetHowTo sample application. 若要从配置文件中读取数据,请将 using Microsoft.Extensions.Configuration; 添加到程序。To read from the configuration file, add using Microsoft.Extensions.Configuration; to your program.

下面的语句创建用于创建、更新或删除索引的索引客户端。The following statement creates the index client used to create, update, or delete indexes. 它采用搜索终结点和管理 API 密钥。It takes a search endpoint and admin API key.

private static SearchIndexClient CreateSearchIndexClient(IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["SearchServiceEndPoint"];
    string adminApiKey = configuration["SearchServiceAdminApiKey"];

    SearchIndexClient indexClient = new SearchIndexClient(new Uri(searchServiceEndPoint), new AzureKeyCredential(adminApiKey));
    return indexClient;
}

下一个语句创建用于加载文档或运行查询的搜索客户端。The next statement creates the search client used to load documents or run queries. SearchClient 需要一个索引。SearchClient requires an index. 你将需要一个管理 API 密钥来加载文档,但可以使用查询 API 密钥来运行查询。You will need an admin API key to load documents, but you can use a query API key to run queries.

string indexName = configuration["SearchIndexName"];

private static SearchClient CreateSearchClientForQueries(string indexName, IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["SearchServiceEndPoint"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), indexName, new AzureKeyCredential(queryApiKey));
    return searchClient;
}

备注

如果为导入操作提供了无效的密钥(例如,在其中需要管理密钥的查询密钥),则首次调用 SearchClient 上的操作方法时,它会引发 CloudException 并显示错误消息“已禁用”。If you provide an invalid key for the import operation (for example, a query key where an admin key was required), the SearchClient will throw a CloudException with the error message "Forbidden" the first time you call an operation method on it. 如果遇到此情况,请仔细检查 API 密钥。If this happens to you, double-check the API key.

删除索引Deleting the index

在早期开发阶段,你可能希望包括一个 DeleteIndex 语句来删除正在执行的索引,以便可以使用更新后的定义重新创建它。In the early stages of development, you might want to include a DeleteIndex statement to delete a work-in-progress index so that you can recreate it with an updated definition. Azure 认知搜索的示例代码通常包含一个删除步骤,以便你可以重新运行该示例。Sample code for Azure Cognitive Search often includes a deletion step so that you can re-run the sample.

下面的行调用 DeleteIndexIfExistsThe following line calls DeleteIndexIfExists:

Console.WriteLine("{0}", "Deleting index...\n");
DeleteIndexIfExists(indexName, indexClient);

此方法使用给定的 SearchIndexClient 来检查索引是否存在,如果存在,则删除该索引:This method uses the given SearchIndexClient to check if the index exists, and if so, deletes it:

private static void DeleteIndexIfExists(string indexName, SearchIndexClient indexClient)
{
    try
    {
        if (indexClient.GetIndex(indexName) != null)
        {
            indexClient.DeleteIndex(indexName);
        }
    }
    catch (RequestFailedException e) when (e.Status == 404)
    {
        // Throw an exception if the index name isn't found
        Console.WriteLine("The index doesn't exist. No deletion occurred.");

备注

为了简单起见,本文中的示例代码使用了同步方法,但你应在自己的应用程序中使用异步方法,以使它们保持缩放能力和响应能力。The example code in this article uses the synchronous methods for simplicity, but you should use the asynchronous methods in your own applications to keep them scalable and responsive. 例如,在上述方法中,你可以使用 DeleteIndexAsync 而非 DeleteIndexFor example, in the method above you could use DeleteIndexAsync instead of DeleteIndex.

创建索引Create an index

你可以使用 SearchIndexClient 来创建索引。You can use SearchIndexClient to create an index.

以下方法使用 SearchField 对象的列表创建一个新的 SearchIndex 对象,这些对象定义新索引的架构。The method below creates a new SearchIndex object with a list of SearchField objects that define the schema of the new index. 每个字段都有名称、数据类型和数个属性(定义其搜索行为)。Each field has a name, data type, and several attributes that define its search behavior.

可以使用 FieldBuilder 通过模型类来定义字段。Fields can be defined from a model class using FieldBuilder. FieldBuilder 类通过检查给定 Hotel 模型类的公共属性和特性,使用反射来为索引创建 SearchField 对象的列表。The FieldBuilder class uses reflection to create a list of SearchField objects for the index by examining the public properties and attributes of the given Hotel model class. 我们会在以后详细地查看 Hotel 类。We'll take a closer look at the Hotel class later on.

private static void CreateIndex(string indexName, SearchIndexClient indexClient)
{
    FieldBuilder fieldBuilder = new FieldBuilder();
    var searchFields = fieldBuilder.Build(typeof(Hotel));

    var definition = new SearchIndex(indexName, searchFields);

    indexClient.CreateOrUpdateIndex(definition);
}

除了字段之外,还可以向索引添加计分配置文件、建议器或 CORS 选项(为简洁起见,示例中省略了这些参数)。Besides fields, you could also add scoring profiles, suggesters, or CORS options to the index (these parameters are omitted from the sample for brevity). 可在 SearchIndex 属性列表以及 REST API 参考中找到有关 SearchIndex 对象及其组成部分的详细信息。You can find more information about the SearchIndex object and its constituent parts in the SearchIndex properties list, as well as in the REST API reference.

备注

如果需要,始终可以直接创建 Field 对象的列表,而不是使用 FieldBuilderYou can always create the list of Field objects directly instead of using FieldBuilder if needed. 例如,你可能不想使用模型类,或者可能需要使用不希望通过添加属性来进行修改的现有模型类。For example, you may not want to use a model class or you may need to use an existing model class that you don't want to modify by adding attributes.

在 Main() 中调用 CreateIndexCall CreateIndex in Main()

Main 通过调用以上方法创建新的“hotels”索引:Main creates a new "hotels" index by calling the above method:

Console.WriteLine("{0}", "Creating index...\n");
CreateIndex(indexName, indexClient);

使用模型类来表示数据Use a model class for data representation

DotNetHowTo 示例为 HotelAddressRoom 数据结构使用了模型类。The DotNetHowTo sample uses model classes for the Hotel, Address, and Room data structures. Hotel 引用了 Address(一个单一级别的复杂类型,多部件字段)和 Room(多部件字段的集合)。Hotel references Address, a single level complex type (a multi-part field), and Room (a collection of multi-part fields).

你可以使用这些类型来创建和加载索引,以及构建查询的响应:You can use these types to create and load the index, and to structure the response from a query:

// Use-case: <Hotel> in a field definition
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));

// Use-case: <Hotel> in a response
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

另一种方法是直接向索引添加字段。An alternative approach is to add fields to an index directly. 以下示例只显示了几个字段。The following example shows just a few fields.

 SearchIndex index = new SearchIndex(indexName)
 {
     Fields =
         {
             new SimpleField("hotelId", SearchFieldDataType.String) { IsKey = true, IsFilterable = true, IsSortable = true },
             new SearchableField("hotelName") { IsFilterable = true, IsSortable = true },
             new SearchableField("hotelCategory") { IsFilterable = true, IsSortable = true },
             new SimpleField("baseRate", SearchFieldDataType.Int32) { IsFilterable = true, IsSortable = true },
             new SimpleField("lastRenovationDate", SearchFieldDataType.DateTimeOffset) { IsFilterable = true, IsSortable = true }
         }
 };

字段定义Field definitions

.NET 中的数据模型及其相应的索引架构应该支持面向最终用户的搜索体验。Your data model in .NET and its corresponding index schema should support the search experience you'd like to give to your end user. .NET 中的每个顶级对象(例如搜索索引中的搜索文档)对应于在用户界面中显示的搜索结果。Each top level object in .NET, such as a search document in a search index, corresponds to a search result you would present in your user interface. 例如,在酒店搜索应用程序中,最终用户可能想要按酒店名称、酒店特色或特定客房的特征进行搜索。For example, in a hotel search application your end users may want to search by hotel name, features of the hotel, or the characteristics of a particular room.

在每个类中,都为字段定义了数据类型和属性,属性决定了如何使用该字段。Within each class, a field is defined with a data type and attributes that determine how it's used. 每个类中的每个公共属性的名称会映射到索引定义中的同名字段。The name of each public property in each class maps to a field with the same name in the index definition.

请看下面的代码片段,该代码片段从 Hotel 类中拉取多个字段定义。Take a look at the following snippet that pulls several field definitions from the Hotel class. 请注意,Address 和 Rooms 是 C# 类型,具有自己的类定义(如果要查看它们,请参阅示例代码)。Notice that Address and Rooms are C# types with their own class definitions (refer to the sample code if you want to view them). 这两者都是复杂类型。Both are complex types. 有关详细信息,请参阅如何为复杂类型建模For more information, see How to model complex types.

public partial class Hotel
{
    [SimpleField(IsKey = true, IsFilterable = true)]
    public string HotelId { get; set; }

    [SearchableField(IsSortable = true)]
    public string HotelName { get; set; }

    [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
    public string Description { get; set; }

    [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
    public string Category { get; set; }

    [JsonIgnore]
    public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

    [SearchableField]
    public Address Address { get; set; }

    public Room[] Rooms { get; set; }

选择字段类Choosing a field class

定义字段时,你可以使用 SearchField 基类,也可以使用充当“模板”的派生帮助程序模型并使用预配置的属性。When defining fields, you can use the base SearchField class, or you can use derivative helper models that serve as "templates", with pre-configured properties.

索引中必须恰好有一个字段充当文档键 (IsKey = true)。Exactly one field in your index must serve as the document key (IsKey = true). 它必须是字符串,并且必须唯一标识每个文档。It must be a string, and it must uniquely identify each document. 它还需要具有 IsHidden = true,这意味着它在搜索结果中不可见。It's also required to have IsHidden = true, which means it cannot be visible in search results.

字段类型Field type 说明和用法Description and usage
SearchField 基类,其中的大多数属性设置为 null,但必需的 Name 和默认为标准 Lucene 的 AnalyzerName 例外。Base class, with most properties set to null, excepting Name which is required, and AnalyzerName which defaults to standard Lucene.
SimpleField 帮助程序模型。Helper model. 可以是任何数据类型,始终不可搜索(全文搜索查询会忽略它),但可检索(未隐藏)。Can be any data type, is always non-searchable (it's ignored for full text search queries), and is retrievable (it's not hidden). 其他属性默认情况下处于关闭状态,但可以启用。Other attributes are off by default, but can be enabled. 你可能会将 SimpleField 用于仅在筛选器、facet 或计分概要文件中使用的文档 ID 或字段。You might use a SimpleField for document IDs or fields used only in filters, facets, or scoring profiles. 如果是这样,请确保应用该方案所需的所有属性,例如为文档 ID 应用 IsKey = trueIf so, be sure to apply any attributes that are necessary for the scenario, such as IsKey = true for a document ID. 有关详细信息,请参阅源代码中的 SimpleFieldAttribute.csFor more information, see SimpleFieldAttribute.cs in source code.
SearchableField 帮助程序模型。Helper model. 必须是字符串,始终可搜索且可检索。Must be a string, and is always searchable and retrievable. 其他属性默认情况下处于关闭状态,但可以启用。Other attributes are off by default, but can be enabled. 因为此字段类型是可搜索的,所以它支持同义词和分析器属性的完整补集。Because this field type is searchable, it supports synonyms and the full complement of analyzer properties. 有关详细信息,请参阅源代码中的 SearchableFieldAttribute.csFor more information, see the SearchableFieldAttribute.cs in source code.

无论使用基本 SearchField API 还是任一帮助程序模型,都必须显式启用筛选器、facet 和排序属性。Whether you use the basic SearchField API or either one of the helper models, you must explicitly enable filter, facet, and sort attributes. 例如,IsFilterableIsSortableIsFacetable 必须进行显式属性化,如上例中所示。For example, IsFilterable, IsSortable, and IsFacetable must be explicitly attributed, as shown in the sample above.

添加字段属性Adding field attributes

请注意每个字段如何通过属性(例如 IsFilterableIsSortableIsKeyAnalyzerName)进行修饰。Notice how each field is decorated with attributes such as IsFilterable, IsSortable, IsKey, and AnalyzerName. 这些属性直接映射到 Azure 认知搜索索引中的相应字段属性These attributes map directly to the corresponding field attributes in an Azure Cognitive Search index. FieldBuilder 类使用这些属性来构造索引的字段定义。The FieldBuilder class uses these properties to construct field definitions for the index.

字段类型映射Field type mapping

属性的 .NET 类型映射到它们在索引定义中的等效字段类型。The .NET types of the properties map to their equivalent field types in the index definition. 例如,Category 字符串属性映射到 Edm.String 类型的 category 字段。For example, the Category string property maps to the category field, which is of type Edm.String. bool?Edm.BooleanDateTimeOffset?Edm.DateTimeOffset 等之间存在类似的类型映射。There are similar type mappings between bool?, Edm.Boolean, DateTimeOffset?, and Edm.DateTimeOffset and so on.

你是否注意到了 SmokingAllowed 属性?Did you happen to notice the SmokingAllowed property?

[JsonIgnore]
public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

此属性的 JsonIgnore 特性告知 FieldBuilder 不要将其序列化为字段形式的索引。The JsonIgnore attribute on this property tells the FieldBuilder to not serialize it to the index as a field. 这是创建可在应用程序中用作帮助器的客户端计算属性的极佳方法。This is a great way to create client-side calculated properties you can use as helpers in your application. 在这种情况下,SmokingAllowed 属性将反映 Rooms 集合中的任何 Room 是否允许吸烟。In this case, the SmokingAllowed property reflects whether any Room in the Rooms collection allows smoking. 如果全部为 false,则表示整个酒店不允许吸烟。If all are false, it indicates that the entire hotel does not allow smoking.

加载索引Load an index

Main 中的下一步骤填充新建的“hotels”索引。The next step in Main populates the newly-created "hotels" index. 此索引填充操作是通过以下方法完成的:(为方便演示,某些代码已替换为“...”。This index population is done in the following method: (Some code replaced with "..." for illustration purposes. 有关完整的数据填充代码,请参阅完整的示例解决方案。)See the full sample solution for the full data population code.)

private static void UploadDocuments(SearchClient searchClient)
{
    IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "1",
                HotelName = "Secret Point Motel",
                ...
                Address = new Address()
                {
                    StreetAddress = "677 5th Ave",
                    ...
                },
                Rooms = new Room[]
                {
                    new Room()
                    {
                        Description = "Budget Room, 1 Queen Bed (Cityside)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Budget Room, 1 King Bed (Mountain View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Deluxe Room, 2 Double Beds (City View)",
                        ...
                    }
                }
            }),
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "2",
                HotelName = "Twin Dome Motel",
                ...
                {
                    StreetAddress = "140 University Town Center Dr",
                    ...
                },
                Rooms = new Room[]
                {
                    new Room()
                    {
                        Description = "Suite, 2 Double Beds (Mountain View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Standard Room, 1 Queen Bed (City View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Budget Room, 1 King Bed (Waterfront View)",
                        ...
                    }
                }
            }),
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "3",
                HotelName = "Triple Landscape Hotel",
                ...
                Address = new Address()
                {
                    StreetAddress = "3393 Peachtree Rd",
                    ...
                },
                Rooms = new Room[]
                {
                    new Room()
                    {
                        Description = "Standard Room, 2 Queen Beds (Amenities)",
                        ...
                    },
                    new Room ()
                    {
                        Description = "Standard Room, 2 Double Beds (Waterfront View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Deluxe Room, 2 Double Beds (Cityside)",
                        ...
                    }
                }
            }
        };

    try
    {
        IndexDocumentsResult result = searchClient.IndexDocuments(batch);
    }
    catch (Exception)
    {
        // Sometimes when your Search service is under load, indexing will fail for some of the documents in
        // the batch. Depending on your application, you can take compensating actions like 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}");
    }

    Console.WriteLine("Waiting for documents to be indexed...\n");
    Thread.Sleep(2000);

此方法有四个部分。This method has four parts. 第一个部分创建包含 3 个 Hotel 对象的数组,其中每个对象包含 3 个用作要上传到索引的输入数据的 Room 对象。The first creates an array of 3 Hotel objects each with 3 Room objects that will serve as our input data to upload to the index. 为简单起见,此数据是硬编码的。This data is hard-coded for simplicity. 在实际应用程序中,数据可能来自外部数据源,例如 SQL 数据库。In an actual application, data will likely come from an external data source such as a SQL database.

第二部分创建包含文档的 IndexDocumentsBatchThe second part creates an IndexDocumentsBatch containing the documents. 创建 Batch 时,指定要应用到 Batch 的操作,在本例中是通过调用 IndexDocumentsAction.Upload 实现的。You specify the operation you want to apply to the batch at the time you create it, in this case by calling IndexDocumentsAction.Upload. 然后,使用 IndexDocuments 方法将批上传到 Azure 认知搜索索引。The batch is then uploaded to the Azure Cognitive Search index by the IndexDocuments method.

备注

在本示例中,我们只需上传文档。In this example, we are just uploading documents. 如果想要将更改合并到现有文档或删除文档,可以改为调用 IndexDocumentsAction.MergeIndexDocumentsAction.MergeOrUploadIndexDocumentsAction.Delete 创建 Batch。If you wanted to merge changes into existing documents or delete documents, you could create batches by calling IndexDocumentsAction.Merge, IndexDocumentsAction.MergeOrUpload, or IndexDocumentsAction.Delete instead. 还可以通过调用 IndexBatch.New 在单个批中混用不同操作,这会用到一个 IndexDocumentsAction 对象的集合,其中的每个对象都会指示 Azure 认知搜索对文档执行特定操作。You can also mix different operations in a single batch by calling IndexBatch.New, which takes a collection of IndexDocumentsAction objects, each of which tells Azure Cognitive Search to perform a particular operation on a document. 可以通过调用 IndexDocumentsAction.MergeIndexAction.Upload 之类的相应方法,创建自带操作的每个 IndexDocumentsActionYou can create each IndexDocumentsAction with its own operation by calling the corresponding method such as IndexDocumentsAction.Merge, IndexAction.Upload, and so on.

此方法的第三部分是处理索引重要错误情况的 catch 块。The third part of this method is a catch block that handles an important error case for indexing. 如果你的搜索服务无法为批中的某些文档编制索引,IndexDocuments 会引发 IndexBatchExceptionIf your search service fails to index some of the documents in the batch, an IndexBatchException is thrown by IndexDocuments. 如果在服务负载过大时为文档编制索引,可能会发生此异常。This exception can happen if you are indexing documents while your service is under heavy load. 强烈建议在代码中显式处理这种情况。We strongly recommend explicitly handling this case in your code. 可以延迟为失败的文档编制索引,并重试,也可以像此示例一样记录并继续执行,还可以执行其他操作,具体取决于应用程序对数据一致性的要求。You can delay and then retry indexing the documents that failed, or you can log and continue like the sample does, or you can do something else depending on your application's data consistency requirements.

最后,UploadDocuments 方法延迟了两秒钟。Finally, the UploadDocuments method delays for two seconds. 编制索引在搜索服务中异步进行,因此,示例应用程序需要等待很短时间,以确保文档可用于搜索。Indexing happens asynchronously in your search service, so the sample application needs to wait a short time to ensure that the documents are available for searching. 此类延迟通常仅在演示、测试和示例应用程序中是必需的。Delays like this are typically only necessary in demos, tests, and sample applications.

在 Main() 中调用 UploadDocumentsCall UploadDocuments in Main()

下面的代码片段使用 indexClient 的 GetSearchClient 方法设置 SearchClient 的实例。The following code snippet sets up an instance of SearchClient using the GetSearchClient method of indexClient. IndexClient 在其请求上使用管理 API 密钥,这是加载或刷新文档所必需的。The indexClient uses an admin API key on its requests, which is required for loading or refreshing documents.

另一种方法是直接调用 SearchClient ,并在 AzureKeyCredential 上传入管理 API 密钥。An alternate approach is to call SearchClient directly, passing in an admin API key on AzureKeyCredential.

SearchClient searchClient = indexClient.GetSearchClient(indexName);

Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(searchClient);

运行查询Run queries

首先,设置从 appsettings.json 读取搜索终结点和查询 API 密钥的 SearchClientFirst, set up a SearchClient that reads the search endpoint and query API key from appsettings.json:

private static SearchClient CreateSearchClientForQueries(string indexName, IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["SearchServiceEndPoint"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), indexName, new AzureKeyCredential(queryApiKey));
    return searchClient;
}

其次,定义发送查询请求的方法。Second, define a method that sends a query request.

此方法在每次执行查询时都创建一个新的 SearchOptions 对象。Each time the method executes a query, it creates a new SearchOptions object. 此对象用于为查询指定其他选项,如排序、筛选、分页和分面。This object is used to specify additional options for the query such as sorting, filtering, paging, and faceting. 在此方法中,我们为不同的查询设置 FilterSelectOrderBy 属性。In this method, we're setting the Filter, Select, and OrderBy property for different queries. 有关搜索查询表达式语法的详细信息,请参阅简单查询语法For more information about the search query expression syntax, Simple query syntax.

下一步是实际执行搜索查询。The next step is to actually execute the search query. 使用 SearchClient.Search 方法运行搜索。Running the search is done using the SearchClient.Search method. 对于每个查询,请以字符串形式传递要使用的搜索文本(或 "*",如果没有搜索文本),以及先前创建的搜索选项。For each query, pass the search text to use as a string (or "*" if there is no search text), plus the search options created earlier. 此外,我们指定 Hotel 作为 SearchClient.Search 的类型参数,这指示 SDK 将搜索结果中的文档反序列化为类型为 Hotel 的对象。We also specify Hotel as the type parameter for SearchClient.Search, which tells the SDK to deserialize documents in the search results into objects of type Hotel.

private static void RunQueries(SearchClient searchClient)
{
    SearchOptions options;
    SearchResults<Hotel> results;

    Console.WriteLine("Query 1: Search for 'motel'. Return only the HotelName in results:\n");

    options = new SearchOptions();
    options.Select.Add("HotelName");

    results = searchClient.Search<Hotel>("motel", options);

    WriteDocuments(results);

    Console.Write("Query 2: Apply a filter to find hotels with rooms cheaper than $100 per night, ");
    Console.WriteLine("returning the HotelId and Description:\n");

    options = new SearchOptions()
    {
        Filter = "Rooms/any(r: r/BaseRate lt 100)"
    };
    options.Select.Add("HotelId");
    options.Select.Add("Description");

    results = searchClient.Search<Hotel>("*", options);

    WriteDocuments(results);

    Console.Write("Query 3: Search the entire index, order by a specific field (lastRenovationDate) ");
    Console.Write("in descending order, take the top two results, and show only hotelName and ");
    Console.WriteLine("lastRenovationDate:\n");

    options =
        new SearchOptions()
        {
            Size = 2
        };
    options.OrderBy.Add("LastRenovationDate desc");
    options.Select.Add("HotelName");
    options.Select.Add("LastRenovationDate");

    results = searchClient.Search<Hotel>("*", options);

    WriteDocuments(results);

    Console.WriteLine("Query 4: Search the HotelName field for the term 'hotel':\n");

    options = new SearchOptions();
    options.SearchFields.Add("HotelName");

    //Adding details to select, because "Location" is not supported yet when deserialize search result to "Hotel"
    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Description");
    options.Select.Add("Category");
    options.Select.Add("Tags");
    options.Select.Add("ParkingIncluded");
    options.Select.Add("LastRenovationDate");
    options.Select.Add("Rating");
    options.Select.Add("Address");
    options.Select.Add("Rooms");

    results = searchClient.Search<Hotel>("hotel", options);

    WriteDocuments(results);
}

第三,定义编写响应的方法,将每个文档输出到控制台:Third, define a method that writes the response, printing each document to the console:

private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

在 Main() 中调用 RunQueriesCall RunQueries in Main()

SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

Console.WriteLine("{0}", "Running queries...\n");
RunQueries(indexClientForQueries);

探究查询构造Explore query constructs

让我们依次仔细查看每个查询。Let's take a closer look at each of the queries in turn. 下面是用于执行第一个查询的代码:Here is the code to execute the first query:

options = new SearchOptions();
options.Select.Add("HotelName");

results = searchClient.Search<Hotel>("motel", options);

WriteDocuments(results);

在本例中,我们将在任何可搜索字段中搜索“motel”一词的整个索引,并且我们只检索 Select 选项指定的酒店名称。In this case, we're searching the entire index for the word "motel" in any searchable field and we only want to retrieve the hotel names, as specified by the Select option. 结果如下:Here are the results:

Name: Secret Point Motel

Name: Twin Dome Motel

在第二个查询中,使用筛选器来选择每晚费用低于 $100 的房间。In the second query, use a filter to select room with a nightly rate of less than $100. 在结果中仅返回酒店 ID 和说明:Return only the hotel ID and description in the results:

options = new SearchOptions()
{
    Filter = "Rooms/any(r: r/BaseRate lt 100)"
};
options.Select.Add("HotelId");
options.Select.Add("Description");

results = searchClient.Search<Hotel>("*", options);

以上查询使用 OData $filter 表达式 Rooms/any(r: r/BaseRate lt 100) 来筛选索引中的文档。The above query uses an OData $filter expression, Rooms/any(r: r/BaseRate lt 100), to filter the documents in the index. 这会使用 any 运算符将“BaseRate lt 100”应用到 Rooms 集合中的每个项。This uses the any operator to apply the 'BaseRate lt 100' to every item in the Rooms collection. 有关详细信息,请参阅 OData 筛选器语法For more information, see OData filter syntax.

在第三个查询中,查找最近翻修的前两个酒店,并显示酒店名称和上次翻修日期。In the third query, find the top two hotels that have been most recently renovated, and show the hotel name and last renovation date. 代码如下:Here is the code:

options =
    new SearchOptions()
    {
        Size = 2
    };
options.OrderBy.Add("LastRenovationDate desc");
options.Select.Add("HotelName");
options.Select.Add("LastRenovationDate");

results = searchClient.Search<Hotel>("*", options);

WriteDocuments(results);

在最后一个查询中,查找与“motel”一词匹配的所有酒店名称:In the last query, find all hotels names that match the word "hotel":

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Description");
options.Select.Add("Category");
options.Select.Add("Tags");
options.Select.Add("ParkingIncluded");
options.Select.Add("LastRenovationDate");
options.Select.Add("Rating");
options.Select.Add("Address");
options.Select.Add("Rooms");

results = searchClient.Search<Hotel>("hotel", options);

WriteDocuments(results);

本部分总结了对 .NET SDK 的介绍,但请不要在这里停止。This section concludes this introduction to the .NET SDK, but don't stop here. 下一部分提供了其他资源,用于详细了解如何通过 Azure 认知搜索进行编程。The next section suggests additional resources for learning more about programming with Azure Cognitive Search.

后续步骤Next steps