如何在 .NET 应用程序中使用 Microsoft.Azure.Search (v10)How to use Microsoft.Azure.Search (v10) in a .NET Application

本文介绍了如何使用 C# 和 Azure 认知搜索 (v10) .NET SDK 来创建和管理搜索对象。This article explains how to create and manage search objects using C# and the Azure Cognitive Search (v10) .NET SDK. 版本 10 是 Microsoft.Azure.Search 包的最新版。Version 10 is the last version of the Microsoft.Azure.Search package. 今后,Azure SDK 团队将会在 Azure.Search.Documents 中推出新功能。Moving forward, new features will be rolled out in Azure.Search.Documents from the Azure SDK team.

如果你有现有的或正在进行外部测试的开发项目,请继续使用版本 10。If you have existing or inflight development projects, continue to use version 10. 对于新项目或者是要使用新功能的情况,则应将现有的搜索解决方案转换到新库。For new projects, or to use new features, you should transition an existing search solution to the new library.

版本 10 中的功能What's in version 10

SDK 包括一些客户端库。借助它,不仅可以管理索引、数据源、索引器和同义词映射,还能上传和管理文档并执行查询,所有这些操作都无需处理 HTTP 和 JSON 的详细信息。The SDK consists of a few client libraries that enable you to manage your indexes, data sources, indexers, and synonym maps, as well as upload and manage documents, and execute queries, all without having to deal with the details of HTTP and JSON. 这些客户端库全部作为 NuGet 包进行分发。These client libraries are all distributed as NuGet packages.

主 NuGet 包是 Microsoft.Azure.Search,它是一个元包,包括所有作为依赖关系的其他程序包。The main NuGet package is Microsoft.Azure.Search, which is a meta-package that includes all the other packages as dependencies. 如果你刚入门,或者如果你知道应用程序将需要 Azure 认知搜索的所有功能,请使用此程序包。Use this package if you're just getting started or if you know your application will need all the features of Azure Cognitive Search.

SDK 中的其他 NuGet 程序包有:The other NuGet packages in the SDK are:

  • Microsoft.Azure.Search.Data:如果使用 Azure 认知搜索开发 .NET 应用程序,则使用此包,并且只需查询或更新索引中的文档。Microsoft.Azure.Search.Data: Use this package if you're developing a .NET application using Azure Cognitive Search, and you only need to query or update documents in your indexes. 如果还需要创建或更新索引、同义词映射或其他服务级资源,请改用 Microsoft.Azure.Search 包。If you also need to create or update indexes, synonym maps, or other service-level resources, use the Microsoft.Azure.Search package instead.
  • Microsoft.Azure.Search.Service:如果在 .NET 中开发自动化以管理 Azure 认知搜索索引、同义词映射、索引器、数据源或其他服务级资源,请使用此包。Microsoft.Azure.Search.Service: Use this package if you're developing automation in .NET to manage Azure Cognitive Search indexes, synonym maps, indexers, data sources, or other service-level resources. 如果只需要查询或更新索引中的文档,请改用 Microsoft.Azure.Search.Data 包。If you only need to query or update documents in your indexes, use the Microsoft.Azure.Search.Data package instead. 如果需要 Azure 认知搜索的所有功能,请改用 Microsoft.Azure.Search 包。If you need all the functionality of Azure Cognitive Search, use the Microsoft.Azure.Search package instead.
  • Microsoft.Azure.Search.Common:Azure 认知搜索 .NET 库需要的常见类型。Microsoft.Azure.Search.Common: Common types needed by the Azure Cognitive Search .NET libraries. 无需在应用程序中直接使用此包。You do not need to use this package directly in your application. 此包仅用作依赖项。It is only meant to be used as a dependency.

各种客户端库定义 IndexFieldDocument 等类,以及 SearchServiceClientSearchIndexClient 类中的 Indexes.CreateDocuments.Search 等操作。The various client libraries define classes like Index, Field, and Document, as well as operations like Indexes.Create and Documents.Search on the SearchServiceClient and SearchIndexClient classes. 这些类已组织成以下命名空间:These classes are organized into the following namespaces:

如果想要为 SDK 的未来更新提供反馈,请参阅我们的反馈页,或者在 GitHub 上创建问题并在问题标题中提到“Azure 认知搜索”。If you would like to provide feedback for a future update of the SDK, see our feedback page or create an issue on GitHub and mention "Azure Cognitive Search" in the issue title.

.NET SDK 面向 2019-05-06Azure 认知搜索 REST APIThe .NET SDK targets version 2019-05-06 of the Azure Cognitive Search REST API. 此版本包括在为 Azure Blob 编制索引时所需的对复杂类型AI 增强自动完成JsonLines 分析模式的支持。This version includes support for complex types, AI enrichment, autocomplete, and JsonLines parsing mode when indexing Azure Blobs.

此 SDK 不支持管理操作(如创建和缩放搜索服务以及管理 API 密钥)。This SDK does not support Management Operations such as creating and scaling Search services and managing API keys. 如果需要从 .NET 应用程序管理搜索资源,可以使用 Azure 认知搜索 .NET 管理 SDKIf you need to manage your Search resources from a .NET application, you can use the Azure Cognitive Search .NET Management SDK.

升级到最新版本的 SDKUpgrading to the latest version of the SDK

如果你已在使用较旧版本的 Azure 认知搜索 .NET SDK,并且想要升级到最新的正式版,此文介绍了操作方法。If you're already using an older version of the Azure Cognitive Search .NET SDK and you'd like to upgrade to the latest generally available version, this article explains how.

SDK 的要求Requirements for the SDK

  1. Visual Studio 2017 或更高版本。Visual Studio 2017 or later.
  2. 有自己的 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 will help you through these steps.
  3. 在 Visual Studio 中,通过使用“管理 NuGet 包”来下载 Azure 认知搜索 .NET SDK NuGet 包Download the Azure Cognitive Search .NET SDK NuGet package by using "Manage NuGet Packages" in Visual Studio. 只需在 NuGet.org 上搜索程序包名称 Microsoft.Azure.Search(或者如果你只需要其中一部分功能,则可以搜索上述其中一个其他程序包名称)。Just search for the package name Microsoft.Azure.Search on NuGet.org (or one of the other package names above if you only need a subset of the functionality).

Azure 认知搜索 .NET SDK 支持面向 .NET Framework 4.5.2 和更高版本以及 .NET Core 2.0 和更高版本的应用程序。The Azure Cognitive Search .NET SDK supports applications targeting the .NET Framework 4.5.2 and higher, as well as .NET Core 2.0 and higher.

核心方案Core scenarios

需要在搜索应用程序中完成几项操作。There are several things you'll need to do in your search application. 在本教程中,我们介绍以下核心方案:In this tutorial, we'll cover these core scenarios:

  • 创建索引Creating an index
  • 使用文档填充索引Populating the index with documents
  • 使用全文搜索和筛选器搜索文档Searching for documents using full-text search and filters

以下示例代码演示了上述每个方案。The following sample code illustrates each of these scenarios. 可随意在自己的应用程序中使用代码片段。Feel free to use the code snippets in your own application.

概述Overview

我们将要探索的示例应用程序会创建一个名为“hotels”的新索引、用几个文档对该索引进行填充,并执行一些搜索查询。The sample application we'll be exploring creates a new index named "hotels", populates it with a few documents, then executes some search queries. 以下是主程序,演示总体流程:Here 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();

    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);

    ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName);

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

    ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(configuration);

    RunQueries(indexClientForQueries);

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

备注

可以在 GitHub 上找到本演练中所用示例应用程序的完整源代码。You can find the full source code of the sample application used in this walk through on GitHub.

我们将逐步进行介绍。We'll walk through this step by step. 首先,我们需要创建一个新的 SearchServiceClientFirst we need to create a new SearchServiceClient. 使用此对象,可以管理索引。This object allows you to manage indexes. 要构建一个,需要提供 Azure 认知搜索服务名称以及管理 API 密钥。In order to construct one, you need to provide your Azure Cognitive Search service name as well as an admin API key. 可以在示例应用程序appsettings.json 文件中输入此信息。You can enter this information in the appsettings.json file of the sample application.

private static SearchServiceClient CreateSearchServiceClient(IConfigurationRoot configuration)
{
    string searchServiceName = configuration["SearchServiceName"];
    string adminApiKey = configuration["SearchServiceAdminApiKey"];

    SearchServiceClient serviceClient = new SearchServiceClient(searchServiceName, new SearchCredentials(adminApiKey));
    serviceClient.SearchDnsSuffix = "search.azure.cn";
    return serviceClient;
}

备注

如果提供了不正确的密钥(例如,需要管理密钥的查询密钥),首次调用 Indexes.Create 之类上的操作方法时,SearchServiceClient 会引发 CloudException(错误消息为“已禁用”)。If you provide an incorrect key (for example, a query key where an admin key was required), the SearchServiceClient will throw a CloudException with the error message "Forbidden" the first time you call an operation method on it, such as Indexes.Create. 如果遇到此情况,请仔细检查我们的 API 密钥。If this happens to you, double-check our API key.

以下几行调用方法来创建名为“hotels”的索引,如果该索引已存在,请事先删除它。The next few lines call methods to create an index named "hotels", deleting it first if it already exists. 我们稍后会介绍这些方法。We will walk through these methods a little later.

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

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

接下来,需要填充索引。Next, the index needs to be populated. 若要填充索引,需要 SearchIndexClientTo do populate the index, we will need a SearchIndexClient. 有两种方法可获取一个:构建它,或调用 SearchServiceClient 上的 Indexes.GetClientThere are two ways to obtain one: by constructing it, or by calling Indexes.GetClient on the SearchServiceClient. 为方便起见,我们使用后者。We use the latter for convenience.

ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName);

备注

在典型的搜索应用程序中,索引管理和填充可由搜索查询中的一个单独的组件处理。In a typical search application, index management and population may be handled by a separate component from search queries. Indexes.GetClient 对于填充索引很方便,因为使用它则无需提供额外的 SearchCredentialsIndexes.GetClient is convenient for populating an index because it saves you the trouble of providing additional SearchCredentials. 它通过向新 SearchIndexClient 传递用于创建 SearchServiceClient 的管理密钥来实现此目的。It does this by passing the admin key that you used to create the SearchServiceClient to the new SearchIndexClient. 但是,在执行查询的应用程序中,最好是直接创建 SearchIndexClient ,这样可以传入查询密钥(只允许读取数据)而不是管理密钥。However, in the part of your application that executes queries, it is better to create the SearchIndexClient directly so that you can pass in a query key, which only allows you to read data, instead of an admin key. 这与最小特权原则一致,可帮助使应用程序更安全。This is consistent with the principle of least privilege and will help to make your application more secure. 可在此处了解有关管理密钥和查询密钥的详细信息。You can find out more about admin keys and query keys here.

现在,我们已有 SearchIndexClient,可填充索引。Now that we have a SearchIndexClient, we can populate the index. 索引填充是使用稍后介绍的另一种方法完成的。Index population is done by another method that we will walk through later.

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

最后,我们执行一些搜索查询,并显示结果。Finally, we execute a few search queries and display the results. 这次我们使用不同的 SearchIndexClientThis time we use a different SearchIndexClient:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(indexName, configuration);

RunQueries(indexClientForQueries);

我们会在以后详细地查看 RunQueries 方法。We will take a closer look at the RunQueries method later. 下面是用于创建新 SearchIndexClient 的代码:Here is the code to create the new SearchIndexClient:

private static SearchIndexClient CreateSearchIndexClient(string indexName, IConfigurationRoot configuration)
{
    string searchServiceName = configuration["SearchServiceName"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchIndexClient indexClient = new SearchIndexClient(searchServiceName, indexName, new SearchCredentials(queryApiKey));
    indexClient.SearchDnsSuffix = "search.azure.cn";
    return indexClient;
}

这次我们使用查询密钥,因为我们不需要对索引进行写访问。This time we use a query key since we do not need write access to the index. 可以在示例应用程序appsettings.json 文件中输入此信息。You can enter this information in the appsettings.json file of the sample application.

如果使用有效服务名称和 API 密钥运行此应用程序,输出应如以下示例所示:(为方便演示,某些控制台输出已替换为“...”。)If you run this application with a valid service name and API keys, the output should look like this example: (Some console output has been replaced with "..." for illustration purposes.)


Deleting index...

Creating index...

Uploading documents...

Waiting for documents to be indexed...

Search the entire index for the term 'motel' and return only the HotelName field:

Name: Secret Point Motel

Name: Twin Dome Motel


Apply a filter to the index to find hotels with a room cheaper than $100 per night, and return the hotelId and description:

HotelId: 1
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 Times 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.

HotelId: 2
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.


Search the entire index, order by a specific field (lastRenovationDate) in descending order, take the top two results, and show only hotelName and lastRenovationDate:

Name: Triple Landscape Hotel
Last renovated on: 9/20/2015 12:00:00 AM +00:00

Name: Twin Dome Motel
Last renovated on: 2/18/1979 12:00:00 AM +00:00


Search the hotel names for the term 'hotel':

HotelId: 3
Name: Triple Landscape Hotel
...

Complete.  Press any key to end application... 

本文末尾处提供了应用程序的完整源代码。The full source code of the application is provided at the end of this article.

接下来,我们会详细介绍 Main 调用的每个方法。Next, we will take a closer look at each of the methods called by Main.

创建索引Creating an index

创建 SearchServiceClient 后,Main 会删除“hotels”索引(如果该索引已存在)。After creating a SearchServiceClient, Main deletes the "hotels" index if it already exists. 使用以下方法完成该删除操作:That deletion is done by the following method:

private static void DeleteIndexIfExists(string indexName, SearchServiceClient serviceClient)
{
    if (serviceClient.Indexes.Exists(indexName))
    {
        serviceClient.Indexes.Delete(indexName);
    }
}

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

备注

为简单起见,本文中的示例代码使用 Azure 认知搜索 .NET SDK 的同步方法。The example code in this article uses the synchronous methods of the Azure Cognitive Search .NET SDK for simplicity. 建议用户在自己的应用程序中使用异步方法,使应用程序保持可缩放且响应迅速。We recommend that you use the asynchronous methods in your own applications to keep them scalable and responsive. 例如,在上述方法中,可以使用 ExistsAsyncDeleteAsync,而不是 ExistsDeleteFor example, in the method above you could use ExistsAsync and DeleteAsync instead of Exists and Delete.

接下来,Main 通过调用如下方法创建新的“hotels”索引:Next, Main creates a new "hotels" index by calling this method:

private static void CreateIndex(string indexName, SearchServiceClient serviceClient)
{
    var definition = new Index()
    {
        Name = indexName,
        Fields = FieldBuilder.BuildForType<Hotel>()
    };
    
    serviceClient.Indexes.Create(definition);
}

此方法使用用于定义新索引架构的一列 Field 对象创建新的 Index 对象。This method creates a new Index object with a list of Field objects that defines the schema of the new index. 每个字段都有名称、数据类型和数个属性(定义其搜索行为)。Each field has a name, data type, and several attributes that define its search behavior. FieldBuilder 类通过检查给定 Hotel 模型类的公共属性和特性,使用反射来为索引创建 Field 对象的列表。The FieldBuilder class uses reflection to create a list of Field 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.

备注

如果需要,始终可以直接创建 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.

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

填充索引Populating the index

Main 中的下一步骤填充新建的索引。The next step in Main populates the newly-created 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(ISearchIndexClient indexClient)
{
    var hotels = new Hotel[]
    {
        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)",
                    ...
                }
            }
        },
        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)",
                    ...
                }
            }
        },
        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)",
                    ...
                }
            }
        }
    };

    var batch = IndexBatch.Upload(hotels);

    try
    {
        indexClient.Documents.Index(batch);
    }
    catch (IndexBatchException e)
    {
        // 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}",
            String.Join(", ", e.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key)));
    }

    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 your own application, your data will likely come from an external data source such as a SQL database.

第二部分创建包含文档的 IndexBatchThe second part creates an IndexBatch containing the documents. 创建 Batch 时,指定要应用到 Batch 的操作,在这种情况下应调用 IndexBatch.UploadYou specify the operation you want to apply to the batch at the time you create it, in this case by calling IndexBatch.Upload. 然后,使用 Documents.Index 方法将批上传到 Azure 认知搜索索引。The batch is then uploaded to the Azure Cognitive Search index by the Documents.Index method.

备注

在本示例中,我们只需上传文档。In this example, we are just uploading documents. 如果想要将更改合并到现有文档或删除文档,可以改为调用 IndexBatch.MergeIndexBatch.MergeOrUploadIndexBatch.Delete 创建 Batch。If you wanted to merge changes into existing documents or delete documents, you could create batches by calling IndexBatch.Merge, IndexBatch.MergeOrUpload, or IndexBatch.Delete instead. 还可以通过调用 IndexBatch.New 在单个批中混用不同操作,这会用到一个 IndexAction 对象的集合,其中的每个对象都会指示 Azure 认知搜索对文档执行特定操作。You can also mix different operations in a single batch by calling IndexBatch.New, which takes a collection of IndexAction objects, each of which tells Azure Cognitive Search to perform a particular operation on a document. 可以通过调用 IndexAction.MergeIndexAction.Upload 之类的相应方法,创建自带操作的每个 IndexActionYou can create each IndexAction with its own operation by calling the corresponding method such as IndexAction.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. 如果 Azure 认知搜索服务无法为批中的某些文档编制索引,Documents.Index 将引发 IndexBatchExceptionIf your Azure Cognitive Search service fails to index some of the documents in the batch, an IndexBatchException is thrown by Documents.Index. 如果在服务负载过大时为文档编制索引,可能会发生此异常。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.

备注

可以使用 FindFailedActionsToRetry 方法来构造一个新的批处理,其中仅包含上次调用 Index 时失败的操作。You can use the FindFailedActionsToRetry method to construct a new batch containing only the actions that failed in a previous call to Index. StackOverflow 上有如何正确使用该方法的讨论。There is a discussion of how to properly use it on StackOverflow.

最后,UploadDocuments 方法延迟了两秒钟。Finally, the UploadDocuments method delays for two seconds. 编制索引在 Azure 认知搜索服务中异步进行,因此,示例应用程序需要等待很短时间,以确保文档可用于搜索。Indexing happens asynchronously in your Azure Cognitive 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.

.NET SDK 如何处理文档How the .NET SDK handles documents

用户可能想知道 Azure 认知搜索 .NET SDK 如何将用户定义的类(如 Hotel)的实例上传到索引。You may be wondering how the Azure Cognitive Search .NET SDK is able to upload instances of a user-defined class like Hotel to the index. 为了帮助回答这个问题,请看 Hotel 类:To help answer that question, let's look at the Hotel class:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Microsoft.Spatial;
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.EnLucene)]
    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; }

    // SmokingAllowed reflects whether any room in the hotel allows smoking.
    // The JsonIgnore attribute indicates that a field should not be created 
    // in the index for this property and it will only be used by code in the client.
    [JsonIgnore]
    public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

    [IsFilterable, IsSortable, IsFacetable]
    public DateTimeOffset? LastRenovationDate { get; set; }

    [IsFilterable, IsSortable, IsFacetable]
    public double? Rating { get; set; }

    public Address Address { get; set; }

    [IsFilterable, IsSortable]
    public GeographyPoint Location { get; set; }

    public Room[] Rooms { get; set; }
}

首先要注意的是,Hotel 类中的每个公共属性的名称将映射到索引定义中同名的字段。The first thing to notice is that the name of each public property in the Hotel class will map to a field with the same name in the index definition. 如果你希望每个字段以小写字母开头(“camel 大小写”),可以告知 SDK 使用类中的 [SerializePropertyNamesAsCamelCase] 属性自动将属性名称映射为 camel 大小写格式。If you would like each field to start with a lower-case letter ("camel case"), you can tell the SDK to map the property names to camel-case automatically with the [SerializePropertyNamesAsCamelCase] attribute on the class. 这种情况在执行数据绑定的 .NET 应用程序中很常见,其中的目标架构不受应用程序开发人员的控制,且不违反 .NET 中的“Pascal 大小写”命名准则。This scenario is common in .NET applications that perform data-binding where the target schema is outside the control of the application developer without having to violate the "Pascal case" naming guidelines in .NET.

备注

Azure 认知搜索 .NET SDK 使用 NewtonSoft JSON.NET 库将自定义模型对象序列化为 JSON 和从 JSON 反序列化。The Azure Cognitive Search .NET SDK uses the NewtonSoft JSON.NET library to serialize and deserialize your custom model objects to and from JSON. 如果需要,可以自定义此序列化。You can customize this serialization if needed. 有关详细信息,请参阅使用 JSON.NET 的自定义序列For more information, see Custom Serialization with JSON.NET.

第二个要注意的问题是,每个属性使用 IsFilterableIsSearchableKeyAnalyzer 等属性进行修饰。The second thing to notice is each property is decorated with attributes such as IsFilterable, IsSearchable, Key, and Analyzer. 这些属性直接映射到 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.

有关 Hotel 类的第三个重要问题是公共属性的数据类型。The third important thing about the Hotel class is the data types of the public properties. 这些属性的 .NET 类型映射到它们在索引定义中的等效字段类型。The .NET types of these 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. Azure 认知搜索 .NET SDK 参考中的 Documents.Get 方法记录了类型映射的具体规则。The specific rules for the type mapping are documented with the Documents.Get method in the Azure Cognitive Search .NET SDK reference. FieldBuilder 类会处理此映射,但你最好还是了解此映射,以便在需要排查任何序列化问题时可以下手。The FieldBuilder class takes care of this mapping for you, but it can still be helpful to understand in case you need to troubleshoot any serialization issues.

你是否注意到了 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.

某些属性(例如 AddressRooms)是 .NET 类的实例。Some properties such as Address and Rooms are instances of .NET classes. 这些属性表示更复杂的数据结构,因此,需要在索引中使用复杂数据类型的字段。These properties represent more complex data structures and, as a result, require fields with a complex data type in the index.

Address 属性表示 Address 类中的多个值,定义如下:The Address property represents a set of multiple values in the Address class, defined below:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Newtonsoft.Json;

namespace AzureSearch.SDKHowTo
{
    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; }
    }
}

此类包含用于描述美国或加拿大地址的标准值。This class contains the standard values used to describe addresses in the United States or Canada. 可以使用这种类型在索引中组合逻辑字段。You can use types like this to group logical fields together in the index.

Rooms 属性表示 Room 对象的数组:The Rooms property represents an array of Room objects:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Newtonsoft.Json;

namespace AzureSearch.SDKHowTo
{
    public partial class Room
    {
        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.EnMicrosoft)]
        public string Description { get; set; }

        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.FrMicrosoft)]
        [JsonProperty("Description_fr")]
        public string DescriptionFr { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string Type { get; set; }

        [IsFilterable, IsFacetable]
        public double? BaseRate { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string BedOptions { get; set; }

        [IsFilterable, IsFacetable]
        public int SleepsCount { get; set; }

        [IsFilterable, IsFacetable]
        public bool? SmokingAllowed { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string[] Tags { get; set; }
    }
}

.NET 中的数据模型及其相应的索引架构在设计上应该能够支持面向最终用户的搜索体验。Your data model in .NET and its corresponding index schema should be designed to support the search experience you'd like to give to your end user. .NET 中的每个顶级对象(例如索引中的文档)对应于在用户界面中显示的搜索结果。Each top level object in .NET, ie document in the 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. 稍后我们将介绍一些查询示例。We'll cover some query examples a little later.

使用自己的类来与索引中的文档交互的功能可以朝两个方向进行;此外,还可以检索搜索结果,并使用 SDK 自动将结果反序列化为所选类型,我们会在下一节中对此进行介绍。This ability to use your own classes to interact with documents in the index works in both directions; You can also retrieve search results and have the SDK automatically deserialize them to a type of your choice, as we will see in the next section.

备注

Azure 认知搜索 .NET SDK 还使用 Document 类支持动态类型化文档,该类是字段名称到字段值的键/值映射。The Azure Cognitive Search .NET SDK also supports dynamically-typed documents using the Document class, which is a key/value mapping of field names to field values. 如果在设计时不知道索引架构,或者绑定到特定模型类不太方便,这很有用。This is useful in scenarios where you don't know the index schema at design-time, or where it would be inconvenient to bind to specific model classes. 该 SDK 中处理文档的所有方法都有使用 Document 类的重载,以及采用泛型类型参数的强类型重载。All the methods in the SDK that deal with documents have overloads that work with the Document class, as well as strongly-typed overloads that take a generic type parameter. 本教程中的示例代码仅使用后者。Only the latter are used in the sample code in this tutorial. Document继承自 Dictionary<string, object>The Document class inherits from Dictionary<string, object>.

为何应使用可为 null 的数据类型Why you should use nullable data types

设计自己的模型类以映射到 Azure 认知搜索索引时,建议将值类型的属性(如 boolint)声明为可以为 null(例如,bool? 而不是 bool)。When designing your own model classes to map to an Azure Cognitive Search index, we recommend declaring properties of value types such as bool and int to be nullable (for example, bool? instead of bool). 如果使用不可为 null 属性,必须 保证 索引中的所有文档的对应字段都不包含 null 值。If you use a non-nullable property, you have to guarantee that no documents in your index contain a null value for the corresponding field. 该 SDK 和 Azure 认知搜索服务都不会帮助强制实施此检查。Neither the SDK nor the Azure Cognitive Search service will help you to enforce this.

这不只是假想的问题:假设将新字段添加到 Edm.Int32 类型的现有索引。This is not just a hypothetical concern: Imagine a scenario where you add a new field to an existing index that is of type Edm.Int32. 更新索引定义后,所有文档的该新字段都具有 null 值(因为 Azure 认知搜索中的所有类型都可以为 null)。After updating the index definition, all documents will have a null value for that new field (since all types are nullable in Azure Cognitive Search). 如果随后使用该字段具有不可为 null int 属性的模型类,则在尝试检索文档时会获得如下所示的 JsonSerializationExceptionIf you then use a model class with a non-nullable int property for that field, you will get a JsonSerializationException like this when trying to retrieve documents:

Error converting value {null} to type 'System.Int32'. Path 'IntValue'.

由于此原因,最佳做法是建议在模型类中使用可以为 null 的类型。For this reason, we recommend that you use nullable types in your model classes as a best practice.

使用 JSON.NET 的自定义序列Custom Serialization with JSON.NET

SDK 使用 JSON.NET 对文档进行序列化和反序列化。The SDK uses JSON.NET for serializing and deserializing documents. 如果需要,可以通过定义自己的 JsonConverterIContractResolver 来自定义序列化和反序列化。You can customize serialization and deserialization if needed by defining your own JsonConverter or IContractResolver. 有关详细信息,请参阅 JSON.NET 文档For more information, see the JSON.NET documentation. 想要使应用程序中的现有模型类适用于 Azure 认知搜索和其他更高级的方案时,这可能非常有用。This can be useful when you want to adapt an existing model class from your application for use with Azure Cognitive Search, and other more advanced scenarios. 例如,使用自定义序列,可以:For example, with custom serialization you can:

  • 包含或排除模型类的某些属性作为文档字段存储。Include or exclude certain properties of your model class from being stored as document fields.
  • 在代码中的属性名称与索引中的字段名称之间进行映射。Map between property names in your code and field names in your index.
  • 创建可用于将属性映射到文档字段的自定义属性。Create custom attributes that can be used for mapping properties to document fields.

可以在 GitHub 上的 Azure 认知搜索 .NET SDK 的单元测试中找到实现自定义序列化的示例。You can find examples of implementing custom serialization in the unit tests for the Azure Cognitive Search .NET SDK on GitHub. 一个好的起点是此文件夹A good starting point is this folder. 它包含了自定义序列化测试所用的类。It contains classes that are used by the custom serialization tests.

在索引中搜索文档Searching for documents in the index

示例应用程序中的最后一步是在索引中搜索一些文档:The last step in the sample application is to search for some documents in the index:

private static void RunQueries(ISearchIndexClient indexClient)
{
    SearchParameters parameters;
    DocumentSearchResult<Hotel> results;

    Console.WriteLine("Search the entire index for the term 'motel' and return only the HotelName field:\n");

    parameters =
        new SearchParameters()
        {
            Select = new[] { "HotelName" }
        };

    results = indexClient.Documents.Search<Hotel>("motel", parameters);

    WriteDocuments(results);

    Console.Write("Apply a filter to the index to find hotels with a room cheaper than $100 per night, ");
    Console.WriteLine("and return the hotelId and description:\n");

    parameters =
        new SearchParameters()
        {
            Filter = "Rooms/any(r: r/BaseRate lt 100)",
            Select = new[] { "HotelId", "Description" }
        };

    results = indexClient.Documents.Search<Hotel>("*", parameters);

    WriteDocuments(results);

    Console.Write("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");

    parameters =
        new SearchParameters()
        {
            OrderBy = new[] { "LastRenovationDate desc" },
            Select = new[] { "HotelName", "LastRenovationDate" },
            Top = 2
        };

    results = indexClient.Documents.Search<Hotel>("*", parameters);

    WriteDocuments(results);

    Console.WriteLine("Search the entire index for the term 'hotel':\n");

    parameters = new SearchParameters();
    results = indexClient.Documents.Search<Hotel>("hotel", parameters);

    WriteDocuments(results);
}

每次执行查询时,此方法首先创建一个新的 SearchParameters 对象。Each time it executes a query, this method first creates a new SearchParameters object. 此对象用于为查询指定其他选项,如排序、筛选、分页和分面。This object is used to specify additional options for the query such as sorting, filtering, paging, and faceting. 在此方法中,我们要为不同查询设置 FilterSelectOrderByTop 属性。In this method, we're setting the Filter, Select, OrderBy, and Top property for different queries. 所有 SearchParameters 属性在此处进行了说明。All the SearchParameters properties are documented here.

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

备注

可在此处找到有关搜索查询表达式语法的详细信息。You can find more information about the search query expression syntax here.

最后,在每个查询后,该方法循环访问搜索结果中的所有匹配项,将每个文档打印到控制台:Finally, after each query this method iterates through all the matches in the search results, printing each document to the console:

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

    Console.WriteLine();
}

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

parameters =
    new SearchParameters()
    {
        Select = new[] { "HotelName" }
    };

results = indexClient.Documents.Search<Hotel>("motel", parameters);

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 parameter. 结果如下:Here are the results:

Name: Secret Point Motel

Name: Twin Dome Motel

下一个查询更有趣一点。The next query is a little more interesting. 我们想要查找客房价格不超过 100 美元的任何酒店,并仅返回酒店 ID 和说明:We want to find any hotels that have a room with a nightly rate of less than $100 and return only the hotel ID and description:

parameters =
    new SearchParameters()
    {
        Filter = "Rooms/any(r: r/BaseRate lt 100)",
        Select = new[] { "HotelId", "Description" }
    };

results = indexClient.Documents.Search<Hotel>("*", parameters);

WriteDocuments(results);

此查询使用 OData $filter 表达式 Rooms/any(r: r/BaseRate lt 100) 来筛选索引中的文档。This 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. 可在此处找到有关 Azure 认知搜索支持的 OData 语法的详细信息。You can find out more about the OData syntax that Azure Cognitive Search supports here.

下面是查询的结果:Here are the results of the query:

HotelId: 1
Description: The hotel is ideally located on the main commercial artery of the city in the heart of New York...

HotelId: 2
Description: The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to...

接下来,我们想要查找最近翻修的前两个酒店,并显示酒店名称和上次翻修日期。Next, we want to find the top two hotels that have been most recently renovated, and show the hotel name and last renovation date. 代码如下:Here is the code:

parameters =
    new SearchParameters()
    {
        OrderBy = new[] { "LastRenovationDate desc" },
        Select = new[] { "HotelName", "LastRenovationDate" },
        Top = 2
    };

results = indexClient.Documents.Search<Hotel>("*", parameters);

WriteDocuments(results);

在此示例中,我们再次使用 OData 语法来指定 OrderBy 参数作为 lastRenovationDate descIn this case, we again use OData syntax to specify the OrderBy parameter as lastRenovationDate desc. 我们还将 Top 设置为 2 以确保仅获取前两个文档。We also set Top to 2 to ensure we only get the top two documents. 与以前一样,我们设置 Select 以指定应返回的字段。As before, we set Select to specify which fields should be returned.

结果如下:Here are the results:

Name: Fancy Stay        Last renovated on: 6/27/2010 12:00:00 AM +00:00
Name: Roach Motel       Last renovated on: 4/28/1982 12:00:00 AM +00:00

最后,我们想要查找与“motel”一词匹配的所有酒店名称:Finally, we want to find all hotels names that match the word "hotel":

parameters = new SearchParameters()
{
    SearchFields = new[] { "HotelName" }
};
results = indexClient.Documents.Search<Hotel>("hotel", parameters);

WriteDocuments(results);

以下是结果,它包含所有字段,因为我们未指定 Select 属性:And here are the results, which include all fields since we did not specify the Select property:

    HotelId: 3
    Name: Triple Landscape Hotel
    ...

本教程到此步骤结束,但不要就此打住。This step completes the tutorial, but don't stop here. **后续步骤提供了详细了解 Azure 认知搜索的其他资源。**Next steps provide additional resources for learning more about Azure Cognitive Search.

后续步骤Next steps